001package io.ebean.config; 002 003import io.ebean.config.dbplatform.DatabasePlatform; 004import io.ebean.util.AnnotationUtil; 005 006import javax.persistence.DiscriminatorValue; 007import javax.persistence.Inheritance; 008import javax.persistence.Table; 009 010import static io.ebean.util.StringHelper.isNull; 011 012/** 013 * Provides some base implementation for NamingConventions. 014 * 015 * @author emcgreal 016 */ 017public abstract class AbstractNamingConvention implements NamingConvention { 018 019 /** 020 * The Constant DEFAULT_SEQ_FORMAT. 021 */ 022 public static final String DEFAULT_SEQ_FORMAT = "{table}_seq"; 023 024 /** 025 * The catalog. 026 */ 027 private String catalog; 028 029 /** 030 * The schema. 031 */ 032 private String schema; 033 034 /** 035 * The sequence format. 036 */ 037 private String sequenceFormat; 038 039 /** 040 * The database platform. 041 */ 042 protected DatabasePlatform databasePlatform; 043 044 /** 045 * Used to trim off extra prefix for M2M. 046 */ 047 protected int rhsPrefixLength = 3; 048 049 protected boolean useForeignKeyPrefix; 050 051 /** 052 * Construct with a sequence format and useForeignKeyPrefix setting. 053 */ 054 public AbstractNamingConvention(String sequenceFormat, boolean useForeignKeyPrefix) { 055 this.sequenceFormat = sequenceFormat; 056 this.useForeignKeyPrefix = useForeignKeyPrefix; 057 } 058 059 /** 060 * Construct with a sequence format. 061 * 062 * @param sequenceFormat the sequence format 063 */ 064 public AbstractNamingConvention(String sequenceFormat) { 065 this.sequenceFormat = sequenceFormat; 066 this.useForeignKeyPrefix = true; 067 } 068 069 /** 070 * Construct with the default sequence format ("{table}_seq") and useForeignKeyPrefix as true. 071 */ 072 public AbstractNamingConvention() { 073 this(DEFAULT_SEQ_FORMAT); 074 } 075 076 @Override 077 public void setDatabasePlatform(DatabasePlatform databasePlatform) { 078 this.databasePlatform = databasePlatform; 079 } 080 081 @Override 082 public String getSequenceName(String rawTableName, String pkColumn) { 083 TableName tableName = new TableName(rawTableName); 084 String seqName = seqName(pkColumn, tableName.getName()); 085 return tableName.withCatalogAndSchema(seqName); 086 } 087 088 private String seqName(String pkColumn, String tableName) { 089 final String tableNameUnquoted = unQuote(tableName); 090 String seqName = sequenceFormat.replace("{table}", tableNameUnquoted); 091 pkColumn = (pkColumn == null) ? "" : unQuote(pkColumn); 092 return quoteIdentifiers(seqName.replace("{column}", pkColumn)); 093 } 094 095 /** 096 * Return the catalog. 097 */ 098 public String getCatalog() { 099 return catalog; 100 } 101 102 /** 103 * Sets the catalog. 104 */ 105 public void setCatalog(String catalog) { 106 this.catalog = catalog; 107 } 108 109 /** 110 * Return the schema. 111 */ 112 public String getSchema() { 113 return schema; 114 } 115 116 /** 117 * Sets the schema. 118 */ 119 public void setSchema(String schema) { 120 this.schema = schema; 121 } 122 123 /** 124 * Returns the sequence format. 125 */ 126 public String getSequenceFormat() { 127 return sequenceFormat; 128 } 129 130 /** 131 * Set the sequence format used to generate the sequence name. 132 * <p> 133 * The format should include "{table}". When generating the sequence name 134 * {table} is replaced with the actual table name. 135 * </p> 136 * 137 * @param sequenceFormat string containing "{table}" which is replaced with the actual 138 * table name to generate the sequence name. 139 */ 140 public void setSequenceFormat(String sequenceFormat) { 141 this.sequenceFormat = sequenceFormat; 142 } 143 144 /** 145 * Return true if a prefix should be used building a foreign key name. 146 * <p> 147 * This by default is true and this works well when the primary key column 148 * names are simply "ID". In this case a prefix (such as "ORDER" and 149 * "CUSTOMER" etc) is added to the foreign key column producing "ORDER_ID" and 150 * "CUSTOMER_ID". 151 * </p> 152 * <p> 153 * This should return false when your primary key columns are the same as the 154 * foreign key columns. For example, when the primary key columns are 155 * "ORDER_ID", "CUST_ID" etc ... and they are the same as the foreign key 156 * column names. 157 * </p> 158 */ 159 @Override 160 public boolean isUseForeignKeyPrefix() { 161 return useForeignKeyPrefix; 162 } 163 164 /** 165 * Set this to false when the primary key columns matching your foreign key 166 * columns. 167 */ 168 public void setUseForeignKeyPrefix(boolean useForeignKeyPrefix) { 169 this.useForeignKeyPrefix = useForeignKeyPrefix; 170 } 171 172 /** 173 * Return the tableName using the naming convention (rather than deployed 174 * Table annotation). 175 */ 176 protected abstract TableName getTableNameByConvention(Class<?> beanClass); 177 178 /** 179 * Returns the table name for a given entity bean. 180 * <p> 181 * This first checks for the @Table annotation and if not present uses the 182 * naming convention to define the table name. 183 * </p> 184 * 185 * @see #getTableNameFromAnnotation(Class) 186 * @see #getTableNameByConvention(Class) 187 */ 188 @Override 189 public TableName getTableName(Class<?> beanClass) { 190 while (true) { 191 192 TableName tableName = getTableNameFromAnnotation(beanClass); 193 if (tableName == null) { 194 Class<?> supCls = beanClass.getSuperclass(); 195 if (hasInheritance(supCls)) { 196 // get the table as per inherited class in case there 197 // is not a table annotation in the inheritance hierarchy 198 beanClass = supCls; 199 continue; 200 } 201 202 tableName = getTableNameByConvention(beanClass); 203 } 204 205 // Use naming convention for catalog or schema, 206 // if not set in the annotation. 207 String catalog = tableName.getCatalog(); 208 if (isEmpty(catalog)) { 209 catalog = getCatalog(); 210 } 211 String schema = tableName.getSchema(); 212 if (isEmpty(schema)) { 213 schema = getSchema(); 214 } 215 return new TableName(catalog, schema, tableName.getName()); 216 } 217 } 218 219 /** 220 * Return true if this class is part of entity inheritance. 221 */ 222 protected boolean hasInheritance(Class<?> supCls) { 223 return AnnotationUtil.typeHas(supCls, Inheritance.class) 224 || AnnotationUtil.has(supCls, DiscriminatorValue.class); 225 } 226 227 @Override 228 public TableName getM2MJoinTableName(TableName lhsTable, TableName rhsTable) { 229 StringBuilder buffer = new StringBuilder(); 230 buffer.append(unQuote(lhsTable.getName())); 231 buffer.append("_"); 232 233 String rhsTableName = unQuote(rhsTable.getName()); 234 if (rhsTableName.indexOf('_') < rhsPrefixLength) { 235 // trim off a xx_ prefix if there is one 236 rhsTableName = rhsTableName.substring(rhsTableName.indexOf('_') + 1); 237 } 238 buffer.append(rhsTableName); 239 240 int maxTableNameLength = databasePlatform.getMaxTableNameLength(); 241 242 // maxConstraintNameLength is used as the max table name length. 243 if (buffer.length() > maxTableNameLength) { 244 buffer.setLength(maxTableNameLength); 245 } 246 247 String tableName = quoteIdentifiers(buffer.toString()); 248 return new TableName(lhsTable.getCatalog(), lhsTable.getSchema(), tableName); 249 } 250 251 @Override 252 public String deriveM2MColumn(String tableName, String dbColumn) { 253 return quoteIdentifiers(unQuote(tableName) +"_" + unQuote(dbColumn)); 254 } 255 256 /** 257 * Gets the table name from annotation. 258 */ 259 protected TableName getTableNameFromAnnotation(Class<?> beanClass) { 260 final Table table = AnnotationUtil.typeGet(beanClass, Table.class); 261 if (table != null && !isEmpty(table.name())) { 262 // Note: empty catalog and schema are converted to null 263 // Only need to convert quoted identifiers from annotations 264 return new TableName(quoteIdentifiers(table.catalog()), quoteIdentifiers(table.schema()), quoteIdentifiers(table.name())); 265 } 266 // No annotation 267 return null; 268 } 269 270 @Override 271 public String getTableName(String catalog, String schema, String name) { 272 StringBuilder sb = new StringBuilder(); 273 if (!isNull(catalog)) { 274 sb.append(quoteIdentifiers(catalog)).append("."); 275 } 276 if (!isNull(schema)) { 277 sb.append(quoteIdentifiers(schema)).append("."); 278 } 279 return sb.append(quoteIdentifiers(name)).toString(); 280 } 281 282 /** 283 * Replace back ticks (if they are used) with database platform specific quoted identifiers. 284 */ 285 protected String quoteIdentifiers(String s) { 286 return databasePlatform.convertQuotedIdentifiers(s); 287 } 288 289 private String unQuote(String val) { 290 return databasePlatform.unQuote(val); 291 } 292 293 /** 294 * Checks string is null or empty . 295 */ 296 protected boolean isEmpty(String s) { 297 return s == null || s.trim().isEmpty(); 298 } 299 300 /** 301 * Load settings from properties. 302 */ 303 @Override 304 public void loadFromProperties(PropertiesWrapper properties) { 305 306 useForeignKeyPrefix = properties.getBoolean("namingConvention.useForeignKeyPrefix", useForeignKeyPrefix); 307 sequenceFormat = properties.get("namingConvention.sequenceFormat", sequenceFormat); 308 schema = properties.get("namingConvention.schema", schema); 309 } 310 311}