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