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