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}