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}