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