/**********************************************************************
Copyright (c) 2006 Andy Jefferson and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Contributors:
...
**********************************************************************/
package org.jpox.store.rdbms.sqlidentifier;
import java.util.Map;
import java.util.WeakHashMap;
import org.jpox.exceptions.JPOXException;
import org.jpox.metadata.FieldRole;
import org.jpox.store.mapped.AbstractIdentifierFactory;
import org.jpox.store.mapped.DatastoreAdapter;
import org.jpox.store.mapped.DatastoreContainerObject;
import org.jpox.store.mapped.DatastoreIdentifier;
import org.jpox.store.mapped.IdentifierFactory;
import org.jpox.store.rdbms.JDBCUtils;
import org.jpox.store.rdbms.adapter.RDBMSAdapter;
import org.jpox.store.rdbms.exceptions.TooManyForeignKeysException;
import org.jpox.store.rdbms.exceptions.TooManyIndicesException;
import org.jpox.util.StringUtils;
/**
* Abstract representation of an identifier factory for RDBMS datastores.
* To be extended to generate the identifiers.
*
* @version $Revision: 1.25 $
*/
public abstract class AbstractRDBMSIdentifierFactory extends AbstractIdentifierFactory implements RDBMSIdentifierFactory
{
protected Map tables = new WeakHashMap();
protected Map columns = new WeakHashMap();
protected Map foreignkeys = new WeakHashMap();
protected Map indexes = new WeakHashMap();
protected Map candidates = new WeakHashMap();
protected Map primarykeys = new WeakHashMap();
protected Map sequences = new WeakHashMap();
protected Map references = new WeakHashMap();
/** Separator to use for words in the identifiers. */
protected String wordSeparator = "_";
/** Convenience RDBMSAdapter variable to save casting. */
protected RDBMSAdapter rdba = null;
/** Default catalog name for any created identifiers. */
protected String defaultCatalogName = null;
/** Default schema name for any created identifiers. */
protected String defaultSchemaName = null;
/**
* Constructor.
* @param dba Database adapter
* @param requiredCase The case the user requires
* @param defaultCatalog Name of the default catalog (if any)
* @param defaultSchema Name of the default schema (if any)
*/
public AbstractRDBMSIdentifierFactory(DatastoreAdapter dba, String requiredCase, String defaultCatalog, String defaultSchema)
{
super(dba, requiredCase);
rdba = (RDBMSAdapter)dba;
this.defaultCatalogName = defaultCatalog;
this.defaultSchemaName = defaultSchema;
}
/**
* Accessor for the word separator for identifiers.
* @return The word separator
*/
public String getWordSeparator()
{
return wordSeparator;
}
/**
* Convenience method to convert the passed identifier into an identifier
* in the correct case, and with any required quoting for the datastore adapter.
* If the identifier is already quoted and needs quotes then none are added.
* @param identifier The identifier
* @return The updated identifier in the correct case
*/
public String getIdentifierInAdapterCase(String identifier)
{
if (identifier == null)
{
return null;
}
StringBuffer id = new StringBuffer();
if (identifierCase == IDENTIFIER_LOWER_CASE_QUOTED ||
identifierCase == IDENTIFIER_MIXED_CASE_QUOTED ||
identifierCase == IDENTIFIER_UPPER_CASE_QUOTED)
{
if (!identifier.startsWith(dba.getIdentifierQuoteString()))
{
id.append(dba.getIdentifierQuoteString());
}
}
if (identifierCase == IDENTIFIER_LOWER_CASE ||
identifierCase == IDENTIFIER_LOWER_CASE_QUOTED)
{
id.append(identifier.toLowerCase());
}
else if (identifierCase == IDENTIFIER_UPPER_CASE ||
identifierCase == IDENTIFIER_UPPER_CASE_QUOTED)
{
id.append(identifier.toUpperCase());
}
else
{
id.append(identifier);
}
if (identifierCase == IDENTIFIER_LOWER_CASE_QUOTED ||
identifierCase == IDENTIFIER_MIXED_CASE_QUOTED ||
identifierCase == IDENTIFIER_UPPER_CASE_QUOTED)
{
if (!identifier.endsWith(dba.getIdentifierQuoteString()))
{
id.append(dba.getIdentifierQuoteString());
}
}
return id.toString();
}
/**
* Method to generate an identifier based on the supplied name for the requested type of identifier.
* @param identifierType the type of identifier to be created
* @param name The Java or SQL identifier name
* @return The DatastoreIdentifier
*/
public DatastoreIdentifier newIdentifier(int identifierType, String name)
{
DatastoreIdentifier identifier = null;
String key = JDBCUtils.getIdentifierNameStripped(name, rdba); // Remove any user/JDBC supplied quotes
if (identifierType == TABLE)
{
identifier = (DatastoreIdentifier) tables.get(key);
if (identifier == null)
{
String sqlIdentifier = generateIdentifierNameForJavaName(key);
sqlIdentifier = truncate(sqlIdentifier, getMaxLengthForIdentifierType(identifierType));
identifier = new TableIdentifier(this, sqlIdentifier);
setCatalogSchemaForTable((TableIdentifier)identifier);
tables.put(key, identifier);
}
}
else if (identifierType == COLUMN)
{
identifier = (DatastoreIdentifier) columns.get(key);
if (identifier == null)
{
String sqlIdentifier = generateIdentifierNameForJavaName(key);
sqlIdentifier = truncate(sqlIdentifier, getMaxLengthForIdentifierType(identifierType));
identifier = new ColumnIdentifier(this, sqlIdentifier);
columns.put(key, identifier);
}
}
else if (identifierType == FOREIGN_KEY)
{
identifier = (DatastoreIdentifier) foreignkeys.get(key);
if (identifier == null)
{
String sqlIdentifier = generateIdentifierNameForJavaName(key);
sqlIdentifier = truncate(sqlIdentifier, getMaxLengthForIdentifierType(identifierType));
identifier = new ForeignKeyIdentifier(this, sqlIdentifier);
foreignkeys.put(key, identifier);
}
}
else if (identifierType == INDEX)
{
identifier = (DatastoreIdentifier) indexes.get(key);
if (identifier == null)
{
String sqlIdentifier = generateIdentifierNameForJavaName(key);
sqlIdentifier = truncate(sqlIdentifier, getMaxLengthForIdentifierType(identifierType));
identifier = new IndexIdentifier(this, sqlIdentifier);
indexes.put(key, identifier);
}
}
else if (identifierType == CANDIDATE_KEY)
{
identifier = (DatastoreIdentifier) candidates.get(key);
if (identifier == null)
{
String sqlIdentifier = generateIdentifierNameForJavaName(key);
sqlIdentifier = truncate(sqlIdentifier, getMaxLengthForIdentifierType(identifierType));
identifier = new CandidateKeyIdentifier(this, sqlIdentifier);
candidates.put(key, identifier);
}
}
else if (identifierType == PRIMARY_KEY)
{
identifier = (DatastoreIdentifier) primarykeys.get(key);
if (identifier == null)
{
String sqlIdentifier = generateIdentifierNameForJavaName(key);
sqlIdentifier = truncate(sqlIdentifier, getMaxLengthForIdentifierType(identifierType));
identifier = new PrimaryKeyIdentifier(this, sqlIdentifier);
primarykeys.put(key, identifier);
}
}
else if (identifierType == SEQUENCE)
{
identifier = (DatastoreIdentifier) sequences.get(key);
if (identifier == null)
{
String sqlIdentifier = generateIdentifierNameForJavaName(key);
sqlIdentifier = truncate(sqlIdentifier, getMaxLengthForIdentifierType(identifierType));
identifier = new SequenceIdentifier(this, sqlIdentifier);
sequences.put(key, identifier);
}
}
else
{
throw new JPOXException("identifier type " + identifierType + " not supported by this factory method").setFatal();
}
return identifier;
}
/**
* Method to return a new Identifier based on the passed identifier, but adding on the passed suffix
* @param identifier The current identifier
* @param suffix The suffix
* @return The new identifier
*/
public DatastoreIdentifier newIdentifier(DatastoreIdentifier identifier, String suffix)
{
String newId = identifier.getIdentifier() + getWordSeparator() + suffix;
if (identifier instanceof TableIdentifier)
{
newId = truncate(newId, getMaxLengthForIdentifierType(IdentifierFactory.TABLE));
TableIdentifier tableIdentifier = new TableIdentifier(this, newId);
setCatalogSchemaForTable(tableIdentifier);
return tableIdentifier;
}
else if (identifier instanceof ColumnIdentifier)
{
newId = truncate(newId, getMaxLengthForIdentifierType(IdentifierFactory.COLUMN));
return new ColumnIdentifier(this, newId);
}
else if (identifier instanceof ForeignKeyIdentifier)
{
newId = truncate(newId, getMaxLengthForIdentifierType(IdentifierFactory.FOREIGN_KEY));
return new ForeignKeyIdentifier(this, newId);
}
else if (identifier instanceof IndexIdentifier)
{
newId = truncate(newId, getMaxLengthForIdentifierType(IdentifierFactory.INDEX));
return new IndexIdentifier(this, newId);
}
else if (identifier instanceof CandidateKeyIdentifier)
{
newId = truncate(newId, getMaxLengthForIdentifierType(IdentifierFactory.CANDIDATE_KEY));
return new CandidateKeyIdentifier(this, newId);
}
else if (identifier instanceof PrimaryKeyIdentifier)
{
newId = truncate(newId, getMaxLengthForIdentifierType(IdentifierFactory.PRIMARY_KEY));
return new PrimaryKeyIdentifier(this, newId);
}
else if (identifier instanceof SequenceIdentifier)
{
newId = truncate(newId, getMaxLengthForIdentifierType(IdentifierFactory.SEQUENCE));
return new SequenceIdentifier(this, newId);
}
return null;
}
/**
* Method to use to generate an identifier for a datastore field.
* The passed name will not be changed (other than in its case) although it may
* be truncated to fit the maximum length permitted for a datastore field identifier.
* @param identifierName The identifier name
* @return The DatastoreIdentifier for the table
*/
public DatastoreIdentifier newDatastoreContainerIdentifier(String identifierName)
{
String key = JDBCUtils.getIdentifierNameStripped(identifierName, dba); // Allow for quotes on input name
DatastoreIdentifier identifier = (DatastoreIdentifier) tables.get(key);
if (identifier == null)
{
String baseID = truncate(key, getMaxLengthForIdentifierType(IdentifierFactory.TABLE));
identifier = new TableIdentifier(this, baseID);
setCatalogSchemaForTable((TableIdentifier)identifier);
tables.put(key, identifier);
}
return identifier;
}
/**
* Method to use to generate an identifier for a datastore field.
* The passed name will not be changed (other than in its case) although it may
* be truncated to fit the maximum length permitted for a datastore field identifier.
* @param identifierName The identifier name
* @return The DatastoreIdentifier
*/
public DatastoreIdentifier newDatastoreFieldIdentifier(String identifierName)
{
String key = JDBCUtils.getIdentifierNameStripped(identifierName, dba); // Allow for quotes on input names
DatastoreIdentifier identifier = (DatastoreIdentifier) columns.get(key);
if (identifier == null)
{
String baseID = truncate(key, getMaxLengthForIdentifierType(IdentifierFactory.COLUMN));
identifier = new ColumnIdentifier(this, baseID);
columns.put(key, identifier);
}
return identifier;
}
/**
* Method to create an identifier for a datastore field where we want the
* name based on the supplied java name, and the field has a particular
* role (and so could have its naming set according to the role).
* @param javaName The java field name
* @param embedded Whether the identifier is for a field embedded
* @param fieldRole The role to be performed by this column e.g FK, Index ?
* @return The DatastoreIdentifier
*/
public DatastoreIdentifier newDatastoreFieldIdentifier(String javaName, boolean embedded, int fieldRole)
{
DatastoreIdentifier identifier = null;
String key = "[" + (javaName == null ? "" : javaName) + "][" + embedded + "][" + fieldRole; // TODO Change this to a string form of fieldRole
identifier = (DatastoreIdentifier) columns.get(key);
if (identifier == null)
{
if (fieldRole == FieldRole.ROLE_CUSTOM)
{
// If the user has provided a name (CUSTOM) so dont need to generate it and dont need a suffix
String baseID = truncate(javaName, getMaxLengthForIdentifierType(IdentifierFactory.COLUMN));
identifier = new ColumnIdentifier(this, baseID);
}
else
{
String suffix = getColumnIdentifierSuffix(fieldRole, embedded);
String datastoreID = generateIdentifierNameForJavaName(javaName);
String baseID = truncate(datastoreID, getMaxLengthForIdentifierType(IdentifierFactory.COLUMN) - suffix.length());
identifier = new ColumnIdentifier(this, baseID + suffix);
}
columns.put(key, identifier);
}
return identifier;
}
/**
* Method to generate an identifier for a sequence using the passed name.
* @param sequenceName the name of the sequence to use
* @return The DatastoreIdentifier
*/
public DatastoreIdentifier newSequenceIdentifier(String sequenceName)
{
String key = sequenceName;
DatastoreIdentifier identifier = (DatastoreIdentifier) sequences.get(key);
if (identifier == null)
{
String baseID = truncate(sequenceName, getMaxLengthForIdentifierType(IdentifierFactory.SEQUENCE));
identifier = new ColumnIdentifier(this, baseID);
sequences.put(key, identifier);
}
return identifier;
}
/**
* Method to generate an identifier for a primary key for the supplied table.
* @param table the table
* @return The DatastoreIdentifier
*/
public DatastoreIdentifier newPrimaryKeyIdentifier(DatastoreContainerObject table)
{
DatastoreIdentifier identifier = null;
String key = table.getIdentifier().toString();
identifier = (DatastoreIdentifier) primarykeys.get(key);
if (identifier == null)
{
String suffix = getWordSeparator() + "PK";
int maxLength = getMaxLengthForIdentifierType(IdentifierFactory.PRIMARY_KEY);
String baseID = truncate(((SQLIdentifier) table.getIdentifier()).getIdentifier(), maxLength - suffix.length());
identifier = new PrimaryKeyIdentifier(this, baseID + suffix);
primarykeys.put(key, identifier);
}
return identifier;
}
/**
* Method to generate an identifier for a candidate key in the supplied table.
* @param table the table
* @param seq the sequential number
* @return The DatastoreIdentifier
*/
public DatastoreIdentifier newCandidateKeyIdentifier(DatastoreContainerObject table, int seq)
{
DatastoreIdentifier identifier = null;
String key = "[" + table.getIdentifier().toString() + "][" + seq + "]";
identifier = (DatastoreIdentifier) candidates.get(key);
if (identifier == null)
{
String suffix = getWordSeparator() + "U" + seq;
int maxLength = getMaxLengthForIdentifierType(IdentifierFactory.CANDIDATE_KEY);
String baseID = truncate(((SQLIdentifier) table.getIdentifier()).getIdentifier(), maxLength - suffix.length());
identifier = new CandidateKeyIdentifier(this, baseID + suffix);
candidates.put(key, identifier);
}
return identifier;
}
/**
* Method to create a new identifier for a foreign key in the supplied table.
* @param table the table
* @param seq the sequential number
* @return The DatastoreIdentifier
*/
public DatastoreIdentifier newForeignKeyIdentifier(DatastoreContainerObject table, int seq)
{
DatastoreIdentifier identifier = null;
String key = "[" + table.getIdentifier().toString() + "][" + seq + "]";
identifier = (DatastoreIdentifier) foreignkeys.get(key);
if (identifier == null)
{
String suffix = getWordSeparator() + "FK";
if (seq < 10)
{
suffix += "" + (char)('0' + seq);
}
else if (seq < rdba.getMaxForeignKeys())
{
suffix += Integer.toHexString('A' + seq);
}
else
{
throw new TooManyForeignKeysException(rdba, table.toString());
}
int maxLength = getMaxLengthForIdentifierType(IdentifierFactory.FOREIGN_KEY);
String baseID = truncate(((SQLIdentifier) table.getIdentifier()).getIdentifier(), maxLength - suffix.length());
identifier = new ForeignKeyIdentifier(this, baseID + suffix);
foreignkeys.put(key, identifier);
}
return identifier;
}
/**
* Method to create an identifier for an Index in the supplied table.
* @param table the table
* @param isUnique if the index is unique
* @param seq the sequential number
* @return The DatastoreIdentifier
*/
public DatastoreIdentifier newIndexIdentifier(DatastoreContainerObject table, boolean isUnique, int seq)
{
DatastoreIdentifier identifier = null;
String key = "[" + table.getIdentifier().toString() + "][" + isUnique + "][" + seq + "]";
identifier = (DatastoreIdentifier) indexes.get(key);
if (identifier == null)
{
String suffix = getWordSeparator() + (isUnique ? "U" : "N");
if (seq < rdba.getMaxIndexes())
{
suffix += String.valueOf('0' + seq);
}
else
{
throw new TooManyIndicesException(rdba, table.toString());
}
int maxLength = getMaxLengthForIdentifierType(IdentifierFactory.INDEX);
String baseID = truncate(((SQLIdentifier) table.getIdentifier()).getIdentifier(), maxLength - suffix.length());
identifier = new IndexIdentifier(this, baseID + suffix);
indexes.put(key, identifier);
}
return identifier;
}
/**
* Accessor for the suffix to add to any column identifier, based on the role type.
* @param role Datastore field role
* @param embedded Whether the DatastoreField is stored embedded
* @return The suffix (e.g _ID for id columns).
**/
protected abstract String getColumnIdentifierSuffix(int role, boolean embedded);
/**
* Generate a datastore identifier from a Java identifier.
* Embodies the naming rules for the factory.
* @param javaName the Java identifier.
* @return The datastore identifier
*/
protected abstract String generateIdentifierNameForJavaName(String javaName);
/**
* Convenience method to set the catalog/schema on the passed TableIdentifier.
* @param identifier The TableIdentifier
*/
protected void setCatalogSchemaForTable(TableIdentifier identifier)
{
String catalogName = identifier.getCatalogName();
String schemaName = identifier.getSchemaName();
if (schemaName == null && catalogName == null)
{
// Still no values, so try the PMF settings.
if (rdba.supportsCatalogsInTableDefinitions())
{
identifier.setCatalogName(this.defaultCatalogName);
}
if (rdba.supportsSchemasInTableDefinitions())
{
identifier.setSchemaName(this.defaultSchemaName);
}
}
}
/**
* Convenience method to split a fully-specified identifier name (inc catalog/schema)
* into its constituent parts. Returns a String array with 3 elements. The first is the
* catalog, second the schema, and third the identifier.
* @param name Name
* @return The parts
*/
protected String[] getIdentifierNamePartsFromName(String name)
{
if (name != null)
{
String[] names = new String[3];
if (name.indexOf('.') < 0)
{
names[0] = null;
names[1] = null;
names[2] = name;
}
else
{
String[] specifiedNameParts = StringUtils.split(name, ".");
int currentPartIndex = specifiedNameParts.length-1;
names[2] = specifiedNameParts[currentPartIndex--];
if (rdba.supportsSchemasInTableDefinitions() && currentPartIndex >= 0)
{
names[1] = specifiedNameParts[currentPartIndex--];
}
if (rdba.supportsCatalogsInTableDefinitions() && currentPartIndex >= 0)
{
names[0] = specifiedNameParts[currentPartIndex--];
}
}
return names;
}
return null;
}
/**
* Method to return the maximum permitted length of an identifier of the
* specified type. Returns -1 if the identifier type is not supported.
* @param identifierType Type of identifier
* @return The maximum length
*/
protected int getMaxLengthForIdentifierType(int identifierType)
{
if (identifierType == TABLE)
{
return rdba.getMaxTableNameLength();
}
else if (identifierType == COLUMN)
{
return rdba.getMaxColumnNameLength();
}
else if (identifierType == FOREIGN_KEY)
{
return rdba.getMaxConstraintNameLength();
}
else if (identifierType == INDEX)
{
return rdba.getMaxConstraintNameLength();
}
else if (identifierType == CANDIDATE_KEY)
{
return rdba.getMaxConstraintNameLength();
}
else if (identifierType == PRIMARY_KEY)
{
return rdba.getMaxConstraintNameLength();
}
else if (identifierType == SEQUENCE)
{
return rdba.getMaxTableNameLength();
}
else
{
return -1;
}
}
}