Revision: 4162
http://datanucleus.svn.sourceforge.net/datanucleus/?rev=4162&view=rev
Author: andy_jefferson
Date: 2008-12-02 15:26:10 +0000 (Tue, 02 Dec 2008)
Log Message:
-----------
[NUCRDBMS-79] Factor a few more things down into DatastoreAdapter/DatastoreIdentifier. Refactor org.datanucleus.store.rdbms.sqlidentifier to org.datanucleus.store.mapped.identifier
Added Paths:
-----------
platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/
platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/AbstractRDBMSIdentifierFactory.java
platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/CandidateKeyIdentifier.java
platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/ColumnIdentifier.java
platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/ForeignKeyIdentifier.java
platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/IndexIdentifier.java
platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/JPAIdentifierFactory.java
platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/JPOX2IdentifierFactory.java
platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/JPOXIdentifierFactory.java
platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/PrimaryKeyIdentifier.java
platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/SQLIdentifier.java
platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/SequenceIdentifier.java
platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/TableIdentifier.java
platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/package.html
Added: platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/AbstractRDBMSIdentifierFactory.java
===================================================================
--- platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/AbstractRDBMSIdentifierFactory.java (rev 0)
+++ platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/AbstractRDBMSIdentifierFactory.java 2008-12-02 15:26:10 UTC (rev 4162)
@@ -0,0 +1,864 @@
+/**********************************************************************
+ 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.datanucleus.store.mapped.identifier;
+
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.datanucleus.ClassLoaderResolver;
+import org.datanucleus.exceptions.NucleusException;
+import org.datanucleus.exceptions.NucleusUserException;
+import org.datanucleus.metadata.FieldRole;
+import org.datanucleus.store.mapped.DatastoreAdapter;
+import org.datanucleus.store.mapped.DatastoreContainerObject;
+import org.datanucleus.store.mapped.DatastoreIdentifier;
+import org.datanucleus.store.mapped.IdentifierFactory;
+import org.datanucleus.store.rdbms.RDBMSManager;
+import org.datanucleus.store.rdbms.adapter.RDBMSAdapter;
+import org.datanucleus.store.rdbms.exceptions.TooManyForeignKeysException;
+import org.datanucleus.store.rdbms.exceptions.TooManyIndicesException;
+import org.datanucleus.util.Localiser;
+import org.datanucleus.util.NucleusLogger;
+import org.datanucleus.util.StringUtils;
+
+/**
+ * Abstract representation of an identifier factory for RDBMS datastores.
+ * To be extended to generate the identifiers.
+ */
+public abstract class AbstractRDBMSIdentifierFactory implements IdentifierFactory
+{
+ /** Localiser for messages. */
+ protected static final Localiser LOCALISER = Localiser.getInstance(
+ "org.datanucleus.store.rdbms.Localisation", RDBMSManager.class.getClassLoader());
+
+ public static final int CASE_PRESERVE = 1;
+ public static final int CASE_UPPER = 2;
+ public static final int CASE_LOWER = 3;
+
+ /** Adapter for the datastore. */
+ protected DatastoreAdapter dba;
+
+ /** Resolver for any class loading. */
+ protected ClassLoaderResolver clr;
+
+ /** Case to use for identifiers. */
+ protected int identifierCase;
+
+ protected String quoteString;
+
+ /**
+ * The number of characters used to build the hash.
+ */
+ private static final int HASH_LENGTH = 4;
+
+ /**
+ * Range to use for creating hashed ending when truncating identifiers. The actual hashes have a value
+ * between 0 and <code>HASH_RANGE</code> - 1.
+ */
+ private static final int HASH_RANGE = calculateHashMax();
+ private static final int calculateHashMax()
+ {
+ int hm = 1;
+ for (int i = 0; i < HASH_LENGTH; ++i)
+ {
+ hm *= Character.MAX_RADIX;
+ }
+
+ return hm;
+ }
+
+ protected Map<String, DatastoreIdentifier> tables = new WeakHashMap();
+ protected Map<String, DatastoreIdentifier> columns = new WeakHashMap();
+ protected Map<String, DatastoreIdentifier> foreignkeys = new WeakHashMap();
+ protected Map<String, DatastoreIdentifier> indexes = new WeakHashMap();
+ protected Map<String, DatastoreIdentifier> candidates = new WeakHashMap();
+ protected Map<String, DatastoreIdentifier> primarykeys = new WeakHashMap();
+ protected Map<String, DatastoreIdentifier> sequences = new WeakHashMap();
+ protected Map<String, DatastoreIdentifier> 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.
+ * The properties accepted are
+ * <ul>
+ * <li>RequiredCase : what case the identifiers should be in</li>
+ * <li>DefaultCatalog : default catalog to use (if any)</li>
+ * <li>DefaultSchema : default schema to use (if any)</li>
+ * </ul>
+ * @param dba Database adapter
+ * @param clr ClassLoader resolver
+ * @param properties Any properties controlling identifier generation
+ */
+ public AbstractRDBMSIdentifierFactory(DatastoreAdapter dba, ClassLoaderResolver clr, Map props)
+ {
+ this.dba = dba;
+ this.rdba = (RDBMSAdapter)dba;
+ this.clr = clr;
+ this.quoteString = dba.getIdentifierQuoteString();
+
+ // Set the identifier case to be used based on what the user has requested and what the datastore supports
+ int userIdentifierCase = CASE_UPPER;
+ if (props.containsKey("RequiredCase"))
+ {
+ String requiredCase = (String)props.get("RequiredCase");
+ if (requiredCase.equalsIgnoreCase("UpperCase"))
+ {
+ userIdentifierCase = CASE_UPPER;
+ }
+ else if (requiredCase.equalsIgnoreCase("LowerCase"))
+ {
+ userIdentifierCase = CASE_LOWER;
+ }
+ else if (requiredCase.equalsIgnoreCase("PreserveCase"))
+ {
+ userIdentifierCase = CASE_PRESERVE;
+ }
+ }
+ if (userIdentifierCase == CASE_UPPER)
+ {
+ if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_UPPERCASE))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_UPPER_CASE;
+ }
+ else if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_UPPERCASE_QUOTED))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_UPPER_CASE_QUOTED;
+ }
+ else if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_MIXEDCASE) ||
+ dba.supportsOption(RDBMSAdapter.IDENTIFIERS_MIXEDCASE_SENSITIVE))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_UPPER_CASE;
+ }
+ else if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_MIXEDCASE_QUOTED) ||
+ dba.supportsOption(RDBMSAdapter.IDENTIFIERS_MIXEDCASE_QUOTED_SENSITIVE))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_UPPER_CASE_QUOTED;
+ }
+ else
+ {
+ if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_LOWERCASE))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_LOWER_CASE;
+ NucleusLogger.PERSISTENCE.warn(LOCALISER.msg("039001","UPPERCASE", "LOWERCASE"));
+ }
+ else if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_LOWERCASE_QUOTED))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_LOWER_CASE_QUOTED;
+ NucleusLogger.PERSISTENCE.warn(LOCALISER.msg("039001","UPPERCASE", "LOWERCASEQUOTED"));
+ }
+ else
+ {
+ // Should never happen since we've tried all options
+ throw new NucleusUserException(LOCALISER.msg("039002", "UPPERCASE")).setFatal();
+ }
+ }
+ }
+ else if (userIdentifierCase == CASE_LOWER)
+ {
+ if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_LOWERCASE))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_LOWER_CASE;
+ }
+ else if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_LOWERCASE_QUOTED))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_LOWER_CASE_QUOTED;
+ }
+ else if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_MIXEDCASE) ||
+ dba.supportsOption(RDBMSAdapter.IDENTIFIERS_MIXEDCASE_SENSITIVE))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_LOWER_CASE;
+ }
+ else if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_MIXEDCASE_QUOTED) ||
+ dba.supportsOption(RDBMSAdapter.IDENTIFIERS_MIXEDCASE_QUOTED_SENSITIVE))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_LOWER_CASE_QUOTED;
+ }
+ else
+ {
+ if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_UPPERCASE))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_UPPER_CASE;
+ NucleusLogger.PERSISTENCE.warn(LOCALISER.msg("039001","LOWERCASE", "UPPERCASE"));
+ }
+ else if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_UPPERCASE_QUOTED))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_UPPER_CASE_QUOTED;
+ NucleusLogger.PERSISTENCE.warn(LOCALISER.msg("039001","LOWERCASE", "UPPERCASEQUOTED"));
+ }
+ else
+ {
+ // Should never happen since we've tried all options
+ throw new NucleusUserException(LOCALISER.msg("039002", "LOWERCASE")).setFatal();
+ }
+ }
+ }
+ else if (userIdentifierCase == CASE_PRESERVE)
+ {
+ if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_MIXEDCASE) ||
+ dba.supportsOption(RDBMSAdapter.IDENTIFIERS_MIXEDCASE_SENSITIVE))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_MIXED_CASE;
+ }
+ else if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_MIXEDCASE_QUOTED) ||
+ dba.supportsOption(RDBMSAdapter.IDENTIFIERS_MIXEDCASE_QUOTED_SENSITIVE))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_MIXED_CASE_QUOTED;
+ }
+ else
+ {
+ if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_LOWERCASE))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_LOWER_CASE;
+ NucleusLogger.PERSISTENCE.warn(LOCALISER.msg("039001","MIXEDCASE", "LOWERCASE"));
+ }
+ else if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_LOWERCASE_QUOTED))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_LOWER_CASE_QUOTED;
+ NucleusLogger.PERSISTENCE.warn(LOCALISER.msg("039001", "MIXEDCASE", "LOWERCASEQUOTED"));
+ }
+ else if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_UPPERCASE))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_UPPER_CASE;
+ NucleusLogger.PERSISTENCE.warn(LOCALISER.msg("039001", "MIXEDCASE", "UPPERCASE"));
+ }
+ else if (dba.supportsOption(RDBMSAdapter.IDENTIFIERS_UPPERCASE_QUOTED))
+ {
+ identifierCase = IdentifierFactory.IDENTIFIER_UPPER_CASE_QUOTED;
+ NucleusLogger.PERSISTENCE.warn(LOCALISER.msg("039001", "MIXEDCASE", "UPPERCASEQUOTED"));
+ }
+ else
+ {
+ // Should never happen since we've tried all options
+ throw new NucleusUserException(LOCALISER.msg("039002", "MIXEDCASE")).setFatal();
+ }
+ }
+ }
+ else
+ {
+ // Case not supported
+ throw new NucleusUserException(LOCALISER.msg("039000", userIdentifierCase)).setFatal();
+ }
+
+ // Save the default catalog/schema - in a valid case (may be user input)
+ if (props.containsKey("DefaultCatalog"))
+ {
+ this.defaultCatalogName = getIdentifierInAdapterCase((String)props.get("DefaultCatalog"));
+ }
+ if (props.containsKey("DefaultSchema"))
+ {
+ this.defaultSchemaName = getIdentifierInAdapterCase((String)props.get("DefaultSchema"));
+ }
+ }
+
+ /**
+ * Accessor for the datastore adapter that we are creating identifiers for.
+ * @return The datastore adapter
+ */
+ public DatastoreAdapter getDatastoreAdapter()
+ {
+ return dba;
+ }
+
+ /**
+ * Accessor for the identifier case being used.
+ * @return The identifier case
+ */
+ public int getIdentifierCase()
+ {
+ return identifierCase;
+ }
+
+ /**
+ * Convenience method to return the name for the identifier case.
+ * @return Identifier case name
+ */
+ public String getNameOfIdentifierCase()
+ {
+ if (identifierCase == IDENTIFIER_LOWER_CASE)
+ {
+ return "lowercase";
+ }
+ else if (identifierCase == IDENTIFIER_LOWER_CASE_QUOTED)
+ {
+ return "\"lowercase\"";
+ }
+ else if (identifierCase == IDENTIFIER_MIXED_CASE)
+ {
+ return "\"MixedCase\"";
+ }
+ else if (identifierCase == IDENTIFIER_MIXED_CASE_QUOTED)
+ {
+ return "\"MixedCase\"";
+ }
+ else if (identifierCase == IDENTIFIER_UPPER_CASE)
+ {
+ return "UPPERCASE";
+ }
+ else if (identifierCase == IDENTIFIER_UPPER_CASE_QUOTED)
+ {
+ return "\"UPPERCASE\"";
+ }
+ else
+ {
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * Method to truncate an identifier to fit within the specified identifier length.
+ * If truncation is necessary will use a 4 char hashcode (defined by {@link #HASH_LENGTH}) (at the end) to attempt to create uniqueness.
+ * @param identifier The identifier
+ * @param length The (max) length to use
+ * @return The truncated identifier.
+ */
+ protected static String truncate(String identifier, int length)
+ {
+ if (length == 0) // Special case of no truncation
+ {
+ return identifier;
+ }
+ if (identifier.length() > length)
+ {
+ if (length < HASH_LENGTH)
+ throw new IllegalArgumentException("The length argument (=" + length + ") is less than HASH_LENGTH(=" + HASH_LENGTH + ")!");
+
+ // Truncation is necessary so cut down to "maxlength-HASH_LENGTH" and add HASH_LENGTH chars hashcode
+ int tailIndex = length - HASH_LENGTH;
+ int tailHash = identifier.hashCode();
+
+ // We have to scale down the hash anyway, so we can simply ignore the sign
+ if (tailHash < 0)
+ tailHash *= -1;
+
+ // Scale the hash code down to the range 0 ... (HASH_RANGE - 1)
+ tailHash %= HASH_RANGE;
+
+ String suffix = Integer.toString(tailHash, Character.MAX_RADIX);
+ if (suffix.length() > HASH_LENGTH)
+ throw new IllegalStateException("Calculated hash \"" + suffix + "\" has more characters than defined by HASH_LENGTH (=" + HASH_LENGTH + ")! This should never happen!");
+
+ // we add prefix "0", if it's necessary
+ if (suffix.length() < HASH_LENGTH)
+ {
+ StringBuilder sb = new StringBuilder(HASH_LENGTH);
+ sb.append(suffix);
+ while (sb.length() < HASH_LENGTH)
+ sb.insert(0, '0');
+
+ suffix = sb.toString();
+ }
+
+ return identifier.substring(0, tailIndex) + suffix;
+ }
+ else
+ {
+ return identifier;
+ }
+ }
+
+ /**
+ * 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(quoteString))
+ {
+ id.append(quoteString);
+ }
+ }
+
+ 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(quoteString))
+ {
+ id.append(quoteString);
+ }
+ }
+ 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 = StringUtils.replaceAll(name, quoteString, ""); // Remove any user/JDBC supplied quotes
+ if (identifierType == TABLE)
+ {
+ identifier = tables.get(key);
+ if (identifier == null)
+ {
+ String sqlIdentifier = generateIdentifierNameForJavaName(key);
+ sqlIdentifier = truncate(sqlIdentifier, dba.getDatastoreIdentifierMaxLength(identifierType));
+ identifier = new TableIdentifier(this, sqlIdentifier);
+ setCatalogSchemaForTable((TableIdentifier)identifier);
+ tables.put(key, identifier);
+ }
+ }
+ else if (identifierType == COLUMN)
+ {
+ identifier = columns.get(key);
+ if (identifier == null)
+ {
+ String sqlIdentifier = generateIdentifierNameForJavaName(key);
+ sqlIdentifier = truncate(sqlIdentifier, dba.getDatastoreIdentifierMaxLength(identifierType));
+ identifier = new ColumnIdentifier(this, sqlIdentifier);
+ columns.put(key, identifier);
+ }
+ }
+ else if (identifierType == FOREIGN_KEY)
+ {
+ identifier = foreignkeys.get(key);
+ if (identifier == null)
+ {
+ String sqlIdentifier = generateIdentifierNameForJavaName(key);
+ sqlIdentifier = truncate(sqlIdentifier, dba.getDatastoreIdentifierMaxLength(identifierType));
+ identifier = new ForeignKeyIdentifier(this, sqlIdentifier);
+ foreignkeys.put(key, identifier);
+ }
+ }
+ else if (identifierType == INDEX)
+ {
+ identifier = indexes.get(key);
+ if (identifier == null)
+ {
+ String sqlIdentifier = generateIdentifierNameForJavaName(key);
+ sqlIdentifier = truncate(sqlIdentifier, dba.getDatastoreIdentifierMaxLength(identifierType));
+ identifier = new IndexIdentifier(this, sqlIdentifier);
+ indexes.put(key, identifier);
+ }
+ }
+ else if (identifierType == CANDIDATE_KEY)
+ {
+ identifier = candidates.get(key);
+ if (identifier == null)
+ {
+ String sqlIdentifier = generateIdentifierNameForJavaName(key);
+ sqlIdentifier = truncate(sqlIdentifier, dba.getDatastoreIdentifierMaxLength(identifierType));
+ identifier = new CandidateKeyIdentifier(this, sqlIdentifier);
+ candidates.put(key, identifier);
+ }
+ }
+ else if (identifierType == PRIMARY_KEY)
+ {
+ identifier = primarykeys.get(key);
+ if (identifier == null)
+ {
+ String sqlIdentifier = generateIdentifierNameForJavaName(key);
+ sqlIdentifier = truncate(sqlIdentifier, dba.getDatastoreIdentifierMaxLength(identifierType));
+ identifier = new PrimaryKeyIdentifier(this, sqlIdentifier);
+ primarykeys.put(key, identifier);
+ }
+ }
+ else if (identifierType == SEQUENCE)
+ {
+ identifier = sequences.get(key);
+ if (identifier == null)
+ {
+ String sqlIdentifier = generateIdentifierNameForJavaName(key);
+ sqlIdentifier = truncate(sqlIdentifier, dba.getDatastoreIdentifierMaxLength(identifierType));
+ identifier = new SequenceIdentifier(this, sqlIdentifier);
+ sequences.put(key, identifier);
+ }
+ }
+ else
+ {
+ throw new NucleusException("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, dba.getDatastoreIdentifierMaxLength(IdentifierFactory.TABLE));
+ TableIdentifier tableIdentifier = new TableIdentifier(this, newId);
+ setCatalogSchemaForTable(tableIdentifier);
+ return tableIdentifier;
+ }
+ else if (identifier instanceof ColumnIdentifier)
+ {
+ newId = truncate(newId, dba.getDatastoreIdentifierMaxLength(IdentifierFactory.COLUMN));
+ return new ColumnIdentifier(this, newId);
+ }
+ else if (identifier instanceof ForeignKeyIdentifier)
+ {
+ newId = truncate(newId, dba.getDatastoreIdentifierMaxLength(IdentifierFactory.FOREIGN_KEY));
+ return new ForeignKeyIdentifier(this, newId);
+ }
+ else if (identifier instanceof IndexIdentifier)
+ {
+ newId = truncate(newId, dba.getDatastoreIdentifierMaxLength(IdentifierFactory.INDEX));
+ return new IndexIdentifier(this, newId);
+ }
+ else if (identifier instanceof CandidateKeyIdentifier)
+ {
+ newId = truncate(newId, dba.getDatastoreIdentifierMaxLength(IdentifierFactory.CANDIDATE_KEY));
+ return new CandidateKeyIdentifier(this, newId);
+ }
+ else if (identifier instanceof PrimaryKeyIdentifier)
+ {
+ newId = truncate(newId, dba.getDatastoreIdentifierMaxLength(IdentifierFactory.PRIMARY_KEY));
+ return new PrimaryKeyIdentifier(this, newId);
+ }
+ else if (identifier instanceof SequenceIdentifier)
+ {
+ newId = truncate(newId, dba.getDatastoreIdentifierMaxLength(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 = StringUtils.replaceAll(identifierName, quoteString, ""); // Allow for quotes on input name
+ DatastoreIdentifier identifier = tables.get(key);
+ if (identifier == null)
+ {
+ String baseID = truncate(key, dba.getDatastoreIdentifierMaxLength(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 = StringUtils.replaceAll(identifierName, quoteString, ""); // Allow for quotes on input names
+ DatastoreIdentifier identifier = columns.get(key);
+ if (identifier == null)
+ {
+ String baseID = truncate(key, dba.getDatastoreIdentifierMaxLength(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 = 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, dba.getDatastoreIdentifierMaxLength(IdentifierFactory.COLUMN));
+ identifier = new ColumnIdentifier(this, baseID);
+ }
+ else
+ {
+ String suffix = getColumnIdentifierSuffix(fieldRole, embedded);
+ String datastoreID = generateIdentifierNameForJavaName(javaName);
+ String baseID = truncate(datastoreID, dba.getDatastoreIdentifierMaxLength(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 = sequences.get(key);
+ if (identifier == null)
+ {
+ String baseID = truncate(sequenceName, dba.getDatastoreIdentifierMaxLength(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 = primarykeys.get(key);
+ if (identifier == null)
+ {
+ String suffix = getWordSeparator() + "PK";
+ int maxLength = dba.getDatastoreIdentifierMaxLength(IdentifierFactory.PRIMARY_KEY);
+ String baseID = truncate(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 = candidates.get(key);
+ if (identifier == null)
+ {
+ String suffix = getWordSeparator() + "U" + seq;
+ int maxLength = dba.getDatastoreIdentifierMaxLength(IdentifierFactory.CANDIDATE_KEY);
+ String baseID = truncate(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 = foreignkeys.get(key);
+ if (identifier == null)
+ {
+ String suffix = getWordSeparator() + "FK";
+ if (seq < 10)
+ {
+ suffix += "" + (char)('0' + seq);
+ }
+ else if (seq < dba.getMaxForeignKeys())
+ {
+ suffix += Integer.toHexString('A' + seq);
+ }
+ else
+ {
+ throw new TooManyForeignKeysException(rdba, table.toString());
+ }
+ int maxLength = dba.getDatastoreIdentifierMaxLength(IdentifierFactory.FOREIGN_KEY);
+ String baseID = truncate(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 = indexes.get(key);
+ if (identifier == null)
+ {
+ String suffix = getWordSeparator() + (isUnique ? "U" : "N");
+ if (seq < dba.getMaxIndexes())
+ {
+ suffix += String.valueOf('0' + seq);
+ }
+ else
+ {
+ throw new TooManyIndicesException(rdba, table.toString());
+ }
+ int maxLength = dba.getDatastoreIdentifierMaxLength(IdentifierFactory.INDEX);
+ String baseID = truncate(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 (dba.supportsOption(DatastoreAdapter.CATALOGS_IN_TABLE_DEFINITIONS))
+ {
+ identifier.setCatalogName(this.defaultCatalogName);
+ }
+ if (dba.supportsOption(DatastoreAdapter.SCHEMAS_IN_TABLE_DEFINITIONS))
+ {
+ 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 (dba.supportsOption(DatastoreAdapter.SCHEMAS_IN_TABLE_DEFINITIONS) && currentPartIndex >= 0)
+ {
+ names[1] = specifiedNameParts[currentPartIndex--];
+ }
+ if (dba.supportsOption(DatastoreAdapter.CATALOGS_IN_TABLE_DEFINITIONS) && currentPartIndex >= 0)
+ {
+ names[0] = specifiedNameParts[currentPartIndex--];
+ }
+ }
+ return names;
+ }
+ return null;
+ }
+}
\ No newline at end of file
Added: platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/CandidateKeyIdentifier.java
===================================================================
--- platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/CandidateKeyIdentifier.java (rev 0)
+++ platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/CandidateKeyIdentifier.java 2008-12-02 15:26:10 UTC (rev 4162)
@@ -0,0 +1,36 @@
+/**********************************************************************
+Copyright (c) 2004 Erik Bengtson 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.datanucleus.store.mapped.identifier;
+
+import org.datanucleus.store.mapped.IdentifierFactory;
+
+/**
+ * Identifier for a unique index (candidate keys).
+ */
+class CandidateKeyIdentifier extends SQLIdentifier
+{
+ /**
+ * Constructor for a column identifier
+ * @param factory Identifier factory
+ * @param sqlIdentifier the sql identifier
+ */
+ public CandidateKeyIdentifier(IdentifierFactory factory, String sqlIdentifier)
+ {
+ super(factory, sqlIdentifier);
+ }
+}
\ No newline at end of file
Added: platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/ColumnIdentifier.java
===================================================================
--- platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/ColumnIdentifier.java (rev 0)
+++ platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/ColumnIdentifier.java 2008-12-02 15:26:10 UTC (rev 4162)
@@ -0,0 +1,36 @@
+/**********************************************************************
+Copyright (c) 2004 Erik Bengtson 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.datanucleus.store.mapped.identifier;
+
+import org.datanucleus.store.mapped.IdentifierFactory;
+
+/**
+ * Identifier for a Column.
+ */
+class ColumnIdentifier extends SQLIdentifier
+{
+ /**
+ * Constructor for a column identifier
+ * @param factory Identifier factory
+ * @param sqlIdentifier the sql identifier
+ */
+ public ColumnIdentifier(IdentifierFactory factory, String sqlIdentifier)
+ {
+ super(factory, sqlIdentifier);
+ }
+}
\ No newline at end of file
Added: platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/ForeignKeyIdentifier.java
===================================================================
--- platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/ForeignKeyIdentifier.java (rev 0)
+++ platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/ForeignKeyIdentifier.java 2008-12-02 15:26:10 UTC (rev 4162)
@@ -0,0 +1,36 @@
+/**********************************************************************
+Copyright (c) 2004 Erik Bengtson 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.datanucleus.store.mapped.identifier;
+
+import org.datanucleus.store.mapped.IdentifierFactory;
+
+/**
+ * Identifier for a FK.
+ */
+class ForeignKeyIdentifier extends SQLIdentifier
+{
+ /**
+ * Constructor for a foreign key identifier
+ * @param factory Identifier factory
+ * @param sqlIdentifier the sql identifier
+ */
+ public ForeignKeyIdentifier(IdentifierFactory factory, String sqlIdentifier)
+ {
+ super(factory, sqlIdentifier);
+ }
+}
\ No newline at end of file
Added: platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/IndexIdentifier.java
===================================================================
--- platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/IndexIdentifier.java (rev 0)
+++ platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/IndexIdentifier.java 2008-12-02 15:26:10 UTC (rev 4162)
@@ -0,0 +1,36 @@
+/**********************************************************************
+Copyright (c) 2004 Erik Bengtson 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.datanucleus.store.mapped.identifier;
+
+import org.datanucleus.store.mapped.IdentifierFactory;
+
+/**
+ * Identifier for an Index.
+ */
+class IndexIdentifier extends SQLIdentifier
+{
+ /**
+ * Constructor for an index identifier
+ * @param factory Identifier factory
+ * @param sqlIdentifier the sql identifier
+ */
+ public IndexIdentifier(IdentifierFactory factory, String sqlIdentifier)
+ {
+ super(factory, sqlIdentifier);
+ }
+}
\ No newline at end of file
Added: platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/JPAIdentifierFactory.java
===================================================================
--- platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/JPAIdentifierFactory.java (rev 0)
+++ platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/JPAIdentifierFactory.java 2008-12-02 15:26:10 UTC (rev 4162)
@@ -0,0 +1,623 @@
+/**********************************************************************
+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.datanucleus.store.mapped.identifier;
+
+import java.util.Map;
+
+import org.datanucleus.ClassLoaderResolver;
+import org.datanucleus.exceptions.NucleusException;
+import org.datanucleus.metadata.AbstractClassMetaData;
+import org.datanucleus.metadata.AbstractMemberMetaData;
+import org.datanucleus.metadata.FieldRole;
+import org.datanucleus.store.mapped.DatastoreAdapter;
+import org.datanucleus.store.mapped.DatastoreIdentifier;
+import org.datanucleus.store.mapped.IdentifierFactory;
+
+/**
+ * Factory that creates immutable instances of DatastoreIdentifier for RDBMS datastores with JPA.
+ * Includes the JPA naming strategy, naming as follows
+ * <ul>
+ * <li>Class called "MyClass" will generate table name of "MYCLASS"</li>
+ * <li>Field called "myField" will generate column name of "MYFIELD"</li>
+ * <li>Join table will be named after the ownerClass and the otherClass so "MyClass" joining to "MyOtherClass"
+ * will have a join table called "MYCLASS_MYOTHERCLASS"</li>
+ * <li>Datastore-identity column for class "MyClass" will be "MYCLASS_ID" (not part of JPA)</li>
+ * <li>1-N uni between "MyClass" (field="myField") and "MyElement" will have FK in "MYELEMENT" of MYFIELD_MYCLASS_ID</li>
+ * <li>1-N bi between "MyClass" (field="myField") and "MyElement" (field="myClassRef") will have FK in "MYELEMENT"
+ * of name "MYCLASSREF_MYCLASS_ID".</li>
+ * <li>1-1 uni between "MyClass" (field="myField") and "MyElement" will have FK in "MYCLASS" of name "MYFIELD_MYELEMENT_ID"</li>
+ * <li>Discriminator field columns will, by default, be called "DTYPE"</li>
+ * <li>Version field columns will, by default, be called "VERSION"</li>
+ * <li>Index (ordering) field columns will, for field "myField", be called "MYFIELD_ORDER"</li>
+ * <li>Adapter index field columns will, by default, be called "IDX"</li>
+ * </ul>
+ */
+public class JPAIdentifierFactory extends AbstractRDBMSIdentifierFactory
+{
+ /**
+ * Constructor.
+ * The properties accepted are
+ * <ul>
+ * <li>RequiredCase : what case the identifiers should be in</li>
+ * <li>DefaultCatalog : default catalog to use (if any)</li>
+ * <li>DefaultSchema : default schema to use (if any)</li>
+ * </ul>
+ * @param dba Datastore adapter
+ * @param clr ClassLoader resolver
+ * @param properties Any properties controlling identifier generation
+ */
+ public JPAIdentifierFactory(DatastoreAdapter dba, ClassLoaderResolver clr, Map props)
+ {
+ super(dba, clr, props);
+ }
+
+ /**
+ * Method to return a Table identifier for the join table of the specified field/property.
+ * @param mmd Meta data for the field/property
+ * @return The identifier for the table
+ */
+ public DatastoreIdentifier newDatastoreContainerIdentifier(AbstractMemberMetaData mmd)
+ {
+ String identifierName = null;
+ String schemaName = null;
+ String catalogName = null;
+
+ // Assign an identifier name based on the user-specified table/column name (if any)
+
+ // SCO table for this field
+ AbstractMemberMetaData[] relatedMmds = null;
+ if (mmd.getColumnMetaData().length > 0 && mmd.getColumnMetaData()[0].getName() != null)
+ {
+ // Name the table based on the column
+ identifierName = mmd.getColumnMetaData()[0].getName();
+ }
+ else if (mmd.hasContainer())
+ {
+ // Check for a specified join table name
+ if (mmd.getTable() != null)
+ {
+ // Join table name specified at this side
+ String specifiedName = mmd.getTable();
+ String[] parts = getIdentifierNamePartsFromName(specifiedName);
+ if (parts != null)
+ {
+ catalogName = parts[0];
+ schemaName = parts[1];
+ identifierName = parts[2];
+ }
+ if (catalogName == null)
+ {
+ catalogName = mmd.getCatalog();
+ }
+ if (schemaName == null)
+ {
+ schemaName = mmd.getSchema();
+ }
+ }
+ else
+ {
+ relatedMmds = mmd.getRelatedMemberMetaData(clr);
+ if (relatedMmds != null && relatedMmds[0].getTable() != null)
+ {
+ String specifiedName = relatedMmds[0].getTable();
+ String[] parts = getIdentifierNamePartsFromName(specifiedName);
+ if (parts != null)
+ {
+ catalogName = parts[0];
+ schemaName = parts[1];
+ identifierName = parts[2];
+ }
+ if (catalogName == null)
+ {
+ catalogName = relatedMmds[0].getCatalog();
+ }
+ if (schemaName == null)
+ {
+ schemaName = relatedMmds[0].getSchema();
+ }
+ }
+ }
+ }
+
+ // No schema/catalog specified in the MetaData table "name" so try alternative sources
+ // Note that we treat these as pairs (they both come from the same source)
+ if (schemaName == null && catalogName == null)
+ {
+ // Check the <class schema="..." catalog="..." >
+ if (mmd.getParent() instanceof AbstractClassMetaData)
+ {
+ AbstractClassMetaData ownerCmd = (AbstractClassMetaData)mmd.getParent();
+ if (dba.supportsOption(DatastoreAdapter.CATALOGS_IN_TABLE_DEFINITIONS))
+ {
+ catalogName = ownerCmd.getCatalog();
+ }
+ if (dba.supportsOption(DatastoreAdapter.SCHEMAS_IN_TABLE_DEFINITIONS))
+ {
+ schemaName = ownerCmd.getSchema();
+ }
+ }
+
+ if (schemaName == null && catalogName == null)
+ {
+ // Still no values, so try the PMF settings.
+ if (dba.supportsOption(DatastoreAdapter.CATALOGS_IN_TABLE_DEFINITIONS))
+ {
+ catalogName = this.defaultCatalogName;
+ }
+ if (dba.supportsOption(DatastoreAdapter.SCHEMAS_IN_TABLE_DEFINITIONS))
+ {
+ schemaName = this.defaultSchemaName;
+ }
+ }
+ }
+
+ // No user-specified name, so generate a default using the previously created fallback
+ if (identifierName == null)
+ {
+ // Generate a fallback name, based on the ownerClass/otherClass names
+ String ownerClass = mmd.getClassName(false);
+ String otherClass = mmd.getTypeName();
+ if (mmd.hasCollection())
+ {
+ otherClass = mmd.getCollection().getElementType();
+ }
+ else if (mmd.hasArray())
+ {
+ otherClass = mmd.getArray().getElementType();
+ }
+ else if (mmd.hasMap())
+ {
+ otherClass = mmd.getMap().getValueType();
+ }
+
+ if (mmd.hasCollection() && relatedMmds != null && relatedMmds[0].hasCollection() && mmd.getMappedBy() != null)
+ {
+ // M-N collection and the owner is the other side
+ ownerClass = relatedMmds[0].getClassName(false);
+ otherClass = relatedMmds[0].getCollection().getElementType();
+ }
+
+ otherClass = otherClass.substring(otherClass.lastIndexOf('.')+1);
+ String unique_name = ownerClass + getWordSeparator() + otherClass;
+
+ identifierName = unique_name;
+ }
+
+ // Generate the table identifier now that we have the identifier name
+ DatastoreIdentifier identifier = newDatastoreContainerIdentifier(identifierName);
+ if (schemaName != null)
+ {
+ identifier.setSchemaName(schemaName);
+ }
+ if (catalogName != null)
+ {
+ identifier.setCatalogName(catalogName);
+ }
+
+ return identifier;
+ }
+
+ /**
+ * Method to return a Table identifier for the specified class.
+ * @param cmd Meta data for the class
+ * @return The identifier for the table
+ **/
+ public DatastoreIdentifier newDatastoreContainerIdentifier(AbstractClassMetaData cmd)
+ {
+ String identifierName = null;
+ String schemaName = null;
+ String catalogName = null;
+
+ // Assign an identifier name based on the user-specified table/column name (if any)
+ String specifiedName = cmd.getTable();
+ String[] parts = getIdentifierNamePartsFromName(specifiedName);
+ if (parts != null)
+ {
+ catalogName = parts[0];
+ schemaName = parts[1];
+ identifierName = parts[2];
+ }
+
+ // No schema/catalog specified in the MetaData table "name" so try alternative sources
+ // Note that we treat these as pairs (they both come from the same source)
+ if (schemaName == null && catalogName == null)
+ {
+ // Check the <class schema="..." catalog="..." >
+ if (dba.supportsOption(DatastoreAdapter.CATALOGS_IN_TABLE_DEFINITIONS))
+ {
+ catalogName = cmd.getCatalog();
+ }
+ if (dba.supportsOption(DatastoreAdapter.SCHEMAS_IN_TABLE_DEFINITIONS))
+ {
+ schemaName = cmd.getSchema();
+ }
+
+ if (schemaName == null && catalogName == null)
+ {
+ // Still no values, so try the PMF settings.
+ if (dba.supportsOption(DatastoreAdapter.CATALOGS_IN_TABLE_DEFINITIONS))
+ {
+ catalogName = this.defaultCatalogName;
+ }
+ if (dba.supportsOption(DatastoreAdapter.SCHEMAS_IN_TABLE_DEFINITIONS))
+ {
+ schemaName = this.defaultSchemaName;
+ }
+ }
+ }
+
+ // No user-specified name, so generate a default using the previously created fallback
+ if (identifierName == null)
+ {
+ // Generate a fallback name, based on the last part of the class name ("MyClass" becomes "MYCLASS")
+ String unique_name = cmd.getFullClassName().substring(cmd.getFullClassName().lastIndexOf('.')+1);
+
+ identifierName = unique_name;
+ }
+
+ // Generate the table identifier now that we have the identifier name
+ DatastoreIdentifier identifier = newDatastoreContainerIdentifier(identifierName);
+ if (schemaName != null)
+ {
+ identifier.setSchemaName(schemaName);
+ }
+ if (catalogName != null)
+ {
+ identifier.setCatalogName(catalogName);
+ }
+
+ return identifier;
+ }
+
+ /**
+ * Method to generate an identifier name for reference field, based on the metadata for the
+ * field, and the ClassMetaData for the implementation.
+ * @param refMetaData the metadata for the reference field
+ * @param implMetaData the AbstractClassMetaData for this implementation
+ * @param implIdentifier PK identifier for the implementation
+ * @param embedded Whether the identifier is for a field embedded
+ * @param fieldRole The role to be performed by this column e.g FK, collection element ?
+ * @return The DatastoreIdentifier
+ */
+ public DatastoreIdentifier newReferenceFieldIdentifier(AbstractMemberMetaData refMetaData,
+ AbstractClassMetaData implMetaData, DatastoreIdentifier implIdentifier, boolean embedded, int fieldRole)
+ {
+ String key = "[" + refMetaData.getFullFieldName() + "][" + implMetaData.getFullClassName() + "][" + implIdentifier.getIdentifier() + "]";
+ DatastoreIdentifier identifier = references.get(key);
+ if (identifier == null)
+ {
+ // use a simple naming for now : <reference-name>_<impl_name>_<impl_type>
+ String referenceName = refMetaData.getName();
+ String implementationName = implMetaData.getFullClassName();
+ int dot = implementationName.lastIndexOf('.');
+ if (dot > -1)
+ {
+ implementationName = implementationName.substring(dot+1);
+ }
+ String name = referenceName + "." + implementationName + "." + implIdentifier.getIdentifier();
+
+ // Set the SQL identifier adding any truncation as necessary
+ String datastoreID = generateIdentifierNameForJavaName(name);
+ String baseID = truncate(datastoreID, dba.getDatastoreIdentifierMaxLength(IdentifierFactory.COLUMN));
+ identifier = new ColumnIdentifier(this, baseID);
+ references.put(key, identifier);
+ }
+ return identifier;
+ }
+
+ /**
+ * Method to generate a join-table identifier. The identifier could be for a foreign-key to another
+ * table (if the destinationId is provided), or could be for a simple column in the join table.
+ * @param ownerFmd MetaData for the owner field
+ * @param destinationId Identifier for the identity field of the destination (if FK)
+ * @param embedded Whether the identifier is for a field embedded
+ * @param fieldRole The role to be performed by this column e.g FK, collection element ?
+ * @return The identifier.
+ */
+ public DatastoreIdentifier newJoinTableFieldIdentifier(AbstractMemberMetaData ownerFmd,
+ AbstractMemberMetaData relatedFmd, DatastoreIdentifier destinationId, boolean embedded, int fieldRole)
+ {
+ DatastoreIdentifier identifier = null;
+
+ if (relatedFmd != null)
+ {
+ // Bidirectional
+ if (fieldRole == FieldRole.ROLE_OWNER)
+ {
+ identifier = newDatastoreFieldIdentifier(relatedFmd.getName() + getWordSeparator() + destinationId.getIdentifier());
+ }
+ else if (fieldRole == FieldRole.ROLE_COLLECTION_ELEMENT ||
+ fieldRole == FieldRole.ROLE_ARRAY_ELEMENT ||
+ fieldRole == FieldRole.ROLE_MAP_KEY ||
+ fieldRole == FieldRole.ROLE_MAP_VALUE)
+ {
+ if (destinationId != null)
+ {
+ // FK to other table
+ identifier = newDatastoreFieldIdentifier(ownerFmd.getName() + getWordSeparator() + destinationId.getIdentifier());
+ }
+ else
+ {
+ // Column in join table
+ if (fieldRole == FieldRole.ROLE_ARRAY_ELEMENT || fieldRole == FieldRole.ROLE_COLLECTION_ELEMENT)
+ {
+ identifier = newDatastoreFieldIdentifier(ownerFmd.getName() + getWordSeparator() + "ELEMENT");
+ }
+ else if (fieldRole == FieldRole.ROLE_MAP_KEY)
+ {
+ identifier = newDatastoreFieldIdentifier(ownerFmd.getName() + getWordSeparator() + "KEY");
+ }
+ else if (fieldRole == FieldRole.ROLE_MAP_VALUE)
+ {
+ identifier = newDatastoreFieldIdentifier(ownerFmd.getName() + getWordSeparator() + "VALUE");
+ }
+ }
+ }
+ else
+ {
+ // Not a known role for a join table so use JPOX-style naming
+ identifier = newDatastoreFieldIdentifier(destinationId.getIdentifier(), embedded, fieldRole);
+ }
+ }
+ else
+ {
+ // Unidirectional
+ if (fieldRole == FieldRole.ROLE_OWNER)
+ {
+ identifier = newDatastoreFieldIdentifier(ownerFmd.getClassName(false) + getWordSeparator() + destinationId.getIdentifier());
+ }
+ else if (fieldRole == FieldRole.ROLE_COLLECTION_ELEMENT ||
+ fieldRole == FieldRole.ROLE_ARRAY_ELEMENT ||
+ fieldRole == FieldRole.ROLE_MAP_KEY ||
+ fieldRole == FieldRole.ROLE_MAP_VALUE)
+ {
+ if (destinationId != null)
+ {
+ // FK to other table
+ identifier = newDatastoreFieldIdentifier(ownerFmd.getName() + getWordSeparator() + destinationId.getIdentifier());
+ }
+ else
+ {
+ // Column in join table
+ if (fieldRole == FieldRole.ROLE_ARRAY_ELEMENT || fieldRole == FieldRole.ROLE_COLLECTION_ELEMENT)
+ {
+ identifier = newDatastoreFieldIdentifier(ownerFmd.getName() + getWordSeparator() + "ELEMENT");
+ }
+ else if (fieldRole == FieldRole.ROLE_MAP_KEY)
+ {
+ identifier = newDatastoreFieldIdentifier(ownerFmd.getName() + getWordSeparator() + "KEY");
+ }
+ else if (fieldRole == FieldRole.ROLE_MAP_VALUE)
+ {
+ identifier = newDatastoreFieldIdentifier(ownerFmd.getName() + getWordSeparator() + "VALUE");
+ }
+ }
+ }
+ else
+ {
+ // Not a known role for a join table so use JPOX-style naming
+ identifier = newDatastoreFieldIdentifier(destinationId.getIdentifier(), embedded, fieldRole);
+ }
+ }
+
+ return identifier;
+ }
+
+ /**
+ * Method to generate a FK/FK-index field identifier.
+ * The identifier could be for the FK field itself, or for a related index for the FK.
+ * @param ownerFmd MetaData for the owner field
+ * @param relatedFmd MetaData for the related field
+ * @param destinationId Identifier for the identity field of the destination table (if strict FK)
+ * @param embedded Whether the identifier is for a field embedded
+ * @param fieldRole The role to be performed by this column e.g ROLE_OWNER, ROLE_INDEX
+ * @return The identifier
+ */
+ public DatastoreIdentifier newForeignKeyFieldIdentifier(AbstractMemberMetaData ownerFmd, AbstractMemberMetaData relatedFmd,
+ DatastoreIdentifier destinationId, boolean embedded, int fieldRole)
+ {
+ if (relatedFmd != null)
+ {
+ // Bidirectional
+ if (fieldRole == FieldRole.ROLE_OWNER)
+ {
+ return newDatastoreFieldIdentifier(relatedFmd.getName() + "." + destinationId.getIdentifier(), embedded, fieldRole);
+ }
+ else if (fieldRole == FieldRole.ROLE_INDEX)
+ {
+ return newDatastoreFieldIdentifier(relatedFmd.getName() + "." + destinationId.getIdentifier(), embedded, fieldRole);
+ }
+ else
+ {
+ throw new NucleusException("DatastoreField role " + fieldRole + " not supported by this method").setFatal();
+ }
+ }
+ else
+ {
+ if (fieldRole == FieldRole.ROLE_OWNER)
+ {
+ // FK field (FK collection/array/list/map)
+ return newDatastoreFieldIdentifier(ownerFmd.getName() + "." + destinationId.getIdentifier(), embedded, fieldRole);
+ }
+ else if (fieldRole == FieldRole.ROLE_INDEX)
+ {
+ // Order field for FK (FK list)
+ return newDatastoreFieldIdentifier(ownerFmd.getName() + ".IDX", embedded, fieldRole);
+ }
+ else
+ {
+ throw new NucleusException("DatastoreField role " + fieldRole + " not supported by this method").setFatal();
+ }
+ }
+ }
+
+ /**
+ * Method to return an identifier for a discriminator column.
+ * Returns an identifier "DTYPE"
+ * @return The discriminator column identifier
+ */
+ public DatastoreIdentifier newDiscriminatorFieldIdentifier()
+ {
+ String name = "DTYPE"; // JPA1 spec [9.1.30] discriminator column defaults to "DTYPE"
+ DatastoreIdentifier identifier = columns.get(name);
+ if (identifier == null)
+ {
+ identifier = new ColumnIdentifier(this, name);
+ columns.put(name, identifier);
+ }
+ return identifier;
+ }
+
+ /**
+ * Method to return an identifier for a version datastore field.
+ * @return The version datastore field identifier
+ */
+ public DatastoreIdentifier newVersionFieldIdentifier()
+ {
+ String name = "VERSION";
+ DatastoreIdentifier identifier = columns.get(name);
+ if (identifier == null)
+ {
+ identifier = new ColumnIdentifier(this, name);
+ columns.put(name, identifier);
+ }
+ return identifier;
+ }
+
+ /**
+ * Method to return an identifier for an index (ordering) datastore field.
+ * @param mmd MetaData for the field/property
+ * @return The index datastore field identifier
+ */
+ public DatastoreIdentifier newIndexFieldIdentifier(AbstractMemberMetaData mmd)
+ {
+ String name = mmd.getName() + getWordSeparator() + "ORDER";
+ DatastoreIdentifier identifier = columns.get(name);
+ if (identifier == null)
+ {
+ identifier = new ColumnIdentifier(this, name);
+ columns.put(name, identifier);
+ }
+ return identifier;
+ }
+
+ /**
+ * Method to return an identifier for an adapter index datastore field.
+ * An "adapter index" is a column added to be part of a primary key when some other
+ * column cant perform that role.
+ * @return The index datastore field identifier
+ */
+ public DatastoreIdentifier newAdapterIndexFieldIdentifier()
+ {
+ String name = "IDX"; // All index fields are called IDX in this factory
+ DatastoreIdentifier identifier = columns.get(name);
+ if (identifier == null)
+ {
+ identifier = new ColumnIdentifier(this, name);
+ columns.put(name, identifier);
+ }
+ return identifier;
+ }
+
+ /**
+ * Generate a datastore identifier from a Java identifier.
+ *
+ * <p>Conversion consists of breaking the identifier into words, converting
+ * each word to upper-case, and separating each one with an underscore "_".
+ * Words are identified by a leading upper-case character.
+ * Any leading or trailing underscores are removed.</p>
+ *
+ * @param javaName the Java identifier.
+ * @return The datastore identifier
+ */
+ public String generateIdentifierNameForJavaName(String javaName)
+ {
+ if (javaName == null)
+ {
+ return null;
+ }
+
+ StringBuffer s = new StringBuffer();
+
+ for (int i = 0; i < javaName.length(); ++i)
+ {
+ char c = javaName.charAt(i);
+
+ if (c >= 'A' && c <= 'Z' && (identifierCase != IDENTIFIER_MIXED_CASE && identifierCase != IDENTIFIER_MIXED_CASE_QUOTED))
+ {
+ s.append(c);
+ }
+ else if (c >= 'A' && c <= 'Z' && (identifierCase == IDENTIFIER_MIXED_CASE || identifierCase == IDENTIFIER_MIXED_CASE_QUOTED))
+ {
+ s.append(c);
+ }
+ else if (c >= 'a' && c <= 'z' && (identifierCase == IDENTIFIER_MIXED_CASE || identifierCase == IDENTIFIER_MIXED_CASE_QUOTED))
+ {
+ s.append(c);
+ }
+ else if (c >= 'a' && c <= 'z' && (identifierCase != IDENTIFIER_MIXED_CASE && identifierCase != IDENTIFIER_MIXED_CASE_QUOTED))
+ {
+ s.append((char)(c - ('a' - 'A')));
+ }
+ else if (c >= '0' && c <= '9' || c=='_')
+ {
+ s.append(c);
+ }
+ else if (c == '.')
+ {
+ s.append(getWordSeparator());
+ }
+ else
+ {
+ String cval = "000" + Integer.toHexString(c);
+
+ s.append(cval.substring(cval.length() - (c > 0xff ? 4 : 2)));
+ }
+ }
+
+ // Remove leading and trailing underscores
+ while (s.length() > 0 && s.charAt(0) == '_')
+ {
+ s.deleteCharAt(0);
+ }
+ if (s.length() == 0)
+ {
+ throw new IllegalArgumentException("Illegal Java identifier: " + javaName);
+ }
+
+ return s.toString();
+ }
+
+ /**
+ * 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 String getColumnIdentifierSuffix(int role, boolean embedded)
+ {
+ String suffix = "";
+ if (role == FieldRole.ROLE_NONE)
+ {
+ // JPA doesnt allow datastore identity so we can do as we like here. Lets add an "_ID" to match JPOX default
+ suffix = !embedded ? "_ID" : "";
+ }
+
+ return suffix;
+ }
+}
\ No newline at end of file
Added: platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/JPOX2IdentifierFactory.java
===================================================================
--- platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/JPOX2IdentifierFactory.java (rev 0)
+++ platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/JPOX2IdentifierFactory.java 2008-12-02 15:26:10 UTC (rev 4162)
@@ -0,0 +1,255 @@
+/**********************************************************************
+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.datanucleus.store.mapped.identifier;
+
+import java.util.Map;
+
+import org.datanucleus.ClassLoaderResolver;
+import org.datanucleus.exceptions.NucleusException;
+import org.datanucleus.metadata.AbstractMemberMetaData;
+import org.datanucleus.metadata.FieldRole;
+import org.datanucleus.store.mapped.DatastoreAdapter;
+import org.datanucleus.store.mapped.DatastoreIdentifier;
+
+/**
+ * Factory that creates immutable instances of DatastoreIdentifier for RDBMS datastores.
+ * Provides a more concise and consistent alternative to "jpox". "jpox" is left alone
+ * so we preserve backwards compatibility with JPOX 1.1.0.
+ * Naming as follows:-
+ * <ul>
+ * <li>Class called "MyClass" will generate table name of "MYCLASS"</li>
+ * <li>Field called "myField" will generate column name of "MYFIELD"</li>
+ * <li>Datastore id field for class "MyClass" will have the PK field "MYCLASS_ID"</li>
+ * <li>Join table will be named after the class and field, so "MyClass" with field "myField" will become
+ * a table with name "MYCLASS_MYFIELD".</li>
+ * <li>Columns of a join table will be named after the PK fields of the owner and element. So something
+ * like "MYCLASS_ID_OID" and "MYELEMENT_ID_EID"</li>
+ * <li>Discriminator field columns will, by default, be called "DISCRIMINATOR"</li>
+ * <li>Index field columns will, by default, be called "IDX"</li>
+ * <li>Version field columns will, by default, be called "VERSION"</li>
+ * <li>Adapter index field columns will, by default, be called "IDX"</li>
+ * </ul>
+ */
+public class JPOX2IdentifierFactory extends JPOXIdentifierFactory
+{
+ /**
+ * Constructor.
+ * The properties accepted are
+ * <ul>
+ * <li>RequiredCase : what case the identifiers should be in</li>
+ * <li>DefaultCatalog : default catalog to use (if any)</li>
+ * <li>DefaultSchema : default schema to use (if any)</li>
+ * <li>WordSeparator : separator character(s) between identifier words</li>
+ * <li>TablePrefix : Prefix to prepend to all table identifiers</li>
+ * <li>TableSuffix : Suffix to append to all table identifiers</li>
+ * </ul>
+ * @param dba Database adapter
+ * @param clr ClassLoader resolver
+ * @param properties Any properties controlling identifier generation
+ */
+ public JPOX2IdentifierFactory(DatastoreAdapter dba, ClassLoaderResolver clr, Map props)
+ {
+ super(dba, clr, props);
+ }
+
+ /**
+ * Method to generate a join-table identifier. The identifier could be for a foreign-key
+ * to another table (if the destinationId is provided), or could be for a simple column
+ * in the join table.
+ * @param ownerFmd MetaData for the owner field
+ * @param relatedFmd MetaData for the related field
+ * @param destinationId Identifier for the identity field of the destination (if FK)
+ * @param embedded Whether the identifier is for a field embedded
+ * @param fieldRole The role to be performed by this column e.g FK, collection element ?
+ * @return The identifier.
+ */
+ public DatastoreIdentifier newJoinTableFieldIdentifier(AbstractMemberMetaData ownerFmd, AbstractMemberMetaData relatedFmd,
+ DatastoreIdentifier destinationId, boolean embedded, int fieldRole)
+ {
+ if (destinationId != null)
+ {
+ return newDatastoreFieldIdentifier(destinationId.getIdentifier(), embedded, fieldRole);
+ }
+ else
+ {
+ String baseName = null;
+ if (fieldRole == FieldRole.ROLE_COLLECTION_ELEMENT)
+ {
+ baseName = "ELEMENT";
+ }
+ else if (fieldRole == FieldRole.ROLE_ARRAY_ELEMENT)
+ {
+ baseName = "ELEMENT";
+ }
+ else if (fieldRole == FieldRole.ROLE_MAP_KEY)
+ {
+ baseName = "KEY";
+ }
+ else if (fieldRole == FieldRole.ROLE_MAP_VALUE)
+ {
+ baseName = "VALUE";
+ }
+ else
+ {
+ baseName = "UNKNOWN";
+ }
+ return newDatastoreFieldIdentifier(baseName);
+ }
+ }
+
+ /**
+ * Method to generate a FK/FK-index field identifier.
+ * The identifier could be for the FK field itself, or for a related index for the FK.
+ * @param ownerFmd MetaData for the owner field
+ * @param destinationId Identifier for the identity field of the destination table (if strict FK)
+ * @param embedded Whether the identifier is for a field embedded
+ * @param fieldRole The role to be performed by this column e.g owner, index ?
+ * @return The identifier
+ */
+ public DatastoreIdentifier newForeignKeyFieldIdentifier(AbstractMemberMetaData ownerFmd, DatastoreIdentifier destinationId,
+ boolean embedded, int fieldRole)
+ {
+ if (fieldRole == FieldRole.ROLE_OWNER)
+ {
+ // FK field (FK collection/array/list/map)
+ return newDatastoreFieldIdentifier(ownerFmd.getName() + "." + destinationId.getIdentifier(), embedded, fieldRole);
+ }
+ else if (fieldRole == FieldRole.ROLE_INDEX)
+ {
+ // Order field for FK (FK list)
+ return newDatastoreFieldIdentifier(ownerFmd.getName(), embedded, fieldRole);
+ }
+ else
+ {
+ throw new NucleusException("DatastoreField role " + fieldRole + " not supported by this method").setFatal();
+ }
+ }
+
+ /**
+ * Method to return an identifier for a version datastore field.
+ * @return The version datastore field identifier
+ */
+ public DatastoreIdentifier newVersionFieldIdentifier()
+ {
+ String name = "VERSION";
+ DatastoreIdentifier identifier = columns.get(name);
+ if (identifier == null)
+ {
+ identifier = new ColumnIdentifier(this, name);
+ columns.put(name, identifier);
+ }
+ return identifier;
+ }
+
+ /**
+ * Method to return an identifier for an index (ordering) datastore field.
+ * @param mmd MetaData for the field/property - not used here
+ * @return The index datastore field identifier
+ */
+ public DatastoreIdentifier newIndexFieldIdentifier(AbstractMemberMetaData mmd)
+ {
+ String name = "IDX";
+ DatastoreIdentifier identifier = columns.get(name);
+ if (identifier == null)
+ {
+ identifier = new ColumnIdentifier(this, name);
+ columns.put(name, identifier);
+ }
+ return identifier;
+ }
+
+ /**
+ * Method to return an identifier for an adapter index datastore field.
+ * An "adapter index" is a column added to be part of a primary key when some other
+ * column cant perform that role.
+ * @return The index datastore field identifier
+ */
+ public DatastoreIdentifier newAdapterIndexFieldIdentifier()
+ {
+ return newIndexFieldIdentifier(null);
+ }
+
+ /**
+ * Generate a datastore identifier from a Java identifier.
+ *
+ * <p>Conversion consists of breaking the identifier into words, converting
+ * each word to upper-case, and separating each one with a word separator.
+ * Words are identified by a leading upper-case character.
+ * Any leading or trailing underscores are removed.</p>
+ *
+ * @param javaName the Java identifier.
+ * @return The datastore identifier
+ */
+ public String generateIdentifierNameForJavaName(String javaName)
+ {
+ if (javaName == null)
+ {
+ return null;
+ }
+
+ StringBuffer s = new StringBuffer();
+
+ for (int i = 0; i < javaName.length(); ++i)
+ {
+ char c = javaName.charAt(i);
+
+ if (c >= 'A' && c <= 'Z' && (identifierCase != IDENTIFIER_MIXED_CASE && identifierCase != IDENTIFIER_MIXED_CASE_QUOTED))
+ {
+ s.append(c);
+ }
+ else if (c >= 'A' && c <= 'Z' && (identifierCase == IDENTIFIER_MIXED_CASE || identifierCase == IDENTIFIER_MIXED_CASE_QUOTED))
+ {
+ s.append(c);
+ }
+ else if (c >= 'a' && c <= 'z' && (identifierCase == IDENTIFIER_MIXED_CASE || identifierCase == IDENTIFIER_MIXED_CASE_QUOTED))
+ {
+ s.append(c);
+ }
+ else if (c >= 'a' && c <= 'z' && (identifierCase != IDENTIFIER_MIXED_CASE && identifierCase != IDENTIFIER_MIXED_CASE_QUOTED))
+ {
+ s.append((char)(c - ('a' - 'A')));
+ }
+ else if (c >= '0' && c <= '9' || c=='_')
+ {
+ s.append(c);
+ }
+ else if (c == '.')
+ {
+ s.append(wordSeparator);
+ }
+ else
+ {
+ String cval = "000" + Integer.toHexString(c);
+
+ s.append(cval.substring(cval.length() - (c > 0xff ? 4 : 2)));
+ }
+ }
+
+ // Remove leading and trailing underscores
+ while (s.length() > 0 && s.charAt(0) == '_')
+ {
+ s.deleteCharAt(0);
+ }
+ if (s.length() == 0)
+ {
+ throw new IllegalArgumentException("Illegal Java identifier: " + javaName);
+ }
+
+ return s.toString();
+ }
+}
\ No newline at end of file
Added: platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/JPOXIdentifierFactory.java
===================================================================
--- platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/JPOXIdentifierFactory.java (rev 0)
+++ platform/store.rdbms/trunk/src/java/org/datanucleus/store/mapped/identifier/JPOXIdentifierFactory.java 2008-12-02 15:26:10 UTC (rev 4162)
@@ -0,0 +1,655 @@
+/**********************************************************************
+Copyright (c) 2005 Erik Bengtson 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:
+2006 Andy Jefferson - moved catalog/schema setting code into newTableIdentifierForMetaData
+2006 Andy Jefferson - changed to implement IdentifierFactory for RDBMS stores
+ ...
+**********************************************************************/
+package org.datanucleus.store.mapped.identifier;
+
+import java.util.ArrayList;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.datanucleus.ClassLoaderResolver;
+import org.datanucleus.exceptions.NucleusException;
+import org.datanucleus.metadata.AbstractClassMetaData;
+import org.datanucleus.metadata.AbstractMemberMetaData;
+import org.datanucleus.metadata.FieldRole;
+import org.datanucleus.store.mapped.DatastoreAdapter;
+import org.datanucleus.store.mapped.DatastoreIdentifier;
+import org.datanucleus.store.mapped.IdentifierFactory;
+
+/**
+ * Factory that creates immutable instances of DatastoreIdentifier for RDBMS datastores.
+ * Includes a JPOX naming strategy, naming as follows
+ * <ul>
+ * <li>Class called "MyClass" will generate table name of "MYCLASS"</li>
+ * <li>Field called "myField" will generate column name of "MY_FIELD"</li>
+ * <li>Datastore id field for class "MyClass" will have the PK field "MYCLASS_ID"</li>
+ * <li>Join table will be named after the class and field, so "MyClass" with field "myField" will become
+ * a table with name "MYCLASS_MYFIELD".</li>
+ * <li>Columns of a join table will be named after the PK fields of the owner and element. So something
+ * like "MYCLASS_ID_OID" and "MYELEMENT_ID_EID"</li>
+ * <li>Discriminator field columns will, by default, be called "DISCRIMINATOR"</li>
+ * <li>Index field columns will, by default, be called "INTEGER_IDX"</li>
+ * <li>Version field columns will, by default, be called "OPT_VERSION"</li>
+ * <li>Adapter index field columns will, by default, be called "ADPT_PK_IDX"</li>
+ * </ul>
+ * Strictly speaking the naming of the table is inconsistent with the naming of the column since
+ * the table doesn't use the word separator whereas the column does, but JPOX has been like this for
+ * some time so we can't just change the default like that - an option needs adding to allow backwards
+ * compatibility.
+ */
+public class JPOXIdentifierFactory extends AbstractRDBMSIdentifierFactory
+{
+ /** Prefix for all generated table names. */
+ protected String tablePrefix = null;
+
+ /** Suffix for all generated table names. */
+ protected String tableSuffix = null;
+
+ /**
+ * Constructor.
+ * The properties accepted are
+ * <ul>
+ * <li>RequiredCase : what case the identifiers should be in</li>
+ * <li>DefaultCatalog : default catalog to use (if any)</li>
+ * <li>DefaultSchema : default schema to use (if any)</li>
+ * <li>WordSeparator : separator character(s) between identifier words</li>
+ * <li>TablePrefix : Prefix to prepend to all table identifiers</li>
+ * <li>TableSuffix : Suffix to append to all table identifiers</li>
+ * </ul>
+ * @param dba Database adapter
+ * @param clr ClassLoader resolver
+ * @param properties Any properties controlling identifier generation
+ */
+ public JPOXIdentifierFactory(DatastoreAdapter dba, ClassLoaderResolver clr, Map props)
+ {
+ super(dba, clr, props);
+
+ if (props.containsKey("WordSeparator"))
+ {
+ this.wordSeparator = (String)props.get("WordSeparator");
+ }
+ this.tablePrefix = (String)props.get("TablePrefix");
+ this.tableSuffix = (String)props.get("TableSuffix");
+ }
+
+ /**
+ * Method to return a Table identifier for the join table of the specified field.
+ * @param fmd Meta data for the field
+ * @return The identifier for the table
+ **/
+ public DatastoreIdentifier newDatastoreContainerIdentifier(AbstractMemberMetaData fmd)
+ {
+ String identifierName = null;
+ String schemaName = null;
+ String catalogName = null;
+
+ AbstractMemberMetaData[] relatedMmds = null;
+ if (fmd.getColumnMetaData().length > 0 && fmd.getColumnMetaData()[0].getName() != null)
+ {
+ // Name the table based on the column
+ identifierName = fmd.getColumnMetaData()[0].getName();
+ }
+ else if (fmd.hasContainer())
+ {
+ // Check for a specified join table name
+ if (fmd.getTable() != null)
+ {
+ // Join table name specified at this side
+ String specifiedName = fmd.getTable();
+ String[] parts = getIdentifierNamePartsFromName(specifiedName);
+ if (parts != null)
+ {
+ catalogName = parts[0];
+ schemaName = parts[1];
+ identifierName = parts[2];
+ }
+ if (catalogName == null)
+ {
+ catalogName = fmd.getCatalog();
+ }
+ if (schemaName == null)
+ {
+ schemaName = fmd.getSchema();
+ }
+ }
+ else
+ {
+ relatedMmds = fmd.getRelatedMemberMetaData(clr);
+ if (relatedMmds != null && relatedMmds[0].getTable() != null)
+ {
+ // Join table name specified at other side (1-N or M-N)
+ String specifiedName = relatedMmds[0].getTable();
+ String[] parts = getIdentifierNamePartsFromName(specifiedName);
+ if (parts != null)
+ {
+ catalogName = parts[0];
+ schemaName = parts[1];
+ identifierName = parts[2];
+ }
+ if (catalogName == null)
+ {
+ catalogName = fmd.getCatalog();
+ }
+ if (schemaName == null)
+ {
+ schemaName = fmd.getSchema();
+ }
+ }
+ }
+ }
+
+ // No schema/catalog specified in the MetaData table "name" so try alternative sources
+ // Note that we treat these as pairs (they both come from the same source)
+ if (schemaName == null && catalogName == null)
+ {
+ // Check the <class schema="..." catalog="..." >
+ if (fmd.getParent() instanceof AbstractClassMetaData)
+ {
+ AbstractClassMetaData ownerCmd = (AbstractClassMetaData)fmd.getParent();
+ if (dba.supportsOption(DatastoreAdapter.CATALOGS_IN_TABLE_DEFINITIONS))
+ {
+ catalogName = ownerCmd.getCatalog();
+ }
+ if (dba.supportsOption(DatastoreAdapter.SCHEMAS_IN_TABLE_DEFINITIONS))
+ {
+ schemaName = ownerCmd.getSchema();
+ }
+ }
+
+ if (schemaName == null && catalogName == null)
+ {
+ // Still no values, so try the PMF settings.
+ if (dba.supportsOption(DatastoreAdapter.CATALOGS_IN_TABLE_DEFINITIONS))
+ {
+ catalogName = this.defaultCatalogName;
+ }
+ if (dba.supportsOption(DatastoreAdapter.SCHEMAS_IN_TABLE_DEFINITIONS))
+ {
+ schemaName = this.defaultSchemaName;
+ }
+ }
+ }
+
+ // No user-specified name, so generate a default using the previously created fallback
+ if (identifierName == null)
+ {
+ // Use this field as the basis for the fallback name (CLASS_FIELD) unless
+ // this is a bidirectional and the other side is the owner (has mapped-by) in which case OTHERCLASS_FIELD
+ String fieldNameBasis = fmd.getFullFieldName();
+ if (relatedMmds != null && relatedMmds[0].getMappedBy() != null)
+ {
+ // Other field has mapped-by so use that
+ fieldNameBasis = relatedMmds[0].getFullFieldName();
+ }
+
+ // Generate a fallback name, based on the class/field name (CLASS_FIELD)
+ ArrayList name_parts = new ArrayList();
+ StringTokenizer tokens = new StringTokenizer(fieldNameBasis, ".");
+ while (tokens.hasMoreTokens())
+ {
+ name_parts.add(tokens.nextToken());
+ }
+ ListIterator li = name_parts.listIterator(name_parts.size());
+ String unique_name = (String)li.previous();
+ String full_name = (li.hasPrevious() ? (li.previous() + getWordSeparator()) : "") + unique_name;
+
+ identifierName = "";
+ if (tablePrefix != null && tablePrefix.length() > 0)
+ {
+ identifierName = tablePrefix;
+ }
+ if (full_name != null)
+ {
+ identifierName += full_name;
+ }
+ else
+ {
+ identifierName += unique_name;
+ }
+ if (tableSuffix != null && tableSuffix.length() > 0)
+ {
+ identifierName += tableSuffix;
+ }
+ }
+
+ // Generate the table identifier now that we have the identifier name
+ DatastoreIdentifier identifier = newDatastoreContainerIdentifier(identifierName);
+ if (schemaName != null)
+ {
+ identifier.setSchemaName(schemaName);
+ }
+ if (catalogName != null)
+ {
+ identifier.setCatalogName(catalogName);
+ }
+
+ return identifier;
+ }
+
+ /**
+ * Method to return a Table identifier for the specified class.
+ * @param cmd Meta data for the class
+ * @return The identifier for the table
+ **/
+ public DatastoreIdentifier newDatastoreContainerIdentifier(AbstractClassMetaData cmd)
+ {
+ String identifierName = null;
+ String schemaName = null;
+ String catalogName = null;
+
+ // Assign an identifier name based on the user-specified table/column name (if any)
+ String specifiedName = cmd.getTable();
+ String[] parts = getIdentifierNamePartsFromName(specifiedName);
+ if (parts != null)
+ {
+ catalogName = parts[0];
+ schemaName = parts[1];
+ identifierName = parts[2];
+ }
+
+ // No schema/catalog specified in the MetaData table "name" so try alternative sources
+ // Note that we treat these as pairs (they both come from the same source)
+ if (schemaName == null && catalogName == null)
+ {
+ // Check the <class schema="..." catalog="..." >
+ if (dba.supportsOption(DatastoreAdapter.CATALOGS_IN_TABLE_DEFINITIONS))
+ {
+ catalogName = cmd.getCatalog();
+ }
+ if (dba.supportsOption(DatastoreAdapter.SCHEMAS_IN_TABLE_DEFINITIONS))
+ {
+ schemaName = cmd.getSchema();
+ }
+
+ if (schemaName == null && catalogName == null)
+ {
+ // Still no values, so try the PMF settings.
+ if (dba.supportsOption(DatastoreAdapter.CATALOGS_IN_TABLE_DEFINITIONS))
+ {
+ catalogName = this.defaultCatalogName;
+ }
+ if (dba.supportsOption(DatastoreAdapter.SCHEMAS_IN_TABLE_DEFINITIONS))
+ {
+ schemaName = this.defaultSchemaName;
+ }
+ }
+ }
+
+ // No user-specified name, so generate a default using the previously created fallback
+ if (identifierName == null)
+ {
+ // Generate a fallback name, based on the last part of the class name ("MyClass" becomes "MYCLASS")
+ String unique_name = cmd.getFullClassName().substring(cmd.getFullClassName().lastIndexOf('.')+1);
+
+ identifierName = "";
+ if (tablePrefix != null && tablePrefix.length() > 0)
+ {
+ identifierName = tablePrefix;
+ }
+ identifierName += unique_name;
+ if (tableSuffix != null && tableSuffix.length() > 0)
+ {
+ identifierName += tableSuffix;
+ }
+ }
+
+ // Generate the table identifier now that we have the identifier name
+ DatastoreIdentifier identifier = newDatastoreContainerIdentifier(identifierName);
+ if (schemaName != null)
+ {
+ identifier.setSchemaName(schemaName);
+ }
+ if (catalogName != null)
+ {
+ identifier.setCatalogName(catalogName);
+ }
+
+ return identifier;
+ }
+
+ /**
+ * Method to generate an identifier name for reference field, based on the metadata for the
+ * field, and the ClassMetaData for the implementation.
+ * @param refMetaData the metadata for the reference field
+ * @param implMetaData the AbstractClassMetaData for this implementation
+ * @param implIdentifier PK identifier for the implementation
+ * @param embedded Whether the identifier is for a field embedded
+ * @param fieldRole The role to be performed by this column e.g FK, collection element ?
+ * @return The DatastoreIdentifier
+ */
+ public DatastoreIdentifier newReferenceFieldIdentifier(AbstractMemberMetaData refMetaData,
+ AbstractClassMetaData implMetaData, DatastoreIdentifier implIdentifier, boolean embedded, int fieldRole)
+ {
+ DatastoreIdentifier identifier = null;
+ String key = "[" + refMetaData.getFullFieldName() + "][" + implMetaData.getFullClassName() + "][" + implIdentifier.getIdentifier() + "]";
+ identifier = references.get(key);
+ if (identifier == null)
+ {
+ // use a simple naming for now : <reference-name>_<impl_name>_<impl_type>
+ String referenceName = refMetaData.getName();
+ String implementationName = implMetaData.getFullClassName();
+ int dot = implementationName.lastIndexOf('.');
+ if (dot > -1)
+ {
+ implementationName = implementationName.substring(dot+1);
+ }
+ String name = referenceName + "." + implementationName + "." + implIdentifier.getIdentifier();
+
+ // Set the SQL identifier adding any truncation as necessary
+ String suffix = getColumnIdentifierSuffix(fieldRole, embedded);
+ String datastoreID = generateIdentifierNameForJavaName(name);
+ String baseID = truncate(datastoreID,
+ dba.getDatastoreIdentifierMaxLength(IdentifierFactory.COLUMN) - suffix.length());
+ identifier = new ColumnIdentifier(this, baseID + suffix);
+ references.put(key, identifier);
+ }
+ return identifier;
+ }
+
+ /**
+ * Method to generate a join-table identifier. The identifier could be for a foreign-key
+ * to another table (if the destinationId is provided), or could be for a simple column
+ * in the join table.
+ * @param ownerFmd MetaData for the owner field
+ * @param relatedFmd MetaData for the related field
+ * @param destinationId Identifier for the identity field of the destination (if FK)
+ * @param embedded Whether the identifier is for a field embedded
+ * @param fieldRole The role to be performed by this column e.g FK, collection element ?
+ * @return The identifier.
+ */
+ public DatastoreIdentifier newJoinTableFieldIdentifier(AbstractMemberMetaData ownerFmd, AbstractMemberMetaData relatedFmd,
+ DatastoreIdentifier destinationId, boolean embedded, int fieldRole)
+ {
+ if (destinationId != null)
+ {
+ return newDatastoreFieldIdentifier(destinationId.getIdentifier(), embedded, fieldRole);
+ }
+ else
+ {
+ String baseName = null;
+ if (fieldRole == FieldRole.ROLE_COLLECTION_ELEMENT)
+ {
+ String elementType = ownerFmd.getCollection().getElementType();
+ baseName = elementType.substring(elementType.lastIndexOf('.') + 1);
+ }
+ else if (fieldRole == FieldRole.ROLE_ARRAY_ELEMENT)
+ {
+ String elementType = ownerFmd.getArray().getElementType();
+ baseName = elementType.substring(elementType.lastIndexOf('.') + 1);
+ }
+ else if (fieldRole == FieldRole.ROLE_MAP_KEY)
+ {
+ String keyType = ownerFmd.getMap().getKeyType();
+ baseName = keyType.substring(keyType.lastIndexOf('.') + 1);
+ }
+ else if (fieldRole == FieldRole.ROLE_MAP_VALUE)
+ {
+ String valueType = ownerFmd.getMap().getValueType();
+ baseName = valueType.substring(valueType.lastIndexOf('.') + 1);
+ }
+ else
+ {
+ baseName = "UNKNOWN";
+ }
+ return newDatastoreFieldIdentifier(baseName, embedded, fieldRole);
+ }
+ }
+
+ /**
+ * Method to generate a FK/FK-index field identifier.
+ * The identifier could be for the FK field itself, or for a related index for the FK.
+ * @param ownerFmd MetaData for the owner field
+ * @param relatedFmd MetaData for the related field
+ * @param destinationId Identifier for the identity field of the destination table (if strict FK)
+ * @param embedded Whether the identifier is for a field embedded
+ * @param fieldRole The role to be performed by this column e.g owner, index ?
+ * @return The identifier
+ */
+ public DatastoreIdentifier newForeignKeyFieldIdentifier(AbstractMemberMetaData ownerFmd, AbstractMemberMetaData relatedFmd,
+ DatastoreIdentifier destinationId, boolean embedded, int fieldRole)
+ {
+ if (relatedFmd != null)
+ {
+ // Bidirectional
+ if (fieldRole == FieldRole.ROLE_OWNER)
+ {
+ return newDatastoreFieldIdentifier(relatedFmd.getName() + "." + destinationId.getIdentifier(), embedded, fieldRole);
+ }
+ else if (fieldRole == FieldRole.ROLE_INDEX)
+ {
+ return newDatastoreFieldIdentifier(relatedFmd.getName() + "." + destinationId.getIdentifier(), embedded, fieldRole);
+ }
+ else
+ {
+ throw new NucleusException("DatastoreField role " + fieldRole + " not supported by this method").setFatal();
+ }
+ }
+ else
+ {
+ if (fieldRole == FieldRole.ROLE_OWNER)
+ {
+ // FK field (FK collection/array/list/map)
+ return newDatastoreFieldIdentifier(ownerFmd.getName() + "." + destinationId.getIdentifier(), embedded, fieldRole);
+ }
+ else if (fieldRole == FieldRole.ROLE_INDEX)
+ {
+ // Order field for FK (FK list)
+ return newDatastoreFieldIdentifier(ownerFmd.getName() + "." + "INTEGER", embedded, fieldRole);
+ }
+ else
+ {
+ throw new NucleusException("DatastoreField role " + fieldRole + " not supported by this method").setFatal();
+ }
+ }
+ }
+
+ /**
+ * Method to return an identifier for a discriminator column.
+ * Returns an identifier "DISCRIMINATOR"
+ * @return The discriminator column identifier
+ */
+ public DatastoreIdentifier newDiscriminatorFieldIdentifier()
+ {
+ String name = "DISCRIMINATOR";
+ DatastoreIdentifier identifier = columns.get(name);
+ if (identifier == null)
+ {
+ identifier = new ColumnIdentifier(this, name);
+ columns.put(name, identifier);
+ }
+ return identifier;
+ }
+
+
+ /**
+ * Method to return an identifier for a version datastore field.
+ * @return The version datastore field identifier
+ */
+ public DatastoreIdentifier newVersionFieldIdentifier()
+ {
@@ Diff output truncated at 100000 characters. @@
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|