Package org.jpox.store.rdbms

Source Code of org.jpox.store.rdbms.RDBMSManager$ClassAdder

/**********************************************************************
Copyright (c) 2003 Mike Martin (TJDO) 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:
2003 Kelly Grizzle (TJDO)
2003 Erik Bengtson  - removed exist() operation
2003 Erik Bengtson  - refactored the persistent id generator System property
2003 Andy Jefferson - added localiser
2003 Andy Jefferson - updated exception handling with SchemaTable
2003 Andy Jefferson - restructured to remove SchemaTable, and add StoreManagerHelper
2003 Andy Jefferson - updated getSubClassesForClass to recurse
2004 Erik Bengtson  - removed unused method and variables
2004 Erik Bengtson  - fixed problem with getObjectById for App ID in getClassForOID
2004 Andy Jefferson - re-emergence of SchemaTable. Addition of addClass().
2004 Andy Jefferson - Addition of AutoStartMechanism interface
2004 Andy Jefferson - Update to use Logger
2004 Andy Jefferson - Addition of Catalog name to accompany Schema name
2004 Marco Schulze  - replaced catch(NotPersistenceCapableException ...)
                  by advance-check via TypeManager.isSupportedType(...)
2004 Andy Jefferson - split StoreData into superclass.
2004 Andy Jefferson - added support for other inheritance types
2004 Andy Jefferson - added capability to dynamically add columns
2005 Marco Schulze - prevent closing starter during recursion of ClassAdder.addClassTables(...)
    ...
**********************************************************************/
package org.jpox.store.rdbms;

import java.io.IOException;
import java.io.PrintStream;
import java.io.Writer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import org.jpox.ClassLoaderResolver;
import org.jpox.ConnectionFactory;
import org.jpox.ConnectionFactoryRegistry;
import org.jpox.ManagedConnection;
import org.jpox.OMFContext;
import org.jpox.ObjectManager;
import org.jpox.PersistenceConfiguration;
import org.jpox.StateManager;
import org.jpox.UserTransaction;
import org.jpox.api.ApiAdapter;
import org.jpox.exceptions.JPOXDataStoreException;
import org.jpox.exceptions.JPOXException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.exceptions.NoPersistenceInformationException;
import org.jpox.identity.OID;
import org.jpox.identity.SCOID;
import org.jpox.metadata.AbstractClassMetaData;
import org.jpox.metadata.AbstractMemberMetaData;
import org.jpox.metadata.ClassMetaData;
import org.jpox.metadata.ClassPersistenceModifier;
import org.jpox.metadata.ExtensionMetaData;
import org.jpox.metadata.IdentityStrategy;
import org.jpox.metadata.IdentityType;
import org.jpox.metadata.InheritanceMetaData;
import org.jpox.metadata.InheritanceStrategy;
import org.jpox.metadata.MapMetaData;
import org.jpox.metadata.SequenceMetaData;
import org.jpox.metadata.TableGeneratorMetaData;
import org.jpox.sco.SCOUtils;
import org.jpox.sco.exceptions.IncompatibleFieldTypeException;
import org.jpox.store.Extent;
import org.jpox.store.JPOXConnection;
import org.jpox.store.JPOXSequence;
import org.jpox.store.StoreData;
import org.jpox.store.exceptions.NoExtentException;
import org.jpox.store.fieldmanager.FieldManager;
import org.jpox.store.mapped.DatastoreAdapter;
import org.jpox.store.mapped.DatastoreClass;
import org.jpox.store.mapped.DatastoreContainerObject;
import org.jpox.store.mapped.DatastoreIdentifier;
import org.jpox.store.mapped.FetchStatement;
import org.jpox.store.mapped.IdentifierFactory;
import org.jpox.store.mapped.MappedStoreManager;
import org.jpox.store.mapped.StatementExpressionIndex;
import org.jpox.store.mapped.expression.QueryExpression;
import org.jpox.store.mapped.mapping.ArrayMapping;
import org.jpox.store.mapped.mapping.CollectionMapping;
import org.jpox.store.mapped.mapping.JavaTypeMapping;
import org.jpox.store.mapped.mapping.MapMapping;
import org.jpox.store.mapped.query.StatementText;
import org.jpox.store.poid.PoidConnectionProvider;
import org.jpox.store.poid.PoidGenerator;
import org.jpox.store.rdbms.adapter.RDBMSAdapter;
import org.jpox.store.rdbms.adapter.RDBMSAdapterFactory;
import org.jpox.store.rdbms.adapter.DatabaseAdapter.JDBCTypeInfo;
import org.jpox.store.rdbms.columninfo.ColumnInfo;
import org.jpox.store.rdbms.columninfo.TableInfo;
import org.jpox.store.rdbms.extent.ClassTableExtent;
import org.jpox.store.rdbms.extent.ClassViewExtent;
import org.jpox.store.rdbms.fieldmanager.ResultSetGetter;
import org.jpox.store.rdbms.poid.AbstractRDBMSPoidGenerator;
import org.jpox.store.rdbms.scostore.FKArrayStore;
import org.jpox.store.rdbms.scostore.FKListStore;
import org.jpox.store.rdbms.scostore.FKMapStore;
import org.jpox.store.rdbms.scostore.FKSetStore;
import org.jpox.store.rdbms.scostore.JoinArrayStore;
import org.jpox.store.rdbms.scostore.JoinListStore;
import org.jpox.store.rdbms.scostore.JoinMapStore;
import org.jpox.store.rdbms.scostore.JoinSetStore;
import org.jpox.store.rdbms.sqlidentifier.SQLIdentifier;
import org.jpox.store.rdbms.table.AbstractTable;
import org.jpox.store.rdbms.table.ArrayTable;
import org.jpox.store.rdbms.table.ClassTable;
import org.jpox.store.rdbms.table.ClassView;
import org.jpox.store.rdbms.table.CollectionTable;
import org.jpox.store.rdbms.table.ColumnCreator;
import org.jpox.store.rdbms.table.JoinTable;
import org.jpox.store.rdbms.table.MapTable;
import org.jpox.store.rdbms.table.ProbeTable;
import org.jpox.store.rdbms.table.Table;
import org.jpox.store.rdbms.table.TableImpl;
import org.jpox.store.rdbms.table.ViewImpl;
import org.jpox.store.scostore.ArrayStore;
import org.jpox.store.scostore.CollectionStore;
import org.jpox.store.scostore.MapStore;
import org.jpox.store.scostore.Store;
import org.jpox.transaction.TransactionUtils;
import org.jpox.util.ClassUtils;
import org.jpox.util.JPOXLogger;
import org.jpox.util.Localiser;
import org.jpox.util.MacroString;
import org.jpox.util.StringUtils;

/**
* StoreManager for RDBMS datastores.
* Provided by the "store-manager" extension key "rdbms" and accepts datastore URLs valid for JDBC.
* <p>
* The RDBMS manager's responsibilities extend those for StoreManager to add :
* <ul>
* <li>creates and controls access to the data sources required for this datastore instance</li>
* <li>implements insert(), fetch(), update(), delete() in the interface to the StateManager.</li>
* <li>Providing cached access to JDBC database metadata (in particular column information).</li>
* <li>Resolving SQL identifier macros to actual SQL identifiers.</li>
* </ul>
* TODO Change RDBMSManager to share schema information (DatabaseMetaData) with other RDBMSManagers
*
* @version $Revision: 1.348 $
*/
public class RDBMSManager extends MappedStoreManager
{
    /** Localiser for messages. */
    protected static final Localiser LOCALISER_RDBMS = Localiser.getInstance("org.jpox.store.rdbms.Localisation",
        RDBMSManager.class.getClassLoader());

    /** The amount of time before we expire any cached column info. Hardcoded to five minutes. */
    private static final int COLUMN_INFO_EXPIRATION_MS = 5 * 60 * 1000;

    /** Catalog name for the RDBMS database */
    private String catalogName = null;

    /** Schema name for the RDBMS database */
    private String schemaName = null;

    /** Map of ColumnInfo for tables, keyed by the fully-qualified table name. */
    private Map columnInfoByTableName = new HashMap();

    /** Map of ColumnInfo for column name, keyed by the fully-qualified column name. */
    private Map columnInfoByColumnName = new HashMap();

    /** Timestamp when the ColumnInfo was refreshed. */
    private long columnInfoReadTimestamp = -1;

    /** The connectionProvider for this RDBMS **/
    private ConnectionProvider connProvider;

    /** Controller for SQL executed on this store. */
    private SQLController sqlController = null;

    /** Whether to check if table/view exists */
    protected final boolean checkExistTablesOrViews;

    /**
     * The active class adder transaction, if any. Some RDBMSManager methods are
     * called recursively in the course of adding new classes. This field allows
     * such methods to coordinate with the active ClassAdder transaction.
     * Recursive methods include:
     * <ul>
     * <li>addClasses()</li>
     * <li>addSetTable()</li>
     * <li>addMapTable()</li>
     * <li>addListTable()</li>
     * <li>addArrayTable()</li>
     * </ul>
     * Access is synchronized on the RDBMSManager itself.
     * Invariant: classAdder == null if RDBMSManager is unlocked.
     */
    private ClassAdder classAdder = null;

    /**
     * Object to use for locking the ClassAdder process. This could be revised with CORE-3409
     * and the above ClassAdder field to find a better locking strategy.
     * TODO This locks all RDBMSManager ClassAdder but should really only lock one
     */
    private final Object CLASSADDER_MUTEX = new Object();

    /**
     * Constructs a new RDBMSManager. On successful return the new RDBMSManager
     * will have successfully connected to the database with the given
     * credentials and determined the schema name, but will not have inspected
     * the schema contents any further. The contents (tables, views, etc.) will
     * be subsequently created and/or validated on-demand as the application
     * accesses persistent classes.
     * <p>
     * To avoid creating unnecessary redundant RDBMSManagers, new RDBMSManagers
     * should always be obtained from the {@link org.jpox.store.StoreManagerFactory},
     * rather than constructed directly.
     *
     * @param clr the ClassLoaderResolver
     * @param omf The corresponding PersistenceManagerFactory. This factory's non-tx data source will be
     *     used to get database connections as needed to perform management functions.
     * @exception JPOXDataStoreException If the database could not be accessed or the name of the
     *                schema could not be determined.
     */
    public RDBMSManager(ClassLoaderResolver clr, OMFContext omfContext)
    {
        super("rdbms", clr, omfContext);

        PersistenceConfiguration conf = omfContext.getPersistenceConfiguration();
        checkExistTablesOrViews = conf.getBooleanProperty("org.jpox.rdbms.CheckExistTablesOrViews");

        try
        {
            connProvider = (ConnectionProvider) omfContext.getPluginManager().createExecutableExtension(
                    "org.jpox.store_connectionprovider", "name",
                    conf.getStringProperty("org.jpox.store.connectionProvider.Name"),
                    "class-name", null, null);
            if (connProvider == null)
            {
                // No provider with this name (missing plugin ?)
                throw new JPOXException(LOCALISER_RDBMS.msg("050000",
                    conf.getStringProperty("org.jpox.store.connectionProvider.Name"))).setFatal();
            }
            connProvider.setFailOnError(conf.getBooleanProperty("org.jpox.connectionProvider.FailOnError"));
        }
        catch (Exception e)
        {
            // Error creating provider
            throw new JPOXException(LOCALISER.msg("050001",
                conf.getStringProperty("org.jpox.store.connectionProvider.Name"), e.getMessage()), e).setFatal();
        }

        // Handler for persistence operations
        persistenceHandler = new RDBMSPersistenceHandler(this);

        // Retrieve the Database Adapter for this datastore
        try
        {
            ManagedConnection mc = getConnection();
            Connection conn = (Connection)mc.getConnection();
            if (conn == null)
            {
                //somehow we haven't got an exception from the JDBC driver
                //to troubleshoot the user should telnet to ip/port of database and check if he can open a connection
                //this may be due to security / firewall things.
                throw new JPOXDataStoreException(LOCALISER_RDBMS.msg("050007"));
            }

            try
            {
                dba = RDBMSAdapterFactory.getInstance().getDatastoreAdapter(clr, conn,
                    conf.getStringProperty("org.jpox.datastoreAdapterClassName"),
                    omfContext.getPluginManager());
                dba.loadDatastoreMapping(omfContext.getPluginManager(), clr);

                // Check if the user has specified DefaultCatalogName/DefaultSchemaName and whether the DBA allows it
                if (conf.hasProperty("org.jpox.mapping.Catalog") && !((RDBMSAdapter)dba).supportsCatalogsInTableDefinitions())
                {
                   JPOXLogger.DATASTORE.warn(LOCALISER_RDBMS.msg("050002",
                       conf.getStringProperty("org.jpox.mapping.Catalog")));
                }
                if (conf.hasProperty("org.jpox.mapping.Schema") && !((RDBMSAdapter)dba).supportsSchemasInTableDefinitions())
                {
                    JPOXLogger.DATASTORE.warn(LOCALISER_RDBMS.msg("050003",
                        conf.getStringProperty("org.jpox.mapping.Schema")));
                }

                // Create an identifier factory - needs the database adapter to exist first
                initialiseIdentifierFactory(omfContext);

                // Create the SQL controller
                sqlController = new SQLController(((RDBMSAdapter)dba).supportsStatementBatching(),
                    conf.getIntProperty("org.jpox.rdbms.statementBatchLimit"),
                    conf.getIntProperty("org.jpox.query.timeout"));

                if (autoStartMechanism == null)
                {
                    // No auto-start specified so use RDBMS default of SchemaTable
                    // TODO When CORE-3328 is fixed change this to "SchemaTable2"
                    autoStartMechanism = "SchemaTable";
                }

                // Initialise the Schema "Name", and "Data"
                initialiseSchema(autoStartMechanism, conf.getStringProperty("org.jpox.autoStartMechanismMode"),
                    conn, clr);
            }
            finally
            {
                mc.close();
            }
        }
        catch (JPOXException jpex)
        {
            JPOXLogger.DATASTORE_SCHEMA.error(LOCALISER_RDBMS.msg("050004"), jpex);
            throw jpex.setFatal();
        }
        catch (Exception e1)
        {
            // Unknown type of exception so wrap it in a JPOXException for later handling
            String msg = LOCALISER_RDBMS.msg("050004") + ' ' +
                LOCALISER_RDBMS.msg("050006") + ' ' + LOCALISER_RDBMS.msg("048000",e1);
            JPOXLogger.DATASTORE_SCHEMA.error(msg, e1);
            throw new JPOXUserException(msg, e1).setFatal();
        }
        finally
        {
            logConfiguration();
        }
    }

    /**
     * Convenience method to log the configuration of this store manager.
     */
    protected void logConfiguration()
    {
        if (JPOXLogger.DATASTORE.isDebugEnabled())
        {
            PersistenceConfiguration conf = getOMFContext().getPersistenceConfiguration();

            String classNames = conf.getStringProperty("org.jpox.autoStartClassNames");
            JPOXLogger.DATASTORE.debug("======================= Datastore =========================");
            JPOXLogger.DATASTORE.debug("StoreManager : \"" + storeManagerKey + "\" (" + getClass().getName() + ")");
            JPOXLogger.DATASTORE.debug("AutoStart : mechanism=" + autoStartMechanism +
                ", mode=" + conf.getStringProperty("org.jpox.autoStartMechanismMode") +
                ((classNames != null) ? (", classes=" + classNames) : ""));
            JPOXLogger.DATASTORE.debug("Connection Pooling : " + conf.getStringProperty("org.jpox.connectionPoolingType"));
            if (identifierFactory != null)
            {
                JPOXLogger.DATASTORE.debug("Datastore Identifiers :" +
                    " factory=\"" + conf.getStringProperty("org.jpox.identifierFactory") + "\"" +
                    " case=" + identifierFactory.getNameOfIdentifierCase() +
                    (conf.hasProperty("org.jpox.mapping.Catalog") ? (" catalog=" + conf.getStringProperty("org.jpox.mapping.Catalog")) : "") +
                    (conf.hasProperty("org.jpox.mapping.Schema") ? (" schema=" + conf.getStringProperty("org.jpox.mapping.Schema")) : ""));
            }
            JPOXLogger.DATASTORE.debug("Datastore : " + (readOnlyDatastore ? "read-only" : "read-write") +
                (fixedDatastore ? ", fixed" : "") +
                (conf.getBooleanProperty("org.jpox.rdbms.useUpdateLock") ? ", useUpdateLock" : "") +
                (conf.getBooleanProperty("org.jpox.rdbms.CheckExistTablesOrViews") ? ", checkTableViewExistence" : "") +
                ", rdbmsConstraintCreateMode=" + conf.getStringProperty("org.jpox.rdbms.constraintCreateMode") +
                ", initialiseColumnInfo=" + conf.getStringProperty("org.jpox.rdbms.initializeColumnInfo"));

            // Auto-Create
            StringBuffer autoCreateOptions = null;
            if (autoCreateTables || autoCreateColumns || autoCreateConstraints)
            {
                autoCreateOptions = new StringBuffer();
                boolean first = true;
                if (autoCreateTables)
                {
                    if (!first)
                    {
                        autoCreateOptions.append(",");
                    }
                    autoCreateOptions.append("Tables");
                    first = false;
                }
                if (autoCreateColumns)
                {
                    if (!first)
                    {
                        autoCreateOptions.append(",");
                    }
                    autoCreateOptions.append("Columns");
                    first = false;
                }
                if (autoCreateConstraints)
                {
                    if (!first)
                    {
                        autoCreateOptions.append(",");
                    }
                    autoCreateOptions.append("Constraints");
                    first = false;
                }
            }

            // Validate
            StringBuffer validateOptions = null;
            if (validateTables || validateColumns || validateConstraints)
            {
                validateOptions = new StringBuffer();
                boolean first = true;
                if (validateTables)
                {
                    validateOptions.append("Tables");
                    first = false;
                }
                if (validateColumns)
                {
                    if (!first)
                    {
                        validateOptions.append(",");
                    }
                    validateOptions.append("Columns");
                    first = false;
                }
                if (validateConstraints)
                {
                    if (!first)
                    {
                        validateOptions.append(",");
                    }
                    validateOptions.append("Constraints");
                    first = false;
                }
            }

            JPOXLogger.DATASTORE.debug("Schema Control : " +
                "AutoCreate(" + (autoCreateOptions != null ? autoCreateOptions.toString() : "None") + ")" +
                ", Validate(" + (validateOptions != null ? validateOptions.toString() : "None") + ")");

            int batchLimit = conf.getIntProperty("org.jpox.rdbms.statementBatchLimit");
            JPOXLogger.DATASTORE.debug("Statement Batching : max-batch-size=" +
                (batchLimit == -1 ? "UNLIMITED" : "" + batchLimit));
            String[] queryLanguages = getOMFContext().getPluginManager().getAttributeValuesForExtension("org.jpox.store_query_query",
                "datastore", storeManagerKey, "name");
            JPOXLogger.DATASTORE.debug("Query Languages : " + StringUtils.objectArrayToString(queryLanguages));
            JPOXLogger.DATASTORE.debug("Queries : Timeout=" + conf.getIntProperty("org.jpox.query.timeout"));
            JPOXLogger.DATASTORE.debug("Queries : Results " +
                "direction=" + conf.getStringProperty("org.jpox.rdbms.query.fetchDirection") +
                ", type=" + conf.getStringProperty("org.jpox.rdbms.query.resultSetType") +
                ", concurrency=" + conf.getStringProperty("org.jpox.rdbms.query.resultSetConcurrency"));

            // Log the datastore adapter configuration
            if (dba != null)
            {
                ((RDBMSAdapter)dba).logConfiguration();
            }

            JPOXLogger.DATASTORE.debug("===========================================================");
        }
    }

    /**
     * Method to create the IdentifierFactory to be used by this store.
     * Relies on the datastore adapter existing before creation
     * @param omfContext ObjectManagerFactory context
     */
    protected void initialiseIdentifierFactory(OMFContext omfContext)
    {
        PersistenceConfiguration conf = omfContext.getPersistenceConfiguration();
        String idFactoryName = conf.getStringProperty("org.jpox.identifierFactory");
        String idFactoryClassName = omfContext.getPluginManager().getAttributeValueForExtension("org.jpox.store_identifierfactory",
            "name", idFactoryName, "class-name");
        if (idFactoryClassName == null)
        {
            throw new JPOXUserException(LOCALISER.msg("039003", idFactoryName)).setFatal();
        }

        try
        {
            // Create the IdentifierFactory
            Class cls = Class.forName(idFactoryClassName);
            Class[] argTypes = new Class[]
                {DatastoreAdapter.class, String.class, String.class, String.class, String.class, String.class, String.class};
            Object[] args = new Object[]
                {
                    dba,
                    conf.getStringProperty("org.jpox.mapping.Catalog"),
                    conf.getStringProperty("org.jpox.mapping.Schema"),
                    conf.getStringProperty("org.jpox.identifier.case"),
                    conf.getStringProperty("org.jpox.identifier.wordSeparator"),
                    conf.getStringProperty("org.jpox.identifier.tablePrefix"),
                    conf.getStringProperty("org.jpox.identifier.tableSuffix")
                };
            identifierFactory = (IdentifierFactory)ClassUtils.newInstance(cls, argTypes, args);
        }
        catch (ClassNotFoundException cnfe)
        {
            throw new JPOXUserException(LOCALISER.msg("039004", idFactoryName, idFactoryClassName), cnfe).setFatal();
        }
        catch (Exception e)
        {
            JPOXLogger.PERSISTENCE.error(e);
            throw new JPOXException(LOCALISER.msg("039005", idFactoryClassName), e).setFatal();
        }
    }

    /**
     * Release of resources
     */
    public void close()
    {
        super.close();
        columnInfoByTableName.clear();
        columnInfoByColumnName.clear();
        classAdder = null;
    }

    /**
     * Method to return a datastore sequence for this datastore matching the passed sequence MetaData.
     * @param om The ObjectManager
     * @param seqmd SequenceMetaData
     * @return The Sequence
     */
    public JPOXSequence getJPOXSequence(ObjectManager om, SequenceMetaData seqmd)
    {
        return new JDOSequenceImpl(om, this, seqmd);
    }

    /**
     * Method to return a JPOX Connection for the ObjectManager.
     * @param om ObjectManager
     * @return The JPOXConnection
     */
    public JPOXConnection getJPOXConnection(final ObjectManager om)
    {
        final ManagedConnection mc;
        final boolean enlisted;
        if (!om.getTransaction().isActive())
        {
            // no active transaction so dont enlist
            enlisted = false;
        }
        else
        {
            enlisted = true;
        }
        ConnectionFactory cf = null;
        if (enlisted)
        {
            cf = getOMFContext().getConnectionFactoryRegistry().lookupConnectionFactory(txConnectionFactoryName);
        }
        else
        {
            cf = getOMFContext().getConnectionFactoryRegistry().lookupConnectionFactory(nontxConnectionFactoryName);
        }
        mc = cf.getConnection(enlisted?om:null, null); // Will throw exception if already locked

        // Lock the connection now that it is in use by the user
        mc.lock();

        return new JDOConnectionImpl(mc.getConnection(), new Runnable()
        {
            public void run()
            {
                // Unlock the connection now that the user has finished with it
                mc.unlock();
                if (!enlisted)
                {
                    // Close the (unenlisted) connection (committing its statements)
                    try
                    {
                        ((Connection)mc.getConnection()).close();
                    }
                    catch (SQLException sqle)
                    {
                        throw new JPOXDataStoreException(sqle.getMessage());
                    }
                }
            }
        });
    }

    /**
     * Accessor for the SQL controller.
     * @return The SQL controller
     */
    public SQLController getSQLController()
    {
        return sqlController;
    }

    /**
     * Accessor for a connection for the store, and will not be enlisted in any transaction.
     * This is used for communicating with the datastore about schema information etc (tableInfo, columnInfo)
     * where we arent tied to a transaction.
     * @return The Connection
     * @throws SQLException Thrown if an error occurs getting the connection
     */
    public ManagedConnection getConnection()
    throws SQLException
    {
        ConnectionFactoryRegistry registry = getOMFContext().getConnectionFactoryRegistry();
        ConnectionFactory connFactory = registry.lookupConnectionFactory(nontxConnectionFactoryName);
        return connFactory.getConnection(null, null);
    }

    /**
     * Utility to return a Connection not associated to the current transaction.
     * It will return a connection from a secondary (non-transactional) DataSource
     * (e.g. javax.jdo.option.connectionFactory2Name), if it is provided.
     * However, if a secondary DataSource is not configured, the connection is obtained from the default DataSource.
     * @param isolation_level The transaction isolation scheme to use e.g Connection.TRANSACTION_NONE
     * @return The Connection to the datastore
     * @throws SQLException
     */
    public ManagedConnection getConnection(int isolation_level)
    throws SQLException
    {
        ConnectionFactoryRegistry registry = omfContext.getConnectionFactoryRegistry();
        ConnectionFactory connFactory = registry.lookupConnectionFactory(nontxConnectionFactoryName);
        Map options = new HashMap();
        options.put("transaction.isolation", new Integer(isolation_level));
        return connFactory.getConnection(null, options);
      
    }

    /**
     * Initialises the schema name for the datastore, and (optionally) the
     * schema table (and associated initial schema data).
     * @param auto_start_mechanism Name of AutoStart mechanism
     * @param auto_start_mechanism_mode Mode of AutoStart mechanism
     * @param conn A connection to the database
     */
    private void initialiseSchema(String auto_start_mechanism,
                                  String auto_start_mechanism_mode,
                                  Connection conn,
                                  ClassLoaderResolver clr)
    throws Exception
    {
        // Initialise the Catalog/Schema names
        PersistenceConfiguration conf = omfContext.getPersistenceConfiguration();
        RDBMSAdapter rdba = (RDBMSAdapter)dba;
        if ((conf.hasProperty("org.jpox.mapping.Catalog") && rdba.supportsCatalogsInTableDefinitions()) ||
            (conf.hasProperty("org.jpox.mapping.Schema") && rdba.supportsSchemasInTableDefinitions()))
        {
            // User-specified catalog/schema
            catalogName = conf.getStringProperty("org.jpox.mapping.Catalog");
            schemaName = conf.getStringProperty("org.jpox.mapping.Schema");
        }
        else
        {
            // TODO Should we bother with this if the RDBMS doesnt support catalog/schema in the table identifiers ?
            // Try to find the catalog/schema from the connection
            try
            {
                try
                {
                    catalogName = rdba.getCatalogName(conn);
                    schemaName = rdba.getSchemaName(conn);
                }
                catch (UnsupportedOperationException e)
                {
                    if (!readOnlyDatastore && !fixedDatastore)
                    {
                        // If we aren't a read-only datastore, try to create a table and then
                        // retrieve its details, so as to obtain the catalog, schema.
                        ProbeTable pt = new ProbeTable(this);
                        pt.initialize(clr);
                        pt.create(conn);
                        try
                        {
                            String[] schema_details = pt.findSchemaDetails(conn);
                            if (schema_details != null)
                            {
                                catalogName = schema_details[0];
                                schemaName = schema_details[1];
                            }
                        }
                        finally
                        {
                            pt.drop(conn);
                        }
                    }
                }
            }
            catch (SQLException e)
            {
                String msg = LOCALISER_RDBMS.msg("050005") + ' ' +
                    LOCALISER_RDBMS.msg("050006") + ' ' + e.toString();
                JPOXLogger.DATASTORE_SCHEMA.warn(msg);
                // This is only logged as a warning since if the JDBC driver has some issue creating the ProbeTable we would be stuck
                // We need to allow SchemaTool "dbinfo" mode to work in all circumstances.
            }
        }

        if (!readOnlyDatastore)
        {
            // Provide any JPOX addons for the datastore that may be needed later
            dba.initialiseDatastore(conn);
        }

        // AutoStarter - Load up any startup class names
        if ((readOnlyDatastore || fixedDatastore) &&
            (auto_start_mechanism.equals("SchemaTable") || auto_start_mechanism.equals("SchemaTable2")))
        {
            auto_start_mechanism = "None";
        }
        if (JPOXLogger.DATASTORE_SCHEMA.isInfoEnabled())
        {
            JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER_RDBMS.msg("050008", catalogName, schemaName,
                auto_start_mechanism));
        }
        initialiseAutoStart(auto_start_mechanism, auto_start_mechanism_mode, clr);

        if (JPOXLogger.DATASTORE_SCHEMA.isInfoEnabled())
        {
            if (readOnlyDatastore)
            {
                JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER_RDBMS.msg("050010",
                    catalogName, schemaName, "" + storeDataMgr.size()));
            }
            else if (fixedDatastore)
            {
                JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER_RDBMS.msg("050011",
                        catalogName, schemaName, "" + storeDataMgr.size()));
            }
            else
            {
                JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER_RDBMS.msg("050009",
                        catalogName, schemaName, "" + storeDataMgr.size()));
            }
        }
    }

    /**
     * Clears all knowledge of tables, cached requests, metadata, etc and resets
     * the store manager to its initial state.
     */
    private void clearSchemaData()
    {
        deregisterAllStoreData();

        columnInfoByTableName.clear();
        columnInfoReadTimestamp = -1;
        ((RDBMSPersistenceHandler)persistenceHandler).removeAllRequests();
    }

    /**
     * Accessor for the (default) RDBMS catalog name.
     *
     * @return The catalog name.
     */
    public String getCatalogName()
    {
        return catalogName;
    }

    /**
     * Accessor for the (default) RDBMS schema name.
     *
     * @return The schema name.
     */
    public String getSchemaName()
    {
        return schemaName;
    }

    // ----------------------------- Class Management -------------------------------

    /**
     * Method to add several (PersistenceCapable) classes to the store manager's set of supported classes.
     * This will create any necessary database objects (tables, views, constraints, indexes etc).
     * This will also cause the addition of any related classes.
     * @param classNames Name of the class(es) to be added.
     * @param clr The ClassLoaderResolver
     */
    public void addClasses(String[] classNames, ClassLoaderResolver clr)
    {
        addClasses(classNames, clr, (Writer)null, false);
    }
   
    /**
     * Method to add several (PersistenceCapable) classes to the store manager's set of supported classes.
     * This will create any necessary database objects (tables, views, constraints, indexes etc).
     * This will cause the addition of any related classes.
     * <b>This is used by SchemaTool</b>
     * @param classNames Name of the class(es) to be added.
     * @param clr The ClassLoaderResolver
     * @param writer Optional writer when you just want the DDL for persisting the specified classes
     * @param completeDdl whether complete DDL will be created when writing DDL to a file, or only for missing elements
     */
    public void addClasses(String[] classNames, ClassLoaderResolver clr, Writer writer, boolean completeDdl)
    {
        if (writer != null)
        {
            AbstractTable.setDdlWriter(writer);
        }
        AbstractTable.setCompleteDdl(completeDdl);

        synchronized (CLASSADDER_MUTEX)
        {
            if (classAdder != null)
            {
                // addClasses() has been recursively re-entered: just add table
                // objects for the requested classes and return.
                classAdder.addClasses(classNames, clr);
                return;
            }
        }
        if (classNames != null && classNames.length > 0)
        {
            new ClassAdder(classNames, writer).execute(clr);
        }
    }

    /**
     * Utility to remove all classes that we are managing.
     * @param clr The ClassLoaderResolver
     */
    public void removeAllClasses(ClassLoaderResolver clr)
    {
        MgmtTransaction mtx = new MgmtTransaction(Connection.TRANSACTION_READ_COMMITTED)
        {
            public String toString()
            {
                return LOCALISER_RDBMS.msg("050045", catalogName, schemaName);
            }

            protected void run(ClassLoaderResolver clr)
            throws SQLException
            {
                synchronized (RDBMSManager.this)
                {
                    boolean success = true;
                    try
                    {
                        JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER_RDBMS.msg("050045",
                            catalogName, schemaName));

                        Map baseTablesByName = new HashMap();
                        Map viewsByName = new HashMap();
                        for (Iterator i = storeDataMgr.getManagedStoreData().iterator(); i.hasNext();)
                        {
                            RDBMSStoreData data = (RDBMSStoreData) i.next();
                            if (JPOXLogger.DATASTORE_SCHEMA.isInfoEnabled())
                            {
                                JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER_RDBMS.msg("050046",data.getName()));
                            }
                           
                            // If the class has a table/view to remove, add it to the list
                            if (data.hasTable())
                            {
                                if (data.mapsToView())
                                {
                                    viewsByName.put(data.getDatastoreIdentifier(),
                                        data.getDatastoreContainerObject());
                                }
                                else
                                {
                                    baseTablesByName.put(data.getDatastoreIdentifier(),
                                        data.getDatastoreContainerObject());
                                }
                            }
                        }
                       
                        // Remove tables, table constraints and views in
                        // reverse order from which they were created
                        Iterator viewsIter = viewsByName.values().iterator();
                        while (viewsIter.hasNext())
                        {
                            ((ViewImpl) viewsIter.next()).drop(getCurrentConnection());
                        }

                        Iterator tablesIter = baseTablesByName.values().iterator();
                        while (tablesIter.hasNext())
                        {
                            ((TableImpl) tablesIter.next()).dropConstraints(getCurrentConnection());
                        }
                        tablesIter = baseTablesByName.values().iterator();
                        while (tablesIter.hasNext())
                        {
                            ((TableImpl) tablesIter.next()).drop(getCurrentConnection());
                        }
                    }
                    catch (Exception e)
                    {
                        success = false;
                        String errorMsg = LOCALISER_RDBMS.msg("050047", e);
                        JPOXLogger.DATASTORE_SCHEMA.error(errorMsg);
                        throw new JPOXUserException(errorMsg, e);
                    }
                    finally
                    {
                        // Only empty our schema data if we succeeded in
                        // dropping everything with no error.
                        if (success)
                        {
                            clearSchemaData();
                        }
                    }
                }
            }
        };
        mtx.execute(clr);
    }

    // ---------------------------------------------------------------------------------------

    /**
     * Method to create a new fetch statement for the passed table.
     * @param table The table to fetch from
     * @return The fetch statement
     */
    public FetchStatement getFetchStatement(DatastoreContainerObject table)
    {
        return new RDBMSFetchStatement(table);
    }

    /**
     * Accessor for a FieldManager capable of processing the results of a query.
     * @param sm StateManager for the object
     * @param rs The results
     * @param stmtExprIndx Statement expression indices for results -> fields mapping
     * @return The FieldManager
     */
    public FieldManager getFieldManagerForResultProcessing(StateManager sm, Object rs,
            StatementExpressionIndex[] stmtExprIndx)
    {
        return new ResultSetGetter(sm, (ResultSet) rs, stmtExprIndx);
    }

    /**
     * Called by (container) Mapping objects to request the creation of a DatastoreObject (table).
     * If the specified field doesn't require a join table then this returns null.
     * If the join table already exists, then this returns it.
     * @param fmd The field metadata describing the field.
     * @param clr The ClassLoaderResolver
     * @return The container object (SetTable/ListTable/MapTable/ArrayTable)
     */
    public synchronized DatastoreContainerObject newJoinDatastoreContainerObject(AbstractMemberMetaData fmd, ClassLoaderResolver clr)
    {
        if (fmd.getJoinMetaData() == null)
        {
            AbstractMemberMetaData[] relatedMmds = fmd.getRelatedMemberMetaData(clr);
            if (relatedMmds != null && relatedMmds[0].getJoinMetaData() != null)
            {
                // Join specified at other end of a bidirectional relation so create a join table
            }
            else
            {
                Class element_class;
                if (fmd.hasCollection())
                {
                    element_class = clr.classForName(fmd.getCollection().getElementType());
                }
                else if (fmd.hasMap())
                {
                    MapMetaData mmd = (MapMetaData)fmd.getContainer();
                    if (fmd.getValueMetaData() != null && fmd.getValueMetaData().getMappedBy() != null)
                    {
                        // value stored in the key table
                        element_class = clr.classForName(mmd.getKeyType());
                    }
                    else if (fmd.getKeyMetaData() != null && fmd.getKeyMetaData().getMappedBy() != null)
                    {
                        // key stored in the value table
                        element_class = clr.classForName(mmd.getValueType());
                    }
                    else
                    {
                        // No information given for what is stored in what, so throw it back to the user to fix the input :-)
                        throw new JPOXUserException(LOCALISER_RDBMS.msg("050050", fmd.getFullFieldName()));
                    }
                }
                else if (fmd.hasArray())
                {
                    element_class = clr.classForName(fmd.getTypeName()).getComponentType();
                }
                else
                {
                    // what is this? should not happen
                    return null;
                }

                // Check that the element class has MetaData
                if (getMetaDataManager().getMetaDataForClass(element_class, clr) != null)
                {
                    // FK relationship, so no join table
                    return null;
                }
                else if (getOMFContext().getTypeManager().isReferenceType(element_class))
                {
                    // reference type using FK relationship so no join table
                    return null;
                }

                // Trap all non-PC elements that haven't had a join table specified but need one
                throw new JPOXUserException(LOCALISER_RDBMS.msg("050049",
                    fmd.getFullFieldName(), fmd.toString()));
            }
        }

        // Check if the join table already exists
        DatastoreContainerObject joinTable = getDatastoreContainerObject(fmd);
        if (joinTable != null)
        {
            return joinTable;
        }

        // Create a new join table for the container
        if (classAdder == null)
        {
            throw new IllegalStateException(LOCALISER_RDBMS.msg("050016"));
        }

        if (fmd.getType().isArray())
        {
            // Use Array table for array types
            return classAdder.addJoinTableForContainer(fmd, clr, ClassAdder.JOIN_TABLE_ARRAY);
        }
        else if (getOMFContext().getTypeManager().isSCOMap(fmd.getType()))
        {
            // Use Map join table for supported map types
            return classAdder.addJoinTableForContainer(fmd, clr, ClassAdder.JOIN_TABLE_MAP);
        }
        else
        {
            // Use Set join table for collection/set types
            return classAdder.addJoinTableForContainer(fmd, clr, ClassAdder.JOIN_TABLE_COLLECTION);
        }
    }
   
    /**
     * Accessor for the backing store for the specified field.
     * @param clr The ClassLoaderResolver
     * @param fmd the field to be persisted by this Store
     * @param type instantiated type or prefered type
     * @return The backing store
     */
    public Store getBackingStoreForField(ClassLoaderResolver clr, AbstractMemberMetaData fmd, Class type)
    {
        if (fmd != null && fmd.isSerialized())
        {
            return null;
        }
       
        if (fmd.getMap() != null)
        {
            assertCompatibleFieldType(fmd,clr,type,MapMapping.class);
            return getBackingStoreForMap(fmd, clr, type);
        }
        if (fmd.getArray() != null)
        {
            assertCompatibleFieldType(fmd,clr,type,ArrayMapping.class);
            return getBackingStoreForArray(fmd, clr, type);
        }
        assertCompatibleFieldType(fmd,clr,type,CollectionMapping.class);
               
        return getBackingStoreForCollection(fmd, clr, type);
    }

    /**
     * Asserts the current mapping for the field is the one expected 
     * @param fmd
     * @param clr
     * @param type
     * @param expectedMappingType
     */
    private void assertCompatibleFieldType(AbstractMemberMetaData fmd, ClassLoaderResolver clr, Class type, Class expectedMappingType)
    {
        DatastoreClass ownerTable = getDatastoreClass(fmd.getClassName(), clr);
        JavaTypeMapping m = ownerTable.getFieldMapping(fmd);
        if (!expectedMappingType.isAssignableFrom(m.getClass()))
        {
            throw new IncompatibleFieldTypeException(fmd.getFullFieldName(),
                type.getName(), fmd.getTypeName());
        }
    }

    /**
     * Method to return a backing store for a Collection, consistent with this store and the instantiated type.
     * @param fmd MetaData for the field that has this collection
     * @param datastoreTable The datastore object
     * @param clr ClassLoader resolver
     * @param instantiated Whether it is already instantiated
     * @param listBased whether the collection is list based
     * @return The backing store of this collection in this store
     */
    private CollectionStore getBackingStoreForCollection(AbstractMemberMetaData fmd, 
            ClassLoaderResolver clr, Class type)
    {
        CollectionStore store = null;
        DatastoreContainerObject datastoreTable = getDatastoreContainerObject(fmd);
        if (type==null)
        {
            // No type to base it on so create it based on the field declared type
            if (datastoreTable == null)
            {
                // We need an "Inverse". Where the owner column resides in the element table
                if (getOMFContext().getTypeManager().isSCOList(fmd.getType()))
                {
                    store = new FKListStore(fmd, this, clr);
                }
                else
                {
                    store = new FKSetStore(fmd, this, clr);
                }
            }
            else
            {
                // If a join table is present we need a "Normal"
                if (getOMFContext().getTypeManager().isSCOList(fmd.getType()))
                {
                    store = new JoinListStore(fmd, (CollectionTable)datastoreTable, clr);
                }
                else
                {
                    store = new JoinSetStore(fmd, (CollectionTable)datastoreTable, clr);
                }
            }
        }
        else
        {
            // Instantiated type specified so use it to pick the associated backing store
            if (datastoreTable == null)
            {
                if (SCOUtils.isListBased(type))
                {
                    // List required
                    store = new FKListStore(fmd, this, clr);
                }
                else
                {
                    // Set required
                    store = new FKSetStore(fmd, this, clr);
                }
            }
            else
            {
                if (SCOUtils.isListBased(type))
                {
                    // List required
                    store = new JoinListStore(fmd, (CollectionTable)datastoreTable, clr);
                }
                else
                {
                    // Set required
                    store = new JoinSetStore(fmd, (CollectionTable)datastoreTable, clr);
                }
            }
        }
        return store;
    }

    /**
     * Method to return a backing store for a Map, consistent with this store and the instantiated type.
     * @param fmd MetaData for the field that has this map
     * @param datastoreTable The datastore object
     * @param clr ClassLoader resolver
     * @return The backing store of this map in this store
     */
    private MapStore getBackingStoreForMap(AbstractMemberMetaData fmd, ClassLoaderResolver clr, Class type)
    {
        MapStore store = null;
        DatastoreContainerObject datastoreTable = getDatastoreContainerObject(fmd);
        if (datastoreTable == null)
        {
            store = new FKMapStore(fmd, this, clr);
        }
        else
        {
            store = new JoinMapStore((MapTable)datastoreTable, clr);
        }
        return store;
    }

    /**
     * Method to return a backing store for an array, consistent with this store and the instantiated type.
     * @param fmd MetaData for the field that has this array
     * @param datastoreTable The datastore object
     * @param clr ClassLoader resolver
     * @return The backing store of this array in this store
     */
    private ArrayStore getBackingStoreForArray(AbstractMemberMetaData fmd, ClassLoaderResolver clr, Class type)
    {
        ArrayStore store = null;
        DatastoreContainerObject datastoreTable = getDatastoreContainerObject(fmd);
        if (datastoreTable != null)
        {
            store = new JoinArrayStore((ArrayTable)datastoreTable, clr);
        }
        else
        {
            store = new FKArrayStore(fmd, this, clr);
        }
        return store;
    }

    /**
     * Returns the class corresponding to the given object JDO ID. If the object
     * is an OID, return the PersistenceCapable class. If the object is a SCOID,
     * return the SCO class. If the object is an AppID PK, return the PersistenceCapable
     * class that the id relates to. If SingleFieldIdentity return the associated PC class.
     * If the object id is an application id and the user supplies the "pm" argument then
     * a check can be performed in the datastore where necessary.
     * @param id The JDO identity of some object.
     * @param clr ClassLoader resolver
     * @param om Object Manager (optional - to allow check in the datastore)
     * @return For datastore identity, return the class of the corresponding
     * object. For application identity, return the class of the corresponding
     * object or null if object does not exist.
     * @exception ClassCastException If the type of ID is not recognized (
     * {@link OID}or {@link SCOID}).
     */
    public String getClassNameForObjectID(Object id, ClassLoaderResolver clr, ObjectManager om)
    {
        // Object is a SCOID
        if (id instanceof SCOID)
        {
            return ((SCOID) id).getSCOClass();
        }
        // Multiple subclasses so we need to find which one using a query
        // Only include classes which have a table.
        List classTree = new ArrayList();

        String targetClassName = null;
        AbstractClassMetaData cmd = null;
        ApiAdapter api = getApiAdapter();

        if (id instanceof OID)
        {
            // Object is an OID
            OID oid = (OID) id;
            cmd = getMetaDataManager().getMetaDataForClass(oid.getPcClass(), clr);
            if( cmd.getDiscriminatorStrategy() == null)
            {
                // Using SingleFieldIdentity so can assume that object is of the target class or a subclass
                List subclasses = new ArrayList(this.getSubClassesForClass(oid.getPcClass(), true, clr));
                if (subclasses.size() < 1)
                {
                    JPOXLogger.PERSISTENCE.debug("1) Id \""+id+"\" has been determined to be the id of class "+oid.getPcClass());
                    // No subclasses so must be an instance of the OID class
                    return oid.getPcClass();
                }
                targetClassName = oid.getPcClass();
            }
        }
        else if (api.isSingleFieldIdentity(id))
        {
            // Using SingleFieldIdentity so can assume that object is of the target class or a subclass
            targetClassName = api.getTargetClassNameForSingleFieldIdentity(id);

            cmd = getMetaDataManager().getMetaDataForClass(targetClassName, clr);
            if (cmd.getIdentityType() != IdentityType.APPLICATION || !cmd.getObjectidClass().equals(id.getClass().getName()))
            {
                throw new JPOXUserException(LOCALISER_RDBMS.msg("050022", id, cmd.getFullClassName()));
            }
            if( cmd.getDiscriminatorStrategy() != null)
            {   
                targetClassName = null;
            }
        }
        else
        {
            // Find all of the application identity PK classes of this type
            Collection c = storeDataMgr.getByPrimaryKeyClass(id.getClass().getName());
            if (c != null)
            {
                Iterator iter = c.iterator();
                while (iter.hasNext())
                {
                    RDBMSStoreData store_data = (RDBMSStoreData)iter.next();
                    if (store_data.hasTable())
                    {
                        classTree.add(store_data);
                    }
                }
            }
        }

        //OID or SingleFieldIdentity
        if (targetClassName != null)
        {
            RDBMSStoreData data = (RDBMSStoreData)storeDataMgr.get(targetClassName);
            if (data != null)
            {
                if (data.hasTable())
                {
                    classTree.add(data);
                }
                HashSet subclasses = getSubClassesForClass(targetClassName, true, clr);
                Iterator subclassesIter = subclasses.iterator();
                while (subclassesIter.hasNext())
                {
                    String subclassName = (String)subclassesIter.next();
                    RDBMSStoreData subclassData = (RDBMSStoreData)storeDataMgr.get(subclassName);
                    if (subclassData.hasTable())
                    {
                        classTree.add(subclassData);
                    }
                }
            }
        }

        if (classTree.size() == 0 && (cmd == null || cmd.getDiscriminatorStrategy()==null))
        {
            JPOXLogger.PERSISTENCE.debug("2) Id \""+id+"\" has not determined to the class");
            return null;
        }
        else if (classTree.size() == 1)
        {
            cmd = getMetaDataManager().getMetaDataForClass(((RDBMSStoreData)classTree.get(0)).getName(), clr);
            if (cmd == null || cmd.getDiscriminatorStrategy() == null)
            {
                JPOXLogger.PERSISTENCE.debug("3) Id \""+id+"\" has been determined to be the id of class "+((RDBMSStoreData)classTree.get(0)).getName());
                return ((RDBMSStoreData)classTree.get(0)).getName();
            }
        }
        if (om != null)
        {
            // Perform a check on the exact object inheritance level with this key (uses SQL query)
            String className = null;
            if( cmd != null && cmd.getDiscriminatorStrategy()!=null)
            {
                DatastoreClass primaryTable = this.getDatastoreClass(cmd.getFullClassName(), om.getClassLoaderResolver());
                if (primaryTable != null)
                {
                    classTree.add(storeDataMgr.get(cmd.getFullClassName()));
                    HashSet subclasses = getSubClassesForClass(cmd.getFullClassName(), true, clr);
                    Iterator subclassesIter = subclasses.iterator();
                    while (subclassesIter.hasNext())
                    {
                        String subclassName = (String)subclassesIter.next();
                        RDBMSStoreData subclassData = (RDBMSStoreData)storeDataMgr.get(subclassName);
                        if (subclassData.hasTable())
                        {
                            classTree.add(subclassData);
                        }
                    }
                    className = RDBMSStoreHelper.getClassNameForIdKeyUsingDiscriminator(om, this, id, classTree);
                    if (className != null)
                    {
                        JPOXLogger.PERSISTENCE.debug("4) Id \""+id+"\" has been determined to be the id of class "+className);
                        return className;
                    }
                    return null;
                }
            }
            else
            {
                className = RDBMSStoreHelper.getClassNameForIdKeyUsingUnion(om, this, id, classTree);
            }
            if (className != null)
            {
                JPOXLogger.PERSISTENCE.debug("6) Id \""+id+"\" has been determined to be the id of class "+className);
                return className;
            }

            if (id instanceof OID)
            {
                JPOXLogger.PERSISTENCE.debug("7) Id \""+id+"\" has been determined to be the id of class "+((OID)id).getPcClass());
                return ((OID)id).getPcClass();
            }
        }
        else
        {
            // Check not possible so just return the first
            JPOXLogger.PERSISTENCE.debug("8) Id \""+id+"\" has been determined to be the id of class "+((RDBMSStoreData)classTree.get(0)).getName());
            return ((RDBMSStoreData)classTree.get(0)).getName();
        }

        return null;
    }

    /**
     * Accessor for an Extent for a class, and its subclasses.
     * @param om The ObjectManager
     * @param c The (candidate) class to use for the Extent
     * @param subclasses Whether to include subclasses of 'c'
     * @return The Extent
     * @exception NoExtentException if an extent is not managed for the
     * specified class
     */
    public Extent getExtent(ObjectManager om, Class c, boolean subclasses)
    {
        AbstractClassMetaData cmd = getMetaDataManager().getMetaDataForClass(c, om.getClassLoaderResolver());
        if (!cmd.isRequiresExtent())
        {
            throw new NoExtentException(c.getName());
        }

        DatastoreClass t = getDatastoreClass(cmd.getFullClassName(), om.getClassLoaderResolver());
        if (cmd.getInheritanceMetaData().getStrategyValue() == InheritanceStrategy.COMPLETE_TABLE)
        {
            // "complete-table" may imply many tables not discoverable from the root table
            HashSet candidateTables = new HashSet();
            if (t != null)
            {
                candidateTables.add(t);
            }
            if (subclasses)
            {
                HashSet subclassNames = getSubClassesForClass(cmd.getFullClassName(), subclasses, om.getClassLoaderResolver());
                if (subclassNames != null)
                {
                    Iterator subclassIter = subclassNames.iterator();
                    while (subclassIter.hasNext())
                    {
                        String subclassName = (String)subclassIter.next();
                        DatastoreClass tbl = getDatastoreClass(subclassName, om.getClassLoaderResolver());
                        if (tbl != null)
                        {
                            candidateTables.add(tbl);
                        }
                    }
                }
            }
            return new ClassTableExtent(om,
                (DatastoreClass[])candidateTables.toArray(new DatastoreClass[candidateTables.size()]),
                c, subclasses, cmd);
        }
        else
        {
            // "new-table", "superclass-table", "subclass-table"
            if (t instanceof ClassView)
            {
                return new ClassViewExtent(om, t, c, subclasses, cmd);
            }
            else if (t instanceof ClassTable)
            {
                return new ClassTableExtent(om, t, c, subclasses, cmd);
            }
            else
            {
                // No specific table, so either has no table, or multiple
                // This happens with the "subclass-table" inheritance strategy
                AbstractClassMetaData[] cmds = getClassesManagingTableForClass(cmd, om.getClassLoaderResolver());
                if (cmds != null)
                {
                    DatastoreClass[] tables = new DatastoreClass[cmds.length];
                    for (int i=0;i<cmds.length;i++)
                    {
                        tables[i] = getDatastoreClass(cmds[i].getFullClassName(), om.getClassLoaderResolver());
                    }
                    return new ClassTableExtent(om, tables, c, subclasses, cmd);
                }
                else
                {
                    throw new JPOXUserException("Attempt to create an Extent for class " + c.getName() + " which has MetaData, yet no table was found! This should be impossible");
                }
            }
        }
    }

    /**
     * Convenience method to return a PreparedStatement for a QueryExpression.
     * @param qs The query expression
     * @param om ObjectManager
     * @param conn The connection to use
     * @param updateLock Whether to use an update lock
     * @param resultSetType Type of result set (if any)
     * @param resultSetConcurrency result-set concurrency (if any)
     * @return The PreparedStatement
     * @throws SQLException If an error occurs in creation
     */
    public PreparedStatement getStatementForQuery(QueryExpression qs, ObjectManager om, ManagedConnection conn,
            boolean updateLock, String resultSetType, String resultSetConcurrency)
    throws SQLException
    {
        StatementText text = qs.toStatementText(updateLock);
        SQLController sqlControl = getSQLController();

        // Generate the statement using the statement text
        PreparedStatement ps = sqlControl.getStatementForQuery(conn, text.toString(), resultSetType, resultSetConcurrency);

        boolean done = false;
        try
        {
            // Apply any parameter values for the statement
            text.setParameters(om, ps);
            done = true;
        }
        finally
        {
            if (!done)
            {
                sqlControl.closeStatement(conn, ps);
            }
        }

        return ps;
    }

    /**
     * Convenience accessor for the statement text for a query expression.
     * @param qs The query expression
     * @param updateLock Whether to use an update lock
     * @return The text of the query
     */
    public String getStatementTextForQuery(QueryExpression qs, boolean updateLock)
    {
        return qs.toStatementText(updateLock).toString();
    }

    /**
     * Accessor for the next value from the specified generator.
     * This implementation caters for datastore-specific generators and provides synchronisation
     * on the connection to the datastore.
     * @param generator The generator
     * @return The next value.
     */
    protected Object getStrategyValueForGenerator(PoidGenerator generator, final ObjectManager om)
    {
        Object oid = null;
        synchronized (generator)
        {
            // Get the next value for this generator for this ObjectManager
            // Note : this is synchronised since we dont want to risk handing out this generator
            // while its connectionProvider is set to that of a different ObjectManager
            // It maybe would be good to change PoidGenerator to have a next taking the connectionProvider
            if (generator instanceof AbstractRDBMSPoidGenerator)
            {
                // RDBMS-based generator so set the connection provider
                final RDBMSManager thisStoreMgr = this;
                PoidConnectionProvider connProvider = new PoidConnectionProvider()
                {
                    ManagedConnection mconn;
                    public ManagedConnection retrieveConnection()
                    {
                        try
                        {
                            PersistenceConfiguration conf = om.getOMFContext().getPersistenceConfiguration();
                            if (conf.getStringProperty("org.jpox.poid.transactionAttribute").equalsIgnoreCase("UsePM"))
                            {
                                this.mconn = thisStoreMgr.getConnection(om);
                            }
                            else
                            {
                                int isolationLevel = TransactionUtils.getTransactionIsolationLevelForName(
                                    conf.getStringProperty("org.jpox.poid.transactionIsolation"));
                                this.mconn = thisStoreMgr.getConnection(isolationLevel);
                            }
                        }
                        catch (SQLException e)
                        {
                            String msg = LOCALISER_RDBMS.msg("050024", e.getMessage());
                            JPOXLogger.POID.error(msg);
                            throw new JPOXDataStoreException(msg, e);
                        }
                        return mconn;
                    }

                    public void releaseConnection()
                    {
                        try
                        {
                            PersistenceConfiguration conf = om.getOMFContext().getPersistenceConfiguration();
                            if (conf.getStringProperty("org.jpox.poid.transactionAttribute").equalsIgnoreCase("UsePM"))
                            {
                                mconn.release();
                            }
                            else
                            {
                                mconn.close();
                            }
                            mconn = null;
                        }
                        catch (JPOXException e)
                        {
                            String msg = LOCALISER_RDBMS.msg("050025", e);
                            JPOXLogger.POID.error(msg);
                            throw new JPOXDataStoreException(msg, e);
                        }
                    }
                };
                ((AbstractRDBMSPoidGenerator)generator).setConnectionProvider(connProvider);
            }
            oid = generator.next();
        }
        return oid;
    }

    /**
     * Method to return the properties to pass to the generator for the specified field.
     * @param cmd MetaData for the class
     * @param absoluteFieldNumber Number of the field (-1 = datastore identity)
     * @param om Object Manager
     * @param seqmd Any sequence metadata
     * @param tablegenmd Any table generator metadata
     * @return The properties to use for this field
     */
    protected Properties getPropertiesForGenerator(AbstractClassMetaData cmd, int absoluteFieldNumber,
            ObjectManager om, SequenceMetaData seqmd, TableGeneratorMetaData tablegenmd)
    {
        AbstractMemberMetaData mmd = null;
        IdentityStrategy strategy = null;
        String sequence = null;
        ExtensionMetaData[] extensions = null;
        if (absoluteFieldNumber >= 0)
        {
            // real field
            mmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(absoluteFieldNumber);
            strategy = mmd.getValueStrategy();
            sequence = mmd.getSequence();
            extensions = mmd.getExtensions();
        }
        else
        {
            // datastore-identity surrogate field
            strategy = cmd.getIdentityMetaData().getValueStrategy();
            sequence = cmd.getIdentityMetaData().getSequence();
            extensions = cmd.getIdentityMetaData().getExtensions();
        }

        // Get base table with the required field
        // TODO The passed-in table may already be the right one - do this better
        DatastoreClass tbl = getDatastoreClass(cmd.getBaseAbstractClassMetaData().getFullClassName(),
            om.getClassLoaderResolver());
        if (tbl == null)
        {
            tbl = getTableForStrategy(cmd,absoluteFieldNumber,om.getClassLoaderResolver());
        }
        JavaTypeMapping m = null;
        if (mmd != null)
        {
            m = tbl.getFieldMapping(mmd);
            if (m == null)
            {
                // Field not mapped in root table so use passed-in table
                tbl = getTableForStrategy(cmd,absoluteFieldNumber,om.getClassLoaderResolver());
                m = tbl.getFieldMapping(mmd);
            }
        }
        else
        {
            m = tbl.getIDMapping();
        }
        StringBuffer columnsName = new StringBuffer();
        for (int i = 0; i < m.getNumberOfDatastoreFields(); i++)
        {
            if (i > 0)
            {
                columnsName.append(",");
            }
            columnsName.append(m.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
        }

        Properties properties = new Properties();
        properties.setProperty("class-name", cmd.getFullClassName());
        properties.put("root-class-name", cmd.getBaseAbstractClassMetaData().getFullClassName());
        if (mmd != null)
        {
            properties.setProperty("field-name", mmd.getFullFieldName());
        }
        if (cmd.getCatalog() != null)
        {
            properties.setProperty("catalog-name", cmd.getCatalog());
        }
        if (cmd.getSchema() != null)
        {
            properties.setProperty("schema-name", cmd.getSchema());
        }
        properties.setProperty("table-name", tbl.getIdentifier().toString());
        properties.setProperty("column-name", columnsName.toString());

        if (sequence != null)
        {
            properties.setProperty("sequence-name", sequence);
        }

        // Add any extension properties
        if (extensions != null)
        {
            for (int i=0;i<extensions.length;i++)
            {
                properties.put(extensions[i].getKey(), extensions[i].getValue());
            }
        }

        if (strategy == IdentityStrategy.INCREMENT && tablegenmd != null)
        {
            // User has specified a TableGenerator (JPA)
            properties.put("key-initial-value", "" + tablegenmd.getInitialValue());
            properties.put("key-cache-size", "" + tablegenmd.getAllocationSize());
            if (tablegenmd.getTableName() != null)
            {
                properties.put("sequence-table-name", tablegenmd.getTableName());
            }
            if (tablegenmd.getCatalogName() != null)
            {
                properties.put("sequence-catalog-name", tablegenmd.getCatalogName());
            }
            if (tablegenmd.getSchemaName() != null)
            {
                properties.put("sequence-schema-name", tablegenmd.getSchemaName());
            }
            if (tablegenmd.getPKColumnName() != null)
            {
                properties.put("sequence-name-column-name", tablegenmd.getPKColumnName());
            }
            if (tablegenmd.getPKColumnName() != null)
            {
                properties.put("sequence-nextval-column-name", tablegenmd.getValueColumnName());
            }
            if (tablegenmd.getPKColumnValue() != null)
            {
                properties.put("sequence-name", tablegenmd.getPKColumnValue());
            }

            // Using JPA generator so dont enable JPOX initial value detection
            properties.remove("table-name");
            properties.remove("column-name");
        }
        else if (strategy == IdentityStrategy.SEQUENCE && seqmd != null)
        {
            // User has specified a SequenceGenerator (JDO/JPA)
            if (seqmd.getDatastoreSequence() != null)
            {
                if (seqmd.getInitialValue() >= 0)
                {
                    properties.put("key-initial-value", "" + seqmd.getInitialValue());
                }
                if (seqmd.getAllocationSize() > 0)
                {
                    properties.put("key-cache-size", "" + seqmd.getAllocationSize());
                }
                properties.put("sequence-name", "" + seqmd.getDatastoreSequence());

                // Add on any extensions specified on the sequence
                ExtensionMetaData[] seqExtensions = seqmd.getExtensions();
                if (seqExtensions != null)
                {
                    for (int i=0;i<seqExtensions.length;i++)
                    {
                        properties.put(seqExtensions[i].getKey(), seqExtensions[i].getValue());
                    }
                }
            }
            else
            {
                // JDO Factory-based sequence generation
                // TODO Support this
            }
        }
        return properties;
    }

    private DatastoreClass getTableForStrategy(AbstractClassMetaData cmd, int fieldNumber, ClassLoaderResolver clr)
    {
        DatastoreClass t = getDatastoreClass(cmd.getFullClassName(), clr);
        if (t == null && cmd.getInheritanceMetaData().getStrategyValue() == InheritanceStrategy.SUBCLASS_TABLE)
        {
            throw new JPOXUserException(LOCALISER.msg("032013", cmd.getFullClassName()));
        }

        if (fieldNumber>=0)
        {
            AbstractMemberMetaData fmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber);
            t = t.getBaseDatastoreClassWithField(fmd);
        }
        else if (t!=null)
        {
            // Go up to overall superclass to find id for that class.
            boolean has_superclass = true;
            while (has_superclass)
            {
                DatastoreClass supert = t.getSuperDatastoreClass();
                if (supert != null)
                {
                    t = supert;
                }
                else
                {
                    has_superclass = false;
                }
            }
        }
        return t;
    }
   
    /**
     * Method defining which value-strategy to use when the user specifies "native".
     * @param sequence Sequence name if specified
     * @return Just returns "increment". Should be overridden by all store managers that have other behaviour.
     */
    protected String getStrategyForNative(String sequence)
    {
        // Using "native" generator so pick the most suitable
        if (((RDBMSAdapter)dba).supportsSequences() && sequence != null)
        {
            return "sequence";
        }
        else
        {
            return "table-sequence"; // Maybe ought to use "increment"
        }
    }

    /**
     * Returns the column info for a column name. This should be used instead
     * of making direct calls to DatabaseMetaData.getColumns().
     * <p>
     * Where possible, this method loads and caches column info for more than
     * just the table being requested, improving performance by reducing the
     * overall number of calls made to DatabaseMetaData.getColumns() (each of
     * which usually results in one or more database queries).
     * </p>
     * @param table The table/view
     * @param conn JDBC connection to the database.
     * @param column the column
     * @return The ColumnInfo objects describing the column.
     * @see org.jpox.store.rdbms.columninfo.ColumnInfo
     * @throws SQLException
     */
    public ColumnInfo getColumnInfoForColumnName(Table table, Connection conn, DatastoreIdentifier column)
    throws SQLException
    {
        ColumnInfo colInfo = null;
        if (storeDataMgr.size() == 0)
        {
            // No SchemaData yet so go to the database directly to find any info for this table.
            colInfo = RDBMSStoreHelper.getColumnInfoForColumnName(this, table, conn, column);
            if (JPOXLogger.DATASTORE_SCHEMA.isDebugEnabled())
            {
                JPOXLogger.DATASTORE_SCHEMA.debug(LOCALISER_RDBMS.msg("050032", table, (colInfo != null ? "" + 1 : "" + 0)));
            }
        }
        else
        {
            synchronized (this)
            {
                long now = System.currentTimeMillis();

                // Get fully-qualified name for this table (regardless of whether it is specified by user)
                DatastoreIdentifier tableId = ((AbstractTable)table).getDatastoreIdentifierFullyQualified();
                List cols = null;
                // Check if we already have valid info for this table in the cache
                if (now >= columnInfoReadTimestamp &&
                    now < columnInfoReadTimestamp + COLUMN_INFO_EXPIRATION_MS)
                {
                    cols = (List) columnInfoByColumnName.get(tableId.getIdentifier()+column.getIdentifier());
                    if (cols != null)
                    {
                        for(int i=0; i<cols.size(); i++)
                        {
                            String[] c = RDBMSStoreHelper.splitColumnIdentifierName(((RDBMSAdapter)this.getDatastoreAdapter()).getCatalogSeparator(), column.getIdentifier());
                            String columnName = column.getIdentifier();
                            if (c[RDBMSStoreHelper.TABLE_IDENTIFIER_COLUMN] != null)
                            {
                                columnName = c[RDBMSStoreHelper.TABLE_IDENTIFIER_COLUMN];
                            }                           
                            ColumnInfo ci = (ColumnInfo) cols.get(i);
                            if (ci.getColumnName().equals(columnName))
                            {
                                colInfo = ci;
                            }
                        }
                    }
                }

                // Refresh the cache if we have no/stale info for that table
                if (cols == null)
                {
                    //TODO have a cache of columnInfo by column name
                    colInfo = RDBMSStoreHelper.getColumnInfoForColumnName(this, table, conn, column);
                    if (colInfo == null)
                    {
                        JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER_RDBMS.msg("050031", column.getIdentifier(), table));
                    }
                    else
                    {
                        columnInfoByColumnName.put(tableId.getIdentifier()+column.getIdentifier(), colInfo);
                        if (JPOXLogger.DATASTORE_SCHEMA.isDebugEnabled())
                        {
                            JPOXLogger.DATASTORE_SCHEMA.debug(LOCALISER_RDBMS.msg("050033", colInfo.getColumnName(), table));
                        }
                    }
                }
            }
        }

        return colInfo;
    }

    /**
     * Returns the column info for a database table. This should be used instead
     * of making direct calls to DatabaseMetaData.getColumns().
     * <p>
     * Where possible, this method loads and caches column info for more than
     * just the table being requested, improving performance by reducing the
     * overall number of calls made to DatabaseMetaData.getColumns() (each of
     * which usually results in one or more database queries).
     * </p>
     * @param table The table/view
     * @param conn JDBC connection to the database.
     * @return A list of ColumnInfo objects describing the columns of the table.
     * The list is in the same order as was supplied by getColumns(). If no
     * column info is found for the given table, an empty list is returned.
     * @see org.jpox.store.rdbms.columninfo.ColumnInfo
     * @throws SQLException
     */
    public List getColumnInfoForTable(Table table, Connection conn)
    throws SQLException
    {
        List cols = null;
        if (storeDataMgr.size() == 0)
        {
            // No SchemaData yet so go to the database directly to find any info for this table.
            cols = RDBMSStoreHelper.getColumnInfoForTable(this, table, conn);
            if (JPOXLogger.DATASTORE_SCHEMA.isDebugEnabled())
            {
                JPOXLogger.DATASTORE_SCHEMA.debug(LOCALISER_RDBMS.msg("050032", table, (cols != null ? "" + cols.size() : "" + 0)));
            }
        }
        else
        {
            synchronized (this)
            {
                long now = System.currentTimeMillis();

                // Get fully-qualified name for this table (regardless of whether it is specified by user)
                DatastoreIdentifier tableId = ((AbstractTable)table).getDatastoreIdentifierFullyQualified();

                // Check if we already have valid info for this table in the cache
                if (now >= columnInfoReadTimestamp &&
                    now < columnInfoReadTimestamp + COLUMN_INFO_EXPIRATION_MS)
                {
                    cols = (List) columnInfoByTableName.get(tableId);
                }

                // Refresh the cache if we have no/stale info for that table
                if (cols == null)
                {
                    refreshColumnInfo(table.getCatalogName(), table.getSchemaName(), conn);

                    // Finally, lookup info for the desired table in the new cache.
                    cols = (List) columnInfoByTableName.get(tableId);
                    if (cols == null)
                    {
                        cols = Collections.EMPTY_LIST;
                        JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER_RDBMS.msg("050030", table));
                    }
                    else
                    {
                        if (JPOXLogger.DATASTORE_SCHEMA.isDebugEnabled())
                        {
                            JPOXLogger.DATASTORE_SCHEMA.debug(LOCALISER_RDBMS.msg("050032", table, "" + cols.size()));
                        }
                    }
                }
            }
        }

        return cols;
    }

    /**
     * Returns the table info for a database schema. This should be used instead
     * of making direct calls to DatabaseMetaData.getTables(). The tables are
     * searched using the default schema name for this RDBMSManager
     * <p>
     * Where possible, this method loads and caches table info for more than
     * just the table being requested, improving performance by reducing the
     * overall number of calls made to DatabaseMetaData.getTables() (each of
     * which usually results in one or more database queries).
     * </p>
     * @param schemaNamePattern the schema name pattern
     * @return A list of TableInfo objects describing the tables.
     * The list is in the same order as was supplied by getTables(). If no
     * table info, an empty list is returned.
     * @see org.jpox.store.rdbms.columninfo.TableInfo
     * @throws SQLException
     */
    public List getTableInfo(String schemaNamePattern)
    throws SQLException
    {
        ManagedConnection mc = getConnection();
        try
        {
            Connection conn = (Connection)mc.getConnection();
            return RDBMSStoreHelper.getTableInfo(this, this.catalogName, schemaNamePattern, conn);
        }
        finally
        {
            if (mc != null)
            {
                mc.close();
            }
        }
    }

    /**
     * Returns the column info for a database schema. This should be used instead
     * of making direct calls to DatabaseMetaData.getColumns().
     * <p>
     * Where possible, this method loads and caches table info for more than
     * just the table being requested, improving performance by reducing the
     * overall number of calls made to DatabaseMetaData.getColumns() (each of
     * which usually results in one or more database queries).
     * </p>
     * @param schemaNamePattern the schema name pattern
     * @param tableNamePattern the table name pattern
     * @return A list of ColumnInfo objects describing the columns.
     * The list is in the same order as was supplied by getColumns(). If no
     * column info, an empty list is returned.
     * @see org.jpox.store.rdbms.columninfo.ColumnInfo
     * @throws SQLException
     */
    public List getColumnInfo(String schemaNamePattern, String tableNamePattern)
    throws SQLException
    {
        ManagedConnection mc = getConnection();
        try
        {
            Connection conn = (Connection)mc.getConnection();
            List cols = new ArrayList();
            ResultSet rs = ((RDBMSAdapter)dba).getColumns(conn, catalogName, schemaNamePattern, tableNamePattern);
            try
            {
                while (rs.next())
                {
                    cols.add(((RDBMSAdapter)dba).newColumnInfo(rs));
                }
            }
            finally
            {
                rs.close();
            }
            return cols;
        }
        finally
        {
            if (mc != null)
            {
                mc.close();
            }
        }
    }

    /**
     * Accessor to the ConnectionProvider
     * @return the ConnectionProvider
     */
    public ConnectionProvider getConnectionProvider()
    {
        return connProvider;
    }

    /**
     * Get the date/time of the datastore.
     * @return Date/time of the datastore
     */
    public Date getDatastoreDate()
    {
        Date serverDate = null;

        String dateStmt = ((RDBMSAdapter)dba).getDatastoreDateStatement();
        ManagedConnection mconn = null;
        try
        {
            mconn = getConnection(UserTransaction.TRANSACTION_NONE);

            PreparedStatement ps = null;
            ResultSet rs = null;
            try
            {
                ps = getSQLController().getStatementForQuery(mconn, dateStmt);
                rs = getSQLController().executeStatementQuery(mconn, dateStmt, ps);
                if (rs.next())
                {
                    // Retrieve the timestamp for the server date/time using the server TimeZone from OMF
                    // Assume that the dateStmt returns 1 column and is Timestamp
                    Timestamp time = rs.getTimestamp(1,
                        getOMFContext().getPersistenceConfiguration().getCalendarForDateTimezone());
                    serverDate = new Date(time.getTime());
                }
                else
                {
                    return null;
                }
            }
            catch (SQLException sqle)
            {
                String msg = LOCALISER_RDBMS.msg("050052", sqle.getMessage());
                JPOXLogger.DATASTORE.warn(msg, sqle);
                throw new JPOXUserException(msg, sqle).setFatal();
            }
            finally
            {
                if (rs != null)
                {
                    rs.close();
                }
                if (ps != null)
                {
                    getSQLController().closeStatement(mconn, ps);
                }
            }
        }
        catch (SQLException sqle)
        {
            String msg = LOCALISER_RDBMS.msg("050052", sqle.getMessage());
            JPOXLogger.DATASTORE.warn(msg, sqle);
            throw new JPOXException(msg, sqle).setFatal();
        }
        finally
        {
            mconn.close();
        }

        return serverDate;
    }

    /**
     * Method to invalidate the cached column info for a table.
     * This is called when we have just added columns to the table in the schema
     * has the effect of a reload of the tables information the next time it is needed.
     * @param table The table
     */
    public void invalidateColumnInfoForTable(Table table)
    {
        DatastoreIdentifier tableId = ((AbstractTable)table).getDatastoreIdentifierFullyQualified();
        columnInfoByTableName.remove(tableId);
    }

    /**
     * Refresh or initialise the column info cache
     * <p>
     * Where possible, this method loads and caches column info for more than
     * just the table being requested, improving performance by reducing the
     * overall number of calls made to DatabaseMetaData.getColumns() (each of
     * which usually results in one or more database queries).
     * </p>
     * @param catalogName Name of catalog to refresh
     * @param schemaName Name of schema to refresh
     * @param conn JDBC connection to the database.
     * @see org.jpox.store.rdbms.columninfo.ColumnInfo
     * @throws SQLException
     */
    private void refreshColumnInfo(String catalogName, String schemaName, Connection conn)
    throws SQLException
    {
        long now = System.currentTimeMillis();

        // Extract a list of known JDO table names.
        Set table_keys = new HashSet();
        for (Iterator i = storeDataMgr.getManagedStoreData().iterator(); i.hasNext();)
        {
            RDBMSStoreData sd = (RDBMSStoreData) i.next();
            if (sd.getDatastoreContainerObject() != null)
            {
                table_keys.add(((AbstractTable)sd.getDatastoreContainerObject()).getDatastoreIdentifierFullyQualified());
            }
        }

        // Query for column info on all tables in catalog/schema,
        // discarding info for any tables that aren't JDO tables,
        // or that are JDO tables but are already validated.
        Map cim = new HashMap();
        ResultSet rs = ((RDBMSAdapter)dba).getColumns(conn, catalogName, schemaName, null);
        try
        {
            while (rs.next())
            {
                // Construct a fully-qualified name for the table in this row of the ResultSet
                String tableIdentifierCatalogName = rs.getString(1);
                String tableIdentifierSchemaName = rs.getString(2);
                String tableIdentifierTableName = rs.getString(3);

                if (rs.wasNull() ||
                    (tableIdentifierCatalogName != null && tableIdentifierCatalogName.length() < 1))
                {
                    tableIdentifierCatalogName = null;
                }
                if (rs.wasNull() ||
                    (tableIdentifierSchemaName != null && tableIdentifierSchemaName.length() < 1))
                {
                    tableIdentifierSchemaName = null;
                }
                if (rs.wasNull() ||
                    (tableIdentifierTableName != null && tableIdentifierTableName.length() < 1))
                {
                    tableIdentifierTableName = null;
                }
                //at least the JDBC driver should return the table name
                if (tableIdentifierTableName == null)
                {
                    throw new JPOXDataStoreException("Invalid 'null' table name identifier returned by database. Check with your JDBC driver vendor (ref:DatabaseMetaData.getColumns).");
                }

                SQLIdentifier fullyQualifiedTableName = (SQLIdentifier)identifierFactory.newDatastoreContainerIdentifier(tableIdentifierTableName);
                fullyQualifiedTableName.setCatalogName(tableIdentifierCatalogName);
                fullyQualifiedTableName.setSchemaName(tableIdentifierSchemaName);

                if (table_keys.contains(fullyQualifiedTableName))
                {
                    // Process this row if it is for one of our tables
                    List l = (List) cim.get(fullyQualifiedTableName);
                    if (l == null)
                    {
                        // Add an entry for the table if none exists
                        l = new ArrayList();
                        cim.put(fullyQualifiedTableName, l);
                    }

                    // Add the column for this tables entry
                    l.add(((RDBMSAdapter)dba).newColumnInfo(rs));
                }
            }
        }
        finally
        {
            rs.close();
        }

        if (JPOXLogger.DATASTORE_SCHEMA.isDebugEnabled())
        {
            JPOXLogger.DATASTORE_SCHEMA.debug(LOCALISER_RDBMS.msg("050029", catalogName, schemaName,
                "" + cim.size(), "" + (System.currentTimeMillis() - now)));
        }

        // Replace the old cache (if any) with the new one
        if (columnInfoByTableName != null && columnInfoByTableName != cim)
        {
            // help the gargabe collector
            columnInfoByTableName.clear();
        }
        columnInfoByTableName = cim;
        columnInfoReadTimestamp = now;       
    }

    /**
     * Resolves an identifier macro. The public fields <var>className</var>,
     * <var>fieldName </var>, and <var>subfieldName </var> of the given macro
     * are taken as inputs, and the public <var>value </var> field is set to the
     * SQL identifier of the corresponding database table or column.
     * @param im The macro to resolve.
     * @param clr The ClassLoaderResolver
     */
    public void resolveIdentifierMacro(MacroString.IdentifierMacro im, ClassLoaderResolver clr)
    {
        DatastoreClass ct = getDatastoreClass(im.className, clr);
        if (im.fieldName == null)
        {
            im.value = ct.getIdentifier().toString();
            return;
        }

        JavaTypeMapping m;
        if (im.fieldName.equals("this"))
        {
            if (!(ct instanceof ClassTable))
            {
                throw new JPOXUserException(LOCALISER_RDBMS.msg("050034", im.className));
            }

            if (im.subfieldName != null)
            {
                throw new JPOXUserException(LOCALISER_RDBMS.msg("050035", im.className, im.fieldName, im.subfieldName));
            }
            m = ((DatastoreContainerObject) ct).getIDMapping();
        }
        else
        {
            AbstractMemberMetaData fmd = getMetaDataManager().getMetaDataForMember(im.className, im.fieldName, clr);
            m = ct.getFieldMapping(fmd);
            DatastoreContainerObject t = getDatastoreContainerObject(fmd);
            if (im.subfieldName == null)
            {
                if (t != null)
                {
                    im.value = t.getIdentifier().toString();
                    return;
                }
            }
            else
            {
                if (t instanceof CollectionTable)
                {
                    CollectionTable collTable = (CollectionTable) t;
                    if (im.subfieldName.equals("owner"))
                    {
                        m = collTable.getOwnerMapping();
                    }
                    else if (im.subfieldName.equals("element"))
                    {
                        m = collTable.getElementMapping();
                    }
                    else if (im.subfieldName.equals("index"))
                    {
                        m = collTable.getOrderMapping();
                    }
                    else
                    {
                        throw new JPOXUserException(LOCALISER_RDBMS.msg(
                            "050036", im.subfieldName, im));
                    }
                }
                else if (t instanceof MapTable)
                {
                    MapTable mt = (MapTable) t;
                    if (im.subfieldName.equals("owner"))
                    {
                        m = mt.getOwnerMapping();
                    }
                    else if (im.subfieldName.equals("key"))
                    {
                        m = mt.getKeyMapping();
                    }
                    else if (im.subfieldName.equals("value"))
                    {
                        m = mt.getValueMapping();
                    }
                    else
                    {
                        throw new JPOXUserException(LOCALISER_RDBMS.msg(
                                "050037",
                                im.subfieldName, im));
                    }
                }
                else
                {
                    throw new JPOXUserException(LOCALISER_RDBMS.msg(
                            "050035", im.className,
                            im.fieldName, im.subfieldName));
                }
            }
        }
        im.value = ((Column)m.getDataStoreMapping(0).getDatastoreField()).getIdentifier().toString();
    }

    /**
     * Accessor for the SQL identifier for a table defined by the specified class.
     * Consults the StoreData for the class. If the class isn't known it generates the table name.
     * @param cmd ClassMetaData for the class
     * @return The SQL identifier for the table
     */
    private DatastoreIdentifier getTableIdentifier(ClassMetaData cmd, ClassLoaderResolver clr)
    {
        RDBMSStoreData sd = (RDBMSStoreData) storeDataMgr.get(cmd.getFullClassName());
        if (sd !=null && sd.getDatastoreIdentifier() != null)
        {
            return sd.getDatastoreIdentifier();
        }
        return identifierFactory.newDatastoreContainerIdentifier(clr, cmd);
    }

    /**
     * Accessor for the SQL identifier for a (join) table defined by the specified field.
     * Consults the StoreData for the field. If the field isn't known it generates the table name.
     * @param mmd Metadata for the field with the SCO needing a table
     * @return The SQL identifier for the table
     */
    private DatastoreIdentifier getTableIdentifier(AbstractMemberMetaData mmd, ClassLoaderResolver clr)
    {
        RDBMSStoreData sd = (RDBMSStoreData) storeDataMgr.get(mmd);
        if (sd != null && sd.getDatastoreIdentifier() != null)
        {
            return sd.getDatastoreIdentifier();
        }
        return identifierFactory.newDatastoreContainerIdentifier(clr, mmd);
    }

    /**
     * Method to create the necessary datastore columns for a reference field.
     * @param m (Java) Mapping for the field
     * @param table The datastore container where the datastore columns will be created
     * @param fmd MetaData for the field/property
     * @param clr ClassLoader resolver
     * @param embedded Whether the field is embedded
     */
    public void createDatastoreColumnsForReferenceField(JavaTypeMapping m, DatastoreContainerObject table,
            AbstractMemberMetaData fmd, ClassLoaderResolver clr, boolean embedded)
    {
        ColumnCreator.createColumnsForFieldUsingReference(m, table, fmd, clr, embedded);
    }

    /**
     * Method to create the necessary datastore columns for a field using subclass-table inheritance.
     * @param m (Java) Mapping for the field
     * @param table The datastore container where the datastore columns will be created
     * @param fmd MetaData for the field/property
     * @param clr ClassLoader resolver
     */
    public void createDatastoreColumnsForFieldUsingSubclassTable(JavaTypeMapping m,
        DatastoreContainerObject table, AbstractMemberMetaData fmd, ClassLoaderResolver clr)
    {
        ColumnCreator.createColumnsForFieldUsingSubclassTable(m, table, fmd, clr);
    }

    /**
     * Method to output the datastore information to the specified PrintStream.
     * @param ps PrintStream
     * @throws Exception If an error occurs in obtaining the schema information
     */
    public void outputDatastoreInformation(PrintStream ps)
    throws Exception
    {
        RDBMSAdapter dba = (RDBMSAdapter) getDatastoreAdapter();

        // Print out the DBA
        String msg = dba.toString();
        JPOXLogger.SCHEMATOOL.info(msg);
        ps.println(msg);
        ps.println();

        // Print out the type info
        msg = "Database TypeInfo";
        JPOXLogger.SCHEMATOOL.info(msg);
        ps.println(msg);

        Iterator typeInfosIter = dba.iteratorTypeInfo();
        while (typeInfosIter.hasNext())
        {
            JDBCTypeInfo jti = (JDBCTypeInfo)typeInfosIter.next();
            String typeStr = "JDBC Type=" + JDBCUtils.getNameForJDBCType(jti.getJdbcType()) +
                " datastoreTypes=" + StringUtils.objectArrayToString(jti.getTypeNames());
            JPOXLogger.SCHEMATOOL.info(typeStr);
            ps.println(typeStr);
            JPOXLogger.SCHEMATOOL.info(jti.getDefault());
            ps.println(jti.getDefault());
        }
        ps.println("");

        // Print out the keywords info
        msg = "Database Keywords";
        JPOXLogger.SCHEMATOOL.info(msg);
        ps.println(msg);

        Iterator reservedWordsIter = dba.iteratorReservedWords();
        while (reservedWordsIter.hasNext())
        {
            Object words = reservedWordsIter.next();
            JPOXLogger.SCHEMATOOL.info(words);
            ps.println(words);
        }
        ps.println("");
    }

    /**
     * Method to output the schema information to the specified PrintStream.
     * @param ps PrintStream
     * @throws Exception If an error occurs in obtaining the schema information
     */
    public void outputSchemaInformation(PrintStream ps)
    throws Exception
    {
        RDBMSAdapter dba = (RDBMSAdapter) getDatastoreAdapter();

        // Print out the DBA
        String msg = dba.toString();
        JPOXLogger.SCHEMATOOL.info(msg);
        ps.println(msg);
        ps.println();

        JPOXLogger.SCHEMATOOL.info("TABLES");
        Iterator itTables = getTableInfo(null).iterator();
        while (itTables.hasNext())
        {
            TableInfo ti = (TableInfo) itTables.next();
            JPOXLogger.SCHEMATOOL.info(ti);
            ps.println(ti);
            Iterator itColumns = getColumnInfo(ti.getTableSchem(), ti.getTableName()).iterator();
            while (itColumns.hasNext())
            {
                ColumnInfo ci = (ColumnInfo) itColumns.next();
                JPOXLogger.SCHEMATOOL.info(ci);
                ps.println(ci);
            }
        }
        ps.println("");
    }

    // ============================ Inner classes ==============================

    /**
     * An abstract base class for RDBMSManager transactions that perform some
     * management function on the database.
     * <p>
     * Management transactions may be retried in the face of SQL exceptions to
     * work around failures caused by transient conditions, such as DB
     * deadlocks.
     * </p>
     */
    private abstract class MgmtTransaction
    {
        protected final int isolationLevel;
        protected final int maxRetries;
        protected ManagedConnection mconn;
        private Connection conn;

        /**
         * Constructs a new management transaction having the given isolation
         * level.
         * @param isolationLevel One of the isolation level constants from
         * java.sql.Connection.
         */
        private MgmtTransaction(int isolationLevel)
        {
            this.isolationLevel = isolationLevel;
            maxRetries = omfContext.getPersistenceConfiguration().getIntProperty("org.jpox.rdbms.classAdditionMaxRetries");
        }

        /**
         * Returns a description of the management transaction. Subclasses
         * should override this method so that transaction failures are given an
         * appropriate exception message.
         *
         * @return A description of the management transaction.
         */
        public abstract String toString();

        /**
         * Implements the body of the transaction.
         * @param clr the ClassLoaderResolver
         * @exception SQLException Thrown if the transaction fails due to a
         * database error that should allow the entire transaction to be
         * retried.
         */
        protected abstract void run(ClassLoaderResolver clr)
        throws SQLException;
       
        /**
         * Obtains the current connection for the transaction. Creates
         * one if needed
         * @return the connection
         */
        protected Connection getCurrentConnection() throws SQLException
        {
            if (conn == null)
            {
                mconn = getConnection(isolationLevel);
                conn = (Connection) mconn.getConnection();
            }
            return conn;
        }
       
        /**
         * Executes the transaction.
         * <p>
         * A database connection is acquired and the
         * {@link #execute(ClassLoaderResolver)}method is invoked.
         * If the selected isolation level is not Connection.TRANSACTION_NONE,
         * then commit() or rollback() is called on the connection according to
         * whether the invocation succeeded or not. If the invocation failed the
         * sequence is repeated, up to a maximum of <var>maxRetries </var>
         * times, configurable by the system property org.jpox.store.maxRetries.
         * @param clr the ClassLoaderResolver
         * @exception JPOXDataStoreException If a SQL exception occurred even after "maxRetries" attempts.
         */
        // This code below was added in revision 1.347 but broke TCK tests and was rolled back
        // What exactly the change was about I dont know. Left here in case it is of use and needs reinstating (but fixed)
        /*public final void execute(ClassLoaderResolver clr)
        {
            int attempts = 0;
            for (;;)
            {
                try
                {
                    try
                    {
                        run(clr);
                    }
                    finally
                    {
                        if (mconn != null)
                        {
                            mconn.close();

                            conn = null;
                        }
                    }
                    break;
                }
                catch (SQLException e)
                {
                    if (++attempts >= maxRetries)
                    {
                        throw new JPOXDataStoreException("SQL exception: " + this, e);
                    }
                }
            }
        }*/
        public final void execute(ClassLoaderResolver clr)
        {
            int attempts = 0;
            for (;;)
            {
                try
                {
                    try
                    {
                        boolean succeeded = false;
                        try
                        {
                            run(clr);
                            succeeded = true;
                        }
                        finally
                        {
                            if (conn != null)
                            {
                                if (isolationLevel != UserTransaction.TRANSACTION_NONE)
                                {
                                    if (!conn.getAutoCommit())
                                    {
                                        if (succeeded)
                                        {
                                            conn.commit();
                                        }
                                        else
                                        {
                                            conn.rollback();
                                        }
                                    }
                                }
                            }
                        }
                    }
                    finally
                    {
                        if (conn != null)
                        {
                            mconn.close();

                            conn = null;
                        }
                    }
                    break;
                }
                catch (SQLException e)
                {
                    if (++attempts >= maxRetries)
                    {
                        throw new JPOXDataStoreException("SQL exception: " + this, e);
                    }
                }
            }
        }
    }

    /**
     * A management transaction that adds a set of classes to the RDBMSManager,
     * making them usable for persistence.
     * <p>
     * This class embodies the work necessary to activate a persistent class and
     * ready it for storage management. It is the primary mutator of a
     * RDBMSManager.
     * </p>
     * <p>
     * Adding classes is an involved process that includes the creation and/or
     * validation in the database of tables, views, and table constraints, and
     * their corresponding Java objects maintained by the RDBMSManager. Since
     * it's a management transaction, the entire process is subject to retry on
     * SQL exceptions. It is responsible for ensuring that the procedure either
     * adds <i>all </i> of the requested classes successfully, or adds none of
     * them and preserves the previous state of the RDBMSManager exactly as it
     * was.
     * </p>
     */
    private class ClassAdder extends MgmtTransaction
    {
        /** join table for Collection. **/
        public static final int JOIN_TABLE_COLLECTION = 1;
        /** join table for Map. **/
        public static final int JOIN_TABLE_MAP = 2;
        /** join table for Array. **/
        public static final int JOIN_TABLE_ARRAY = 3;

        /** Optional writer to dump the DDL for any classes being added. */
        private Writer ddlWriter = null;

        /** tracks the SchemaData currrently being added - used to rollback the AutoStart added classes **/
        private HashSet schemaDataAdded = new HashSet();
        private final String[] classNames;

        /**
         * Constructs a new class adder transaction that will add the given classes to the RDBMSManager.
         * @param classNames Names of the (initial) class(es) to be added.
         * @param writer Optional writer for DDL when we want the DDL outputting to file instead of creating the tables
         */
        private ClassAdder(String[] classNames, Writer writer)
        {
            super(((RDBMSAdapter)dba).getTransactionIsolationForSchemaCreation());
            this.ddlWriter = writer;
            this.classNames = ClassUtils.getUnsupportedClassNames(getOMFContext().getTypeManager(), classNames);
        }

        /**
         * Method to give a string version of this object.
         * @return The String version of this object.
         */
        public String toString()
        {
            return LOCALISER_RDBMS.msg("050038", catalogName, schemaName);
        }

        /**
         * Method to perform the action using the specified connection to the datastore.
         * @param clr the ClassLoaderResolver
         * @throws SQLException Thrown if an error occurs in execution.
         */
        protected void run(ClassLoaderResolver clr)
        throws SQLException
        {
            if (classNames.length == 0)
            {
                return;
            }

            synchronized (CLASSADDER_MUTEX)
            {
                classAdder = this;
                try
                {
                    addClassTablesAndValidate(classNames, clr);
                }
                finally
                {
                    classAdder = null;
                }
            }
        }

        /**
         * Called by RDBMSManager.addClasses() when it has been recursively
         * re-entered. This just adds table objects for the requested additional
         * classes and returns.
         * @param classNames Names of the (additional) class(es) to be added.
         * @param clr the ClassLoaderResolver
         */
        private void addClasses(String[] classNames, ClassLoaderResolver clr)
        {
            // Filter out any supported classes
            classNames = ClassUtils.getUnsupportedClassNames(getOMFContext().getTypeManager(), classNames);
            if (classNames.length == 0)
            {
                return;
            }

            try
            {
                if (getCurrentConnection() == null)
                {
                    throw new IllegalStateException(LOCALISER_RDBMS.msg("050039"));
                }
            }
            catch(SQLException e)
            {
                throw new JPOXDataStoreException("SQL exception: " + this, e);
            }

            // Add the tables for these additional classes
            addClassTables(classNames, clr);
        }
       
        private int addClassTablesRecursionCounter = 0;

        /**
         * Adds a new table object (ie ClassTable or ClassView) for every
         * class in the given list. These classes
         * <ol>
         * <li>require a table</li>
         * <li>do not yet have a table initialized in the store manager.</li>
         * </ol>
         * <p>
         * This doesn't initialize or validate the tables, it just adds the
         * table objects to the RDBMSManager's internal data structures.
         * </p>
         *
         * @param classNames Names of class(es) whose tables are to be added.
         * @param clr the ClassLoaderResolver
         */
        private void addClassTables(String[] classNames, ClassLoaderResolver clr)
        {
            addClassTablesRecursionCounter += 1;
            try
            {
                Iterator iter = getReferencedClasses(classNames, clr).iterator();
                try
                {
                    if (starter != null && starterInitialised && !starter.isOpen())
                    {
                        starter.open();
                    }

                    // Pass through the classes and create necessary tables
                    while (iter.hasNext())
                    {
                        ClassMetaData cmd = (ClassMetaData) iter.next();
                        addClassTable(cmd, clr);
                    }

                    // For data where the table wasn't defined, make a second pass.
                    // This is necessary where a subclass uses "superclass-table" and the superclass' table
                    // hadn't been defined at the point of adding this class
                    Iterator addedIter = new HashSet(this.schemaDataAdded).iterator();
                    while (addedIter.hasNext())
                    {
                        RDBMSStoreData data = (RDBMSStoreData) addedIter.next();
                        if (data.getDatastoreContainerObject() == null && data.isFCO())
                        {
                            AbstractClassMetaData cmd = (AbstractClassMetaData) data.getMetaData();
                            InheritanceMetaData imd = cmd.getInheritanceMetaData();
                            if (imd.getStrategyValue() == InheritanceStrategy.SUPERCLASS_TABLE)
                            {
                                AbstractClassMetaData[] managingCmds = getClassesManagingTableForClass(cmd, clr);
                                DatastoreClass superTable = null;
                                if (managingCmds != null && managingCmds.length == 1)
                                {
                                    RDBMSStoreData superData =
                                        (RDBMSStoreData) storeDataMgr.get(managingCmds[0].getFullClassName());

                                    // Assert that managing class is in the set of storeDataByClass
                                    if (superData == null)
                                    {
                                        this.addClassTables(new String[]{managingCmds[0].getFullClassName()}, clr);
                                        superData = (RDBMSStoreData) storeDataMgr.get(managingCmds[0].getFullClassName());
                                    }
                                    if (superData == null)
                                    {
                                        String msg = LOCALISER_RDBMS.msg("050013",
                                            cmd.getFullClassName());
                                        JPOXLogger.PERSISTENCE.error(msg);
                                        throw new JPOXUserException(msg);
                                    }
                                    superTable = (DatastoreClass) superData.getDatastoreContainerObject();
                                    data.setDatastoreContainerObject(superTable);
                                }
                            }
                        }
                    }
                }
                finally
                {
                    if (starter != null && starterInitialised && starter.isOpen() && addClassTablesRecursionCounter <= 1)
                    {
                        starter.close();
                    }
                }
            }
            finally
            {
                addClassTablesRecursionCounter -= 1;
            }
        }

        /**
         * Method to add a new table object (ie ClassTable or ClassView).
         * Doesn't initialize or validate the tables, just adding the table objects to the internal data structures.
         * @param cmd the ClassMetaData
         * @param clr the ClassLoaderResolver
         */
        private void addClassTable(ClassMetaData cmd, ClassLoaderResolver clr)
        {
            // Only add tables for "PERSISTENCE_CAPABLE" classes
            if (cmd.getPersistenceModifier() != ClassPersistenceModifier.PERSISTENCE_CAPABLE)
            {
                return;
            }
            if (cmd.getIdentityType() == IdentityType.NONDURABLE)
            {
                if (cmd.hasExtension("requires-table") && cmd.getValueForExtension("requires-table") != null &&
                    cmd.getValueForExtension("requires-table").equalsIgnoreCase("false"))
                {
                    return;
                }
            }
            RDBMSStoreData sd = (RDBMSStoreData) storeDataMgr.get(cmd.getFullClassName());
            if (sd == null)
            {
                // For application-identity classes with user-defined identities we check for use of the
                // objectid-class in different inheritance trees. We prevent this to avoid problems later on.
                // The builtin objectid-classes are allowed to be duplicated.
                if (cmd.getIdentityType() == IdentityType.APPLICATION)
                {
                    if (!cmd.usesSingleFieldIdentityClass())
                    {
                        // Check whether this class has the same base persistable class as the others using the PK.
                        // If not, then throw an error
                        String baseClassWithMetaData = cmd.getBaseAbstractClassMetaData().getFullClassName();
                        Collection c = storeDataMgr.getByPrimaryKeyClass(cmd.getObjectidClass());
                        if (c != null && c.size() > 0)
                        {
                            // We already have at least 1 class using the same app id PK class
                            // so check if it is has the same persistable root class.
                            boolean in_same_tree = false;
                            String sample_class_in_other_tree = null;

                            Iterator id_iter = c.iterator();
                            while (id_iter.hasNext())
                            {
                                RDBMSStoreData id_sd = (RDBMSStoreData) id_iter.next();
                                String otherClassBaseClass =
                                    ((AbstractClassMetaData) id_sd.getMetaData()).getBaseAbstractClassMetaData().getFullClassName();
                                if (otherClassBaseClass.equals(baseClassWithMetaData))
                                {
                                    in_same_tree = true;
                                    break;
                                }
                                sample_class_in_other_tree = id_sd.getName();
                            }

                            if (!in_same_tree)
                            {
                                String error_msg = LOCALISER_RDBMS.msg("050021", cmd.getFullClassName(),
                                    cmd.getObjectidClass(), sample_class_in_other_tree);
                                JPOXLogger.DATASTORE.error(error_msg);
                                throw new JPOXUserException(error_msg);
                            }
                        }
                    }
                }

                if (cmd.isEmbeddedOnly())
                {
                    // Nothing to do. Only persisted as SCO.
                    JPOXLogger.DATASTORE.info(LOCALISER.msg("032012", cmd.getFullClassName()));
                }
                else
                {
                    InheritanceMetaData imd = cmd.getInheritanceMetaData();
                    RDBMSStoreData sdNew = null;
                    if (imd.getStrategyValue() == InheritanceStrategy.SUBCLASS_TABLE)
                    {
                        // Table mapped into the table(s) of subclass(es)
                        // Just add the SchemaData entry with no table - managed by subclass
                        sdNew = new RDBMSStoreData(cmd, null, false);
                        registerStoreData(sdNew);
                    }
                    else if (imd.getStrategyValue() == InheritanceStrategy.COMPLETE_TABLE &&
                            cmd.isAbstractPersistenceCapable())
                    {
                        // Abstract class with "complete-table" so gets no table
                        sdNew = new RDBMSStoreData(cmd, null, false);
                        registerStoreData(sdNew);
                    }
                    else if (imd.getStrategyValue() == InheritanceStrategy.NEW_TABLE ||
                             imd.getStrategyValue() == InheritanceStrategy.COMPLETE_TABLE)
                    {
                        // Table managed by this class
                        // Generate an identifier for the table required
                        DatastoreIdentifier tableName = getTableIdentifier(cmd, clr);

                        // Check that the required table isn't already in use
                        StoreData[] existingStoreData = getStoreDataForDatastoreContainerObject(tableName);
                        if (existingStoreData != null)
                        {
                            String existingClass = null;
                            for (int j=0;j<existingStoreData.length;j++)
                            {
                                if (!existingStoreData[j].getName().equals(cmd.getFullClassName()))
                                {
                                    existingClass = existingStoreData[j].getName();
                                    break;
                                }
                            }
                            // Give a warning and then create a new instance of the table (mapped to the same datastore object)
                            if (existingClass != null)
                            {
                                String msg = LOCALISER_RDBMS.msg("050015", cmd.getFullClassName(),
                                    tableName.getIdentifier(), existingClass);
                                JPOXLogger.DATASTORE.warn(msg);
                            }
                        }

                        // Create the table to use for this class
                        DatastoreClass t = null;
                        boolean hasViewDef = false;
                        if (dba.getVendorID() != null)
                        {
                            hasViewDef = cmd.hasExtension("view-definition" + '-' + dba.getVendorID());
                        }
                        if (!hasViewDef)
                        {
                            hasViewDef = cmd.hasExtension("view-definition");
                        }
                        if (hasViewDef)
                        {
                            t = new ClassView(tableName, RDBMSManager.this, cmd);
                        }
                        else
                        {
                            t = new ClassTable(tableName, RDBMSManager.this, cmd);
                        }

                        sdNew = new RDBMSStoreData(cmd, t, true);
                        registerStoreData(sdNew);

                        // must be initialized after registering, to avoid StackOverflowError
                        ((Table) t).preInitialize(clr);
                    }
                    else if (imd.getStrategyValue() == InheritanceStrategy.SUPERCLASS_TABLE)
                    {
                        // Table mapped into table of superclass
                        // Find the superclass - should have been created first
                        AbstractClassMetaData[] managingCmds = getClassesManagingTableForClass(cmd, clr);
                        DatastoreContainerObject superTable = null;
                        if (managingCmds != null && managingCmds.length == 1)
                        {
                            RDBMSStoreData superData = (RDBMSStoreData) storeDataMgr.get(managingCmds[0].getFullClassName());
                            if (superData != null)
                            {
                                // Specify the table if it already exists
                                superTable = superData.getDatastoreContainerObject();
                            }
                            sdNew = new RDBMSStoreData(cmd, superTable, false);
                            registerStoreData(sdNew);
                        }
                        else
                        {
                            String msg = LOCALISER_RDBMS.msg("050013", cmd.getFullClassName());
                            JPOXLogger.PERSISTENCE.error(msg);
                            throw new JPOXUserException(msg);
                        }
                    }
                    schemaDataAdded.add(sdNew);
                }
            }
        }

        /**
         * Returns a List of {@link ClassMetaData}objects representing the set
         * of classes consisting of the requested classes plus any and all other
         * classes they may reference, directly or indirectly. The returned list
         * is ordered by dependency.
         * @param classNames Array of names of persistence-capable classes.
         * @param clr the ClassLoaderResolver
         * @return A List of <tt>ClassMetaData</tt> objects.
         * @exception org.jpox.exceptions.ClassNotPersistableException If any of the given classes is not persistence-capable.
         * @exception NoPersistenceInformationException If metadata/annotations for the classes can't be reached in the classpath.
         */
        private List getReferencedClasses(String[] classNames,
                ClassLoaderResolver clr)
        {
            List cmds = new ArrayList();
            for (int i = 0; i < classNames.length; ++i)
            {
                Class clazz = clr.classForName(classNames[i]);
                if (clazz == null || !clazz.isInterface())
                {
                    AbstractClassMetaData cmd = getMetaDataManager().getMetaDataForClass(classNames[i], clr);
                    if (cmd == null)
                    {
                        JPOXLogger.DATASTORE_SCHEMA.warn(LOCALISER_RDBMS.msg("050042", classNames[i]));
                        throw new NoPersistenceInformationException(classNames[i]);
                    }
                    cmds.addAll(getMetaDataManager().getReferencedClassMetaData(cmd, dba.getVendorID(), clr));
                }
            }
            return cmds;
        }

        /**
         * Adds a new table object (ie ClassTable or ClassView) for every class
         * in the given list that 1) requires an extent and 2) does not yet have
         * an extent (ie table) initialized in the store manager.
         * <p>
         * After all of the table objects, including any other tables they might
         * reference, have been added, each table is initialized and validated
         * in the database.
         * </p>
         * <p>
         * If any error occurs along the way, any table(s) that were created are
         * dropped and the state of the RDBMSManager is rolled back to the point
         * at which this method was called.
         * </p>
         * @param classNames The class(es) whose tables are to be added.
         * @param clr the ClassLoaderResolver
         */
        private void addClassTablesAndValidate(String[] classNames, ClassLoaderResolver clr)
        {
            storeDataMgr.begin();
            boolean completed = false;

            List tablesCreated = null;
            List tableConstraintsCreated = null;
            List viewsCreated = null;
           
            try
            {
                List autoCreateErrors = new ArrayList();

                // Add SchemaData entries and Table's for the requested classes
                addClassTables(classNames, clr);

                // Initialise all tables/views for the classes
                List[] toValidate = initializeClassTables(classNames, clr);

                if (toValidate[0] != null && toValidate[0].size() > 0)
                {
                    // Validate the tables
                    List[] result = performTablesValidation(toValidate[0], clr);
                    tablesCreated = result[0];
                    tableConstraintsCreated = result[1];
                    autoCreateErrors = result[2];
                }

                if (toValidate[1] != null && toValidate[1].size() > 0)
                {
                    // Validate the views
                    List[] result = performViewsValidation(toValidate[1]);
                    viewsCreated = result[0];
                    autoCreateErrors.addAll(result[1]);
                }

                // Process all errors from the above
                verifyErrors(autoCreateErrors);

                completed = true;
            }
            catch (SQLException sqle)
            {
                String msg = LOCALISER_RDBMS.msg("050044", sqle);
                JPOXLogger.DATASTORE_SCHEMA.error(msg);
                throw new JPOXDataStoreException(msg, sqle);
            }
            catch (Exception e)
            {
                if (JPOXException.class.isAssignableFrom(e.getClass()))
                {
                    throw (JPOXException)e;
                }
                else
                {
                    JPOXLogger.DATASTORE_SCHEMA.error(LOCALISER_RDBMS.msg("050044", e));
                }
                throw new JPOXException(e.toString(), e).setFatal();
            }
            finally
            {
                // If something went wrong, roll things back to the way they were before we started.
                // This may not restore the database 100% of the time (if DDL statements are not transactional)
                // but it will always put the RDBMSManager's internal structures back the way they were.
                if (!completed)
                {
                    storeDataMgr.rollback();
                    rollbackSchemaCreation(viewsCreated,tableConstraintsCreated,tablesCreated);
                }
                else
                {
                    storeDataMgr.commit();
                }
                schemaDataAdded.clear();
            }
        }

        /**
         * Initialisation of tables. Updates the internal representation of the table to match what is
         * required for the class(es). Each time a table object is initialized, it may cause other associated
         * table objects to be added (via callbacks to addClasses()) so the loop is repeated until no more
         * initialisation is needed.
         * @param classNames String array of class names
         * @param clr the ClassLoaderResolver
         * @return an array of List where index == 0 is list of the tables created, index == 1 is list of the views created
         */
        private List[] initializeClassTables(String[] classNames, ClassLoaderResolver clr)
        {
            List tablesToValidate = new ArrayList();
            List viewsToValidate = new ArrayList();
            boolean someNeededInitialization;
            List recentlyInitilized = new ArrayList();
            do
            {
                someNeededInitialization = false;
                RDBMSStoreData[] rdbmsStoreData = (RDBMSStoreData[]) storeDataMgr.getManagedStoreData().toArray(new RDBMSStoreData[storeDataMgr.size()]);
                for (int i=0; i<rdbmsStoreData.length; i++)
                {
                    if (rdbmsStoreData[i].hasTable())
                    {
                        Table t = (Table)rdbmsStoreData[i].getDatastoreContainerObject();
                        if (t instanceof DatastoreClass)
                        {
                            ((RDBMSPersistenceHandler)persistenceHandler).removeRequestsForTable((DatastoreClass)t);
                        }

                        // Any classes managed by their own table needing initialising
                        if (!t.isInitialized())
                        {
                            t.initialize(clr);
                            recentlyInitilized.add(t);
                            if (t instanceof ViewImpl)
                            {
                                viewsToValidate.add(t);
                            }
                            else
                            {
                                tablesToValidate.add(t);
                            }
                            someNeededInitialization = true;
                        }

                        // Any classes that are managed by other tables needing initialising
                        if (!rdbmsStoreData[i].isTableOwner() && !((ClassTable)t).managesClass(rdbmsStoreData[i].getName()))
                        {
                            ((ClassTable)t).manageClass((ClassMetaData)rdbmsStoreData[i].getMetaData(), clr);
                            if (!tablesToValidate.contains(t))
                            {
                                tablesToValidate.add(t);
                            }
                            someNeededInitialization = true;
                        }
                    }
                    else
                    {
                        // Nothing to do for cases without their own table ("subclass-table" strategy) since
                        // the table is initialised to contain those fields by the subclass.
                    }
                }
            }
            while (someNeededInitialization);

            // Post initialisation of tables
            for (int j=0; j<recentlyInitilized.size(); j++)
            {
                ((Table)recentlyInitilized.get(j)).postInitialize(clr);
            }

            return new List[] { tablesToValidate, viewsToValidate };
        }

        /**
         * Validate tables.
         * @param tablesToValidate list of TableImpl to validate
         * @param clr the ClassLoaderResolver
         * @return an array of List where index == 0 has a list of the tables created
         *                                index == 1 has a list of the contraints created
         *                                index == 2 has a list of the auto creation errors
         * @throws SQLException
         */
        private List[] performTablesValidation(List tablesToValidate, ClassLoaderResolver clr) throws SQLException
        {
            List autoCreateErrors = new ArrayList();
            List tableConstraintsCreated = new ArrayList();
            List tablesCreated = new ArrayList();

            if (ddlWriter != null)
            {
                // Remove any existence of the same actual table more than once so we dont duplicate its
                // DDL for creation. Note that this will allow more than once instance of tables with the same
                // name (identifier) since when you have multiple inheritance trees each inheritance tree
                // will have its own ClassTable, and you want both of these to pass through to schema generation.
                tablesToValidate = removeDuplicateTablesFromList(tablesToValidate);
            }

            // Table existence and validation.
            // a). Check for existence of the table
            // b). If autocreate, create the table if necessary
            // c). If validate, validate the table
            Iterator i = tablesToValidate.iterator();
            while (i.hasNext())
            {
                TableImpl t = (TableImpl) i.next();

                boolean columnsValidated = false;
                if (checkExistTablesOrViews)
                {
                    if (ddlWriter != null)
                    {
                        try
                        {
                            if (t instanceof ClassTable)
                            {
                                ddlWriter.write("-- Table " + t.toString() +
                                    " for classes " + StringUtils.objectArrayToString(((ClassTable)t).getManagedClasses()) + "\n");
                            }
                            else if (t instanceof JoinTable)
                            {
                                ddlWriter.write("-- Table " + t.toString() + " for join relationship\n");
                            }
                        }
                        catch (IOException ioe)
                        {
                            JPOXLogger.DATASTORE_SCHEMA.error("error writing DDL into file", ioe);
                        }
                    }

                    if (!tablesCreated.contains(t) && t.exists(getCurrentConnection(), autoCreateTables))
                    {
                        // Table has been created so add to our list so we dont process it multiple times
                        // Any subsequent instance of this table in the list will have the columns checked only
                        tablesCreated.add(t);
                        columnsValidated = true;
                    }
                    else
                    {
                        // Table wasn't just created, so do any autocreate of columns necessary
                        if (t.isInitializedModified() || autoCreateColumns)
                        {
                            // Check for existence of the required columns and add where required
                            t.validateColumns(getCurrentConnection(), false, autoCreateColumns, autoCreateErrors);
                            columnsValidated = true;
                        }
                    }
                }

                if (validateTables && !columnsValidated) // Table not just created and validation requested
                {
                    // Check down to the column structure where required
                    t.validate(getCurrentConnection(), validateColumns, false, autoCreateErrors);
                }
                else if (!columnsValidated)
                {
                    // Validation not requested but allow initialisation of the column information
                    String initInfo = omfContext.getPersistenceConfiguration().getStringProperty("org.jpox.rdbms.initializeColumnInfo");
                    if (initInfo.equalsIgnoreCase("PK"))
                    {
                        // Initialise the PK columns only
                        t.initializeColumnInfoForPrimaryKeyColumns(getCurrentConnection());
                    }
                    else if (initInfo.equalsIgnoreCase("ALL"))
                    {
                        // Initialise all columns
                        t.initializeColumnInfoFromDatastore(getCurrentConnection());
                    }
                }

                // Discard any cached column info used to validate the table
                columnInfoByTableName.remove(t.getDatastoreIdentifierFullyQualified());
            }

            // Table constraint existence and validation
            // a). Check for existence of the constraint
            // b). If autocreate, create the constraint if necessary
            // c). If validate, validate the constraint
            // Constraint processing is done as a separate step from table processing
            // since the constraints are dependent on tables being available
            i = tablesToValidate.iterator();
            while (i.hasNext())
            {
                TableImpl t = (TableImpl) i.next();
                if (validateConstraints || autoCreateConstraints)
                {
                    if (ddlWriter != null)
                    {
                        try
                        {
                            if (t instanceof ClassTable)
                            {
                                ddlWriter.write("-- Constraints for table " + t.toString() +
                                    " for class(es) " + StringUtils.objectArrayToString(((ClassTable)t).getManagedClasses()) + "\n");
                            }
                            else
                            {
                                ddlWriter.write("-- Constraints for table " + t.toString() + "\n");
                            }
                        }
                        catch (IOException ioe)
                        {
                            JPOXLogger.DATASTORE_SCHEMA.error("error writing DDL into file", ioe);
                        }
                    }
                    // TODO : split this method into checkExistsConstraints and validateConstraints
                    // TODO : if duplicated entries on the list, we need to validate before.
                    if (tablesCreated.contains(t) && !hasDuplicateTablesFromList(tablesToValidate))
                    {
                        if (t.createConstraints(getCurrentConnection(), autoCreateErrors, clr))
                        {
                            tableConstraintsCreated.add(t);
                        }
                    }
                    else if (t.validateConstraints(getCurrentConnection(), autoCreateConstraints, autoCreateErrors, clr))
                    {
                        tableConstraintsCreated.add(t);
                    }
                    if (ddlWriter != null)
                    {
                        try
                        {
                            ddlWriter.write("\n");
                        }
                        catch (IOException ioe)
                        {
                            JPOXLogger.DATASTORE_SCHEMA.error("error writing DDL into file", ioe);
                        }
                    }
                }
            }
            return new List[] { tablesCreated, tableConstraintsCreated, autoCreateErrors };
        }

        /**
         * Remove duplicated tables from the list.
         * Tables are only removed if they are the same table object. That is we dont remove if they have
         * the same table identifier.
         * @param newTables the list of DatastoreContainerObject
         * @return a distinct list with DatastoreContainerObject 
         */
        private List removeDuplicateTablesFromList(List newTables)
        {
            List result = new ArrayList();
            Set uniqueTables = new TreeSet(new Comparator()
            {
                public int compare(Object o1, Object o2)
                {
                    DatastoreContainerObject t1 = (DatastoreContainerObject) o1;
                    DatastoreContainerObject t2 = (DatastoreContainerObject) o2;
                    return StringUtils.toJVMIDString(t1).compareTo(StringUtils.toJVMIDString(t2));
                }
            });
            uniqueTables.addAll(newTables);
            result.addAll(uniqueTables);
            return result;
        }

        /**
         * Check if duplicated tables are in the list.
         * @param newTables the list of DatastoreContainerObject
         * @return true if duplicated tables are in the list
         */
        private boolean hasDuplicateTablesFromList(List newTables)
        {
            Map map = new HashMap();
            for (int i=0; i<newTables.size(); i++)
            {
                DatastoreContainerObject t1 = (DatastoreContainerObject) newTables.get(i);
                if (map.containsKey(t1.getIdentifier().getIdentifier()))
                {
                    return true;
                }
                map.put(t1.getIdentifier().getIdentifier(), t1);
            }
            return false;
        }

        /**
         * Validate the supplied views.
         * @param viewsToValidate list of ViewImpl to validate
         * @return an array of List where index == 0 has a list of the views created
         *                                index == 1 has a list of the auto creation errors
         * @throws SQLException
         */
        private List[] performViewsValidation(List viewsToValidate) throws SQLException
        {
            List viewsCreated = new ArrayList();
            List autoCreateErrors = new ArrayList();
            // View existence and validation.
            // a). Check for existence of the view
            // b). If autocreate, create the view if necessary
            // c). If validate, validate the view
            Iterator i = viewsToValidate.iterator();
            while (i.hasNext())
            {
                ViewImpl v = (ViewImpl) i.next();
                if (checkExistTablesOrViews)
                {
                    if (v.exists(getCurrentConnection(), autoCreateTables))
                    {
                        viewsCreated.add(v);
                    }
                }
                if (validateTables)
                {
                    v.validate(getCurrentConnection(), true, false, autoCreateErrors);
                }

                // Discard any cached column info used to validate the view
                columnInfoByTableName.remove(v.getDatastoreIdentifierFullyQualified());
            }
            return new List[] { viewsCreated, autoCreateErrors };
        }

        /**
         * Verify the list of errors, log the errors and raise JPOXDataStoreException when fail on error is enabled.
         * @param autoCreateErrors the list of Throwables
         */
        private void verifyErrors(List autoCreateErrors)
        {
            if (autoCreateErrors.size() > 0)
            {
                // Print out all errors found during auto-creation/validation
                Iterator errorsIter = autoCreateErrors.iterator();
                while (errorsIter.hasNext())
                {
                    Throwable exc = (Throwable)errorsIter.next();
                    if (autoCreateWarnOnError)
                    {
                        JPOXLogger.DATASTORE.warn(LOCALISER_RDBMS.msg("050044", exc));
                    }
                    else
                    {
                        JPOXLogger.DATASTORE.error(LOCALISER_RDBMS.msg("050044", exc));
                    }
                }
                if (!autoCreateWarnOnError)
                {
                    throw new JPOXDataStoreException(LOCALISER_RDBMS.msg("050043"),
                        (Throwable[])autoCreateErrors.toArray(new Throwable[autoCreateErrors.size()]));
                }
            }
        }

        /**
         * Rollback / Compensate schema creation by dropping tables, views, constraints and
         * deleting entries in the auto start mechanism.
         * @param viewsCreated the views created that must be dropped
         * @param tableConstraintsCreated the constraints created that must be dropped
         * @param tablesCreated the tables created that must be dropped
         */
        private void rollbackSchemaCreation(List viewsCreated, List tableConstraintsCreated, List tablesCreated)
        {
            if (JPOXLogger.DATASTORE_SCHEMA.isDebugEnabled())
            {
                JPOXLogger.DATASTORE_SCHEMA.debug(LOCALISER_RDBMS.msg("050040"));
            }

            // Tables, table constraints, and views get removed in the reverse order from which they were created.
            try
            {
                if (viewsCreated != null)
                {
                    ListIterator li = viewsCreated.listIterator(viewsCreated.size());
                    while (li.hasPrevious())
                    {
                        ((ViewImpl) li.previous()).drop(getCurrentConnection());
                    }
                }
                if( tableConstraintsCreated != null)
                {
                    ListIterator li = tableConstraintsCreated.listIterator(tableConstraintsCreated.size());
                    while (li.hasPrevious())
                    {
                        ((TableImpl) li.previous()).dropConstraints(getCurrentConnection());
                    }
                }
                if (tablesCreated != null)
                {
                    ListIterator li = tablesCreated.listIterator(tablesCreated.size());
                    while (li.hasPrevious())
                    {
                        ((TableImpl) li.previous()).drop(getCurrentConnection());
                    }
                }
            }
            catch (Exception e)
            {
                JPOXLogger.DATASTORE_SCHEMA.warn(LOCALISER_RDBMS.msg(
                    "050041", e));
            }

            // AutoStarter - Remove all classes from the supported list that were added in this pass.
            if (starter != null && starterInitialised)
            {
                try
                {
                    if (!starter.isOpen())
                    {
                        starter.open();
                    }
                    Iterator schema_added_iter = schemaDataAdded.iterator();
                    while (schema_added_iter.hasNext())
                    {
                        RDBMSStoreData sd=(RDBMSStoreData)schema_added_iter.next();
                        starter.deleteClass(sd.getName());
                    }                           
                }
                finally
                {
                    if (starter.isOpen())
                    {
                        starter.close();
                    }
                }
            }
        }

        /**
         * Called by Mapping objects in the midst of RDBMSManager.addClasses()
         * to request the creation of a join table to hold a containers' contents.
         * @param fmd The field metadata describing the Array field.
         * @param type The type of the join table
         */
        private DatastoreContainerObject addJoinTableForContainer(AbstractMemberMetaData fmd, ClassLoaderResolver clr, int type)
        {
            DatastoreIdentifier tableName = getTableIdentifier(fmd, clr);
            DatastoreContainerObject join = null;
            if (type == JOIN_TABLE_COLLECTION)
            {
                join = new CollectionTable(tableName, fmd, RDBMSManager.this);
            }
            else if (type == JOIN_TABLE_MAP)
            {
                join = new MapTable(tableName, fmd, RDBMSManager.this);
            }
            else if (type == JOIN_TABLE_ARRAY)
            {
                join = new ArrayTable(tableName, fmd, RDBMSManager.this);
            }

            RDBMSStoreData data;
            try
            {
                if (starter != null && starterInitialised && !starter.isOpen())
                {
                    starter.open();
                }

                data = new RDBMSStoreData(fmd, join);
                registerStoreData(data);
            }
            finally
            {
                if (starter != null && starterInitialised && starter.isOpen())
                {
                    starter.close();
                }
            }

            schemaDataAdded.add(data);
            return join;
        }
    }
   
    /**
     * Accessor for the supported options in string form
     */
    public Collection getSupportedOptions()
    {
        Set set = new HashSet();
        set.add("ORM");
        set.add("ContainerQueueing");
        return set;
    }
   
}
TOP

Related Classes of org.jpox.store.rdbms.RDBMSManager$ClassAdder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.