/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 * 
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.sessions;

import java.util.*;
import java.io.*;
import oracle.toplink.essentials.descriptors.ClassDescriptor;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.platform.server.ServerPlatform;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.internal.identitymaps.*;
import oracle.toplink.essentials.internal.descriptors.*;
import oracle.toplink.essentials.internal.databaseaccess.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.sessions.*;
import oracle.toplink.essentials.internal.sequencing.Sequencing;
import oracle.toplink.essentials.logging.*;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.helper.ConcurrencyManager;
import oracle.toplink.essentials.internal.helper.IdentityHashtable;

/**
 * Implementation of oracle.toplink.essentials.sessions.Session
 * The public interface should be used by public API and testing, the implementation should be used internally.
 * @see oracle.toplink.essentials.sessions.Session
 *
 * <p>
 * <b>Purpose</b>: Define the interface and common protocol of a TopLink compliant session.
 * <p>
 * <b>Description</b>: The session is the primary interface into TopLink,
 * the application should do all of its reading and writing of objects through the session.
 * The session also manages transactions and units of work.  Normally the session
 * is passed and used by the application controler objects.  Controler objects normally
 * sit behind the GUI and perform the buiness processes required for the application,
 * they should perform all explict database access and database access should be avoided from
 * the domain object model.  Do not use a globally accessable session instance, doing so does
 * not allow for multiple sessions.  Multiple sessions may required when performing things like
 * data migration or multiple database access, as well the unit of work feature requires the usage
 * of multiple session instances.  Although session is abstract, any users of its subclasses
 * should only cast the variables to Session to allow usage of any of its subclasses.
 * <p>
 * <b>Responsibilities</b>:
 *    <ul>
 *    <li> Connecting/disconnecting.
 *    <li> Reading and writing objects.
 *    <li> Transaction and unit of work support.
 *    <li> Identity maps and caching.
 *    </ul>
 * @see DatabaseSession
 */
public abstract class AbstractSession implements oracle.toplink.essentials.sessions.Session, java.io.Serializable, java.lang.Cloneable {

    /** ExceptionHandler handles database exceptions. */
    transient protected ExceptionHandler exceptionHandler;

    /** IntegrityChecker catch all the descriptor Exceptions.  */
    transient protected IntegrityChecker integrityChecker;

    /** The project stores configuration information, such as the descriptors and login. */
    transient protected oracle.toplink.essentials.sessions.Project project;

    /** Ensure mutual exclusion of the session's transaction state across multiple threads.*/
    transient protected ConcurrencyManager transactionMutex;

    /** Manages the live object cache.*/
    protected oracle.toplink.essentials.internal.sessions.IdentityMapAccessor identityMapAccessor;

    /** If Transactions were externally started */
    protected boolean wasJTSTransactionInternallyStarted;

    /** The connection to the data store. */
    transient protected Accessor accessor;
    
    /** Allow the datasource platform to be cached. */
    transient protected Platform platform;

    /** Stores predefine reusable queries.*/
    transient protected Map queries;
    
    /** Stores predefined not yet parsed EJBQL queries.*/
    transient protected List ejbqlPlaceHolderQueries;

    /** Resolves referencial integrity on commits. */
    transient protected CommitManager commitManager;

    /** Tool that log performance information. */
    transient protected SessionProfiler profiler;

    /** Support being owned by a session broker. */
    transient protected AbstractSession broker;

    /** Used to identify a session when using the session broker. */
    protected String name;

    /** Keep track of active units of work. */
    transient protected int numberOfActiveUnitsOfWork;

    /** Destination for logged messages and SQL. */
    transient protected SessionLog sessionLog;

    /** When logging the name of the session is typed: class name + system hashcode. */
    transient protected String logSessionString;

    /** Stores the event listeners for this session. */
    transient protected SessionEventManager eventManager;

    /** Allow for user defined properties. */
    protected Map properties;

    /** Delegate that handles synchronizing a UnitOfWork with an external transaction. */
    transient protected ExternalTransactionController externalTransactionController;

    /** Last descriptor accessed, use to optimize descriptor lookup. */
    transient protected ClassDescriptor lastDescriptorAccessed;

    /** Used to determine If a session is in a profile or not */
    public boolean isInProfile;

    /**
     * INTERNAL:
     * Create and return a new session.
     * This should only be called if the database login information is not know at the time of creation.
     * Normally it is better to call the constructor that takes the login information as an argument
     * so that the session can initialize itself to the platform information given in the login.
     */
    protected AbstractSession() {
        this.name = "";
        initializeIdentityMapAccessor();
        // PERF - move to lazy init (3286091)
        this.numberOfActiveUnitsOfWork = 0;
    }

    /**
     * INTERNAL:
     * Create a blank session, used for proxy session.
     */
    protected AbstractSession(int nothing) {
    }

    /**
     * PUBLIC:
     * Create and return a new session.
     * By giving the login information on creation this allows the session to initialize itself
     * to the platform given in the login. This constructor does not return a connected session.
     * To connect the session to the database login() must be sent to it. The login(userName, password)
     * method may also be used to connect the session, this allows for the user name and password
     * to be given at login but for the other database information to be provided when the session is created.
     */
    public AbstractSession(Login login) {
        this(new oracle.toplink.essentials.sessions.Project(login));
    }

    /**
     * PUBLIC:
     * Create and return a new session.
     * This constructor does not return a connected session.
     * To connect the session to the database login() must be sent to it. The login(userName, password)
     * method may also be used to connect the session, this allows for the user name and password
     * to be given at login but for the other database information to be provided when the session is created.
     */
    public AbstractSession(oracle.toplink.essentials.sessions.Project project) {
        this();
        this.project = project;
        if (project.getDatasourceLogin() == null) {
            throw ValidationException.projectLoginIsNull(this);
        }
    }

    /**
     * INTERNAL:
     * Called by a sessions queries to obtain individual query ids.
     * CR #2698903
     */
    public long getNextQueryId() {
        return QueryCounter.getCount();
    }

    /**
     * INTERNAL:
     * Return a unit of work for this session not registered with the JTS transaction.
     */
    public UnitOfWorkImpl acquireNonSynchronizedUnitOfWork() {
        setNumberOfActiveUnitsOfWork(getNumberOfActiveUnitsOfWork() + 1);
        UnitOfWorkImpl unitOfWork = new UnitOfWorkImpl(this);
        if (shouldLog(SessionLog.FINER, SessionLog.TRANSACTION)) {
            log(SessionLog.FINER, SessionLog.TRANSACTION, "acquire_unit_of_work_with_argument", String.valueOf(System.identityHashCode(unitOfWork)));
        }
        return unitOfWork;
    }

    /**
     * PUBLIC:
     * Return a unit of work for this session.
     * The unit of work is an object level transaction that allows
     * a group of changes to be applied as a unit.
     *
     * @see UnitOfWork
     */
    public UnitOfWork acquireUnitOfWork() {
        UnitOfWorkImpl unitOfWork = acquireNonSynchronizedUnitOfWork();
        unitOfWork.registerWithTransactionIfRequired();

        return unitOfWork;
    }

    /**
     * PUBLIC:
     * Add an alias for the descriptor
     */
    public void addAlias(String alias, ClassDescriptor descriptor) {
        project.addAlias(alias, descriptor);
    }

    /**
     * PUBLIC:
     * Add the query to the session queries with the given name.
     * This allows for common queries to be pre-defined, reused and executed by name.
     */
    public void addQuery(String name, DatabaseQuery query) {
        query.setName(name);
        addQuery(query);
    }

    /**
     * INTERNAL:
     * Return all pre-defined not yet parsed EJBQL queries.
     * @see #getAllQueries()
     */
    public void addEjbqlPlaceHolderQuery(DatabaseQuery query) {
        getEjbqlPlaceHolderQueries().add(query);
    }

    /**
     * INTERNAL:
     * Add the query to the session queries.
     */
    protected void addQuery(DatabaseQuery query) {
        Vector queriesByName = (Vector)getQueries().get(query.getName());
        if (queriesByName == null) {
            // lazily create Vector in Hashtable.
            queriesByName = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
            getQueries().put(query.getName(), queriesByName);
        }

        // Check that we do not already have a query that matched it
        for (Enumeration enumtr = queriesByName.elements(); enumtr.hasMoreElements();) {
            DatabaseQuery existingQuery = (DatabaseQuery)enumtr.nextElement();
            if (Helper.areTypesAssignable(query.getArgumentTypes(), existingQuery.getArgumentTypes())) {
                throw ValidationException.existingQueryTypeConflict(query, existingQuery);
            }
        }
        queriesByName.add(query);
    }

    /**
     * INTERNAL:
     * Called by beginTransaction() to start a transaction.
     * This starts a real database transaction.
     */
    protected void basicBeginTransaction() throws DatabaseException {
        try {
            getAccessor().beginTransaction(this);
        } catch (RuntimeException exception) {
            handleException(exception);
        }
    }

    /**
     * INTERNAL:
     * Called after transaction is completed (committed or rolled back)
     */
    public void afterTransaction(boolean committed, boolean isExternalTransaction) {
    }

    /**
     * INTERNAL:
     * Called by commitTransaction() to commit a transaction.
     * This commits the active transaction.
     */
    protected void basicCommitTransaction() throws DatabaseException {
        try {
            getAccessor().commitTransaction(this);
        } catch (RuntimeException exception) {
            handleException(exception);
        }
    }

    /**
     * INTERNAL:
     * Called by rollbackTransaction() to rollback a transaction.
     * This rollsback the active transaction.
     */
    protected void basicRollbackTransaction() throws DatabaseException {
        try {
            getAccessor().rollbackTransaction(this);
        } catch (RuntimeException exception) {
            handleException(exception);
        }
    }

    /**
     * INTERNAL:
     * Attempts to begin an external transaction.
     * Returns true only in one case -
     * extenal transaction has been internally started during this method call:
     * wasJTSTransactionInternallyStarted()==false in the beginning of this method and
     * wasJTSTransactionInternallyStarted()==true in the end of this method.
     */
    public boolean beginExternalTransaction() {
        boolean externalTransactionHasBegun = false;
        if (hasExternalTransactionController() && !wasJTSTransactionInternallyStarted()) {
            try {
                getExternalTransactionController().beginTransaction(this);
            } catch (RuntimeException exception) {
                handleException(exception);
            }
            if (wasJTSTransactionInternallyStarted()) {
                externalTransactionHasBegun = true;
                log(SessionLog.FINER, SessionLog.TRANSACTION, "external_transaction_has_begun_internally");
            }
        }
        return externalTransactionHasBegun;
    }

    /**
     * PUBLIC:
     * Begin a transaction on the database.
     * This allows a group of database modification to be commited or rolledback as a unit.
     * All writes/deletes will be sent to the database be will not be visible to other users until commit.
     * Although databases do not allow nested transaction,
     * TopLink supports nesting through only committing to the database on the outer commit.
     *
     * @exception DatabaseException if the database connection is lost or the begin is rejected.
     * @exception ConcurrencyException if this session's transaction is aquired by another thread and a timeout occurs.
     *
     * @see #isInTransaction()
     */
    public void beginTransaction() throws DatabaseException, ConcurrencyException {
        // If there is no db transaction in progress
        // beginExternalTransaction() starts an external transaction -
        // provided externalTransactionController is used, and there is
        // no active external transaction - so we have to start one internally.
        if (!isInTransaction()) {
            beginExternalTransaction();
        }

        // For unit of work and client session multi threading is allowed as they are a context,
        // this is required for JTS/RMI/CORBA/EJB stuff where the server thread can be different across calls.
        if (isUnitOfWork() || isClientSession()) {
            getTransactionMutex().setActiveThread(Thread.currentThread());
        }

        // Ensure mutual exclusion and call subclass specific begin.
        getTransactionMutex().acquire();
        if (!getTransactionMutex().isNested()) {
            getEventManager().preBeginTransaction();
            basicBeginTransaction();
            getEventManager().postBeginTransaction();
        }
    }

    /**
     * PUBLIC:
     * clear the integrityChecker. IntegrityChecker holds all the Descriptor Exceptions.
     */
    public void clearIntegrityChecker() {
        setIntegrityChecker(null);
    }

    /**
     * INTERNAL:
     * clear the lastDescriptorAccessed.
     */
    public void clearLastDescriptorAccessed() {
        lastDescriptorAccessed = null;
    }

    /**
     * PUBLIC:
     * Clear the profiler, this will end the current profile opperation.
     */
    public void clearProfile() {
        setProfiler(null);
    }

    /**
     * INTERNAL:
     * Clones the descriptor
     */
    public Object clone() {
        // An alternative to this process should be found
        try {
            return super.clone();
        } catch (Exception exception) {
            return null;
        }
    }

    /**
     * INTERNAL:
     * Attempts to commit the running internally started external transaction.
     * Returns true only in one case -
     * extenal transaction has been internally committed during this method call:
     * wasJTSTransactionInternallyStarted()==true in the beginning of this method and
     * wasJTSTransactionInternallyStarted()==false in the end of this method.
     */
    public boolean commitExternalTransaction() {
        boolean externalTransactionHasCommitted = false;
        if (hasExternalTransactionController() && wasJTSTransactionInternallyStarted()) {
            try {
                getExternalTransactionController().commitTransaction(this);
            } catch (RuntimeException exception) {
                handleException(exception);
            }
            if (!wasJTSTransactionInternallyStarted()) {
                externalTransactionHasCommitted = true;
                log(SessionLog.FINER, SessionLog.TRANSACTION, "external_transaction_has_committed_internally");
            }
        }
        return externalTransactionHasCommitted;
    }

    /**
     * PUBLIC:
     * Commit the active database transaction.
     * This allows a group of database modification to be commited or rolledback as a unit.
     * All writes/deletes will be sent to the database be will not be visible to other users until commit.
     * Although databases do not allow nested transaction,
     * TopLink supports nesting through only committing to the database on the outer commit.
     *
     * @exception DatabaseException most databases validate changes as they are done,
     * normally errors do not occur on commit unless the disk fails or the connection is lost.
     * @exception ConcurrencyException if this session is not within a transaction.
     */
    public void commitTransaction() throws DatabaseException, ConcurrencyException {
        // Release mutex and call subclass specific commit.
        if (!getTransactionMutex().isNested()) {
            getEventManager().preCommitTransaction();
            basicCommitTransaction();
            getEventManager().postCommitTransaction();
        }

        // This MUST not be in a try catch or finally as if the commit failed the transaction is still open.
        getTransactionMutex().release();

        // If there is no db transaction in progress
        // if there is an active external transaction 
        // which was started internally - it should be committed internally, too.
        if (!isInTransaction()) {
            commitExternalTransaction();
        }
    }

    /**
     * INTERNAL:
     * Return if the two object match completely.
     * This checks the objects attributes and their private parts.
     */
    public boolean compareObjects(Object firstObject, Object secondObject) {
        if ((firstObject == null) && (secondObject == null)) {
            return true;
        }

        if ((firstObject == null) || (secondObject == null)) {
            return false;
        }

        if (!(firstObject.getClass().equals(secondObject.getClass()))) {
            return false;
        }

        ObjectBuilder builder = getDescriptor(firstObject.getClass()).getObjectBuilder();

        return builder.compareObjects(builder.unwrapObject(firstObject, this), builder.unwrapObject(secondObject, this), this);
    }

    /**
     * TESTING:
     * Return true if the object do not match.
     * This checks the objects attributes and their private parts.
     */
    public boolean compareObjectsDontMatch(Object firstObject, Object secondObject) {
        return !this.compareObjects(firstObject, secondObject);
    }

    /**
     * PUBLIC:
     * Return true if the pre-defined query is defined on the session.
     */
    public boolean containsQuery(String queryName) {
        return getQueries().containsKey(queryName);
    }

    /**
     * PUBLIC:
     * Return a complete copy of the object.
     * This can be used to obtain a scatch copy of an object,
     * or for templatizing an existing object into another new object.
     * The object and all of its privately owned parts will be copied, the object's primary key will be reset to null.
     *
     * @see #copyObject(Object, ObjectCopyingPolicy)
     */
    public Object copyObject(Object original) {
        return copyObject(original, new ObjectCopyingPolicy());
    }

    /**
     * PUBLIC:
     * Return a complete copy of the object.
     * This can be used to obtain a scatch copy of an object,
     * or for templatizing an existing object into another new object.
     * The object copying policy allow for the depth, and reseting of the primary key to null, to be specified.
     */
    public Object copyObject(Object original, ObjectCopyingPolicy policy) {
        if (original == null) {
            return null;
        }

        ClassDescriptor descriptor = getDescriptor(original);
        if (descriptor == null) {
            return original;
        }

        policy.setSession(this);
        return descriptor.getObjectBuilder().copyObject(original, policy);
    }

    /**
     * INTERNAL:
     * Copy the read only classes from the unit of work
     *
     * Added Nov 8, 2000 JED for Patch 2.5.1.8
     * Ref: Prs 24502
     */
    public Vector copyReadOnlyClasses() {
        return getDefaultReadOnlyClasses();
    }

    /**
     * PUBLIC:
     * delete all of the objects and all of their privately owned parts in the database.
     * The allows for a group of objects to be deleted as a unit.
     * The objects will be deleted through a single transactions.
     *
     * @exception DatabaseException if an error occurs on the database,
     * these include constraint violations, security violations and general database erros.
     * @exception OptimisticLockException if the object's descriptor is using optimistic locking and
     * the object has been updated or deleted by another user since it was last read.
     */
    public void deleteAllObjects(Collection domainObjects) throws DatabaseException, OptimisticLockException {
        for (Iterator objectsEnum = domainObjects.iterator(); objectsEnum.hasNext();) {
            deleteObject(objectsEnum.next());
        }
    }

    /**
     * PUBLIC:
     * delete all of the objects and all of their privately owned parts in the database.
     * The allows for a group of objects to be deleted as a unit.
     * The objects will be deleted through a single transactions.
     *
     * @exception DatabaseException if an error occurs on the database,
     * these include constraint violations, security violations and general database erros.
     * @exception OptimisticLockException if the object's descriptor is using optimistic locking and
     * the object has been updated or deleted by another user since it was last read.
     */
    public void deleteAllObjects(Vector domainObjects) throws DatabaseException, OptimisticLockException {
        for (Enumeration objectsEnum = domainObjects.elements(); objectsEnum.hasMoreElements();) {
            deleteObject(objectsEnum.nextElement());
        }
    }

    /**
     * PUBLIC:
     * Delete the object and all of its privately owned parts from the database.
     * The delete operation can be customized through using a delete query.
     *
     * @exception DatabaseException if an error occurs on the database,
     * these include constraint violations, security violations and general database erros.
     * An database error is not raised if the object is already deleted or no rows are effected.
     * @exception OptimisticLockException if the object's descriptor is using optimistic locking and
     * the object has been updated or deleted by another user since it was last read.
     *
     * @see DeleteObjectQuery
     */
    public Object deleteObject(Object domainObject) throws DatabaseException, OptimisticLockException {
        DeleteObjectQuery query = new DeleteObjectQuery();
        query.setObject(domainObject);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Return if the object exists on the database or not.
     * This always checks existence on the database.
     */
    public boolean doesObjectExist(Object object) throws DatabaseException {
        DoesExistQuery query = new DoesExistQuery();
        query.setObject(object);
        query.checkDatabaseForDoesExist();
        return ((Boolean)executeQuery(query)).booleanValue();
    }

    /**
     * PUBLIC:
     * Turn off logging
     */
    public void dontLogMessages() {
        setLogLevel(SessionLog.OFF);
    }

    /**
     * INTERNAL:
     * End the operation timing.
     */
    public void endOperationProfile(String operationName) {
        if (isInProfile()) {
            getProfiler().endOperationProfile(operationName);
        }
    }

    /**
     * INTERNAL:
     * Updates the value of SessionProfiler state
     */
    public void updateProfile(String operationName, Object value) {
        if (isInProfile()) {
            getProfiler().update(operationName, value);
        }
    }

    /**
     * INTERNAL:
     * Updates the count of SessionProfiler event
     */
    public void incrementProfile(String operationName) {
        if (isInProfile()) {
            getProfiler().occurred(operationName);
        }
    }

    /**
     * INTERNAL:
     * Overridden by subclasses that do more than just execute the call.
     * Executes the call directly on this session and does not check which
     * session it should have executed on.
     */
    public Object executeCall(Call call, AbstractRecord translationRow, DatabaseQuery query) throws DatabaseException {
        //** sequencing refactoring
        if (query.getAccessor() == null) {
            query.setAccessor(getAccessor());
        }
        try {
            return query.getAccessor().executeCall(call, translationRow, this);
        } finally {
            if (call.isFinished()) {
                query.setAccessor(null);
            }
        }
    }

    /**
     * PUBLIC:
     * Execute the call on the database.
     * The row count is returned.
     * The call can be a stored procedure call, SQL call or other type of call.
     * <p>Example:
     * <p>session.executeNonSelectingCall(new SQLCall("Delete from Employee");
     *
     * @see #executeSelectingCall(Call)
     */
    public int executeNonSelectingCall(Call call) throws DatabaseException {
        DataModifyQuery query = new DataModifyQuery();
        query.setCall(call);
        Integer value = (Integer)executeQuery(query);
        if (value == null) {
            return 0;
        } else {
            return value.intValue();
        }
    }

    /**
     * PUBLIC:
     * Execute the sql on the database.
     * <p>Example:
     * <p>session.executeNonSelectingSQL("Delete from Employee");
     * @see #executeNonSelectingCall(Call)
     */
    public void executeNonSelectingSQL(String sqlString) throws DatabaseException {
        executeNonSelectingCall(new SQLCall(sqlString));
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     *
     * @see #addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName) throws DatabaseException {
        DatabaseQuery query = getQuery(queryName);

        if (query == null) {
            throw QueryException.queryNotDefined(queryName);
        }

        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     * The class is the descriptor in which the query was pre-defined.
     *
     * @see DescriptorQueryManager#addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Class domainClass) throws DatabaseException {
        ClassDescriptor descriptor = getDescriptor(domainClass);

        if (descriptor == null) {
            throw QueryException.descriptorIsMissingForNamedQuery(domainClass, queryName);
        }

        DatabaseQuery query = (DatabaseQuery)descriptor.getQueryManager().getQuery(queryName);

        if (query == null) {
            throw QueryException.queryNotDefined(queryName, domainClass);
        }

        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     * The class is the descriptor in which the query was pre-defined.
     *
     * @see DescriptorQueryManager#addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Class domainClass, Object arg1) throws DatabaseException {
        Vector argumentValues = new Vector();
        argumentValues.addElement(arg1);
        return executeQuery(queryName, domainClass, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     * The class is the descriptor in which the query was pre-defined.
     *
     * @see DescriptorQueryManager#addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Class domainClass, Object arg1, Object arg2) throws DatabaseException {
        Vector argumentValues = new Vector();
        argumentValues.addElement(arg1);
        argumentValues.addElement(arg2);
        return executeQuery(queryName, domainClass, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     * The class is the descriptor in which the query was pre-defined.
     *
     * @see DescriptorQueryManager#addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Class domainClass, Object arg1, Object arg2, Object arg3) throws DatabaseException {
        Vector argumentValues = new Vector();
        argumentValues.addElement(arg1);
        argumentValues.addElement(arg2);
        argumentValues.addElement(arg3);
        return executeQuery(queryName, domainClass, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     * The class is the descriptor in which the query was pre-defined.
     *
     * @see DescriptorQueryManager#addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Class domainClass, Vector argumentValues) throws DatabaseException {
        ClassDescriptor descriptor = getDescriptor(domainClass);

        if (descriptor == null) {
            throw QueryException.descriptorIsMissingForNamedQuery(domainClass, queryName);
        }

        DatabaseQuery query = (DatabaseQuery)descriptor.getQueryManager().getQuery(queryName, argumentValues);

        if (query == null) {
            throw QueryException.queryNotDefined(queryName, domainClass);
        }

        return executeQuery(query, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     *
     * @see #addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Object arg1) throws DatabaseException {
        Vector argumentValues = new Vector();
        argumentValues.addElement(arg1);
        return executeQuery(queryName, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     *
     * @see #addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Object arg1, Object arg2) throws DatabaseException {
        Vector argumentValues = new Vector();
        argumentValues.addElement(arg1);
        argumentValues.addElement(arg2);
        return executeQuery(queryName, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     *
     * @see #addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Object arg1, Object arg2, Object arg3) throws DatabaseException {
        Vector argumentValues = new Vector();
        argumentValues.addElement(arg1);
        argumentValues.addElement(arg2);
        argumentValues.addElement(arg3);
        return executeQuery(queryName, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     *
     * @see #addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Vector argumentValues) throws DatabaseException {
        DatabaseQuery query = getQuery(queryName, argumentValues);

        if (query == null) {
            throw QueryException.queryNotDefined(queryName);
        }

        return executeQuery(query, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the database query.
     * A query is a database operation such as reading or writting.
     * The query allows for the operation to be customized for such things as,
     * performance, depth, caching, etc.
     *
     * @see DatabaseQuery
     */
    public Object executeQuery(DatabaseQuery query) throws DatabaseException {
        return executeQuery(query, new DatabaseRecord(1));
    }

    /**
     * PUBLIC:
     * Return the results from exeucting the database query.
     * the arguments are passed in as a vector
     */
    public Object executeQuery(DatabaseQuery query, Vector argumentValues) throws DatabaseException {
        if (query == null) {
            throw QueryException.queryNotDefined();
        }

        AbstractRecord row = query.rowFromArguments(argumentValues);

        return executeQuery(query, row);
    }

    /**
     * INTERNAL:
     * Return the results from exeucting the database query.
     * the arguments should be a database row with raw data values.
     */
    public Object executeQuery(DatabaseQuery query, AbstractRecord row) throws DatabaseException {
        if (hasBroker()) {
            if (!((query.isDataModifyQuery() || query.isDataReadQuery()) && (query.getSessionName() == null))) {
                return getBroker().executeQuery(query, row);
            }
        }

        if (query == null) {
            throw QueryException.queryNotDefined();
        }

        //CR#2272
        log(SessionLog.FINEST, SessionLog.QUERY, "execute_query", query);

        try {
            getEventManager().preExecuteQuery(query);
            Object result;
            if (isInProfile()) {
                result = getProfiler().profileExecutionOfQuery(query, row, this);
            } else {
                result = internalExecuteQuery(query, row);
            }
            getEventManager().postExecuteQuery(query, result);
            return result;
        } catch (RuntimeException exception) {
            if (exception instanceof QueryException) {
                QueryException queryException = (QueryException)exception;
                if (queryException.getQuery() == null) {
                    queryException.setQuery(query);
                }
                if (queryException.getQueryArgumentsRecord() == null) {
                    queryException.setQueryArguments(row);
                }
                if (queryException.getSession() == null) {
                    queryException.setSession(this);
                }
            } else if (exception instanceof DatabaseException) {
                DatabaseException databaseException = (DatabaseException)exception;
                if (databaseException.getQuery() == null) {
                    databaseException.setQuery(query);
                }
                if (databaseException.getQueryArgumentsRecord() == null) {
                    databaseException.setQueryArguments(row);
                }
                if (databaseException.getSession() == null) {
                    databaseException.setSession(this);
                }
            }
            return handleException(exception);
        }
    }

    /**
     * PUBLIC:
     * Execute the call on the database and return the result.
     * The call must return a value, if no value is return executeNonSelectCall must be used.
     * The call can be a stored procedure call, SQL call or other type of call.
     * A vector of database rows is returned, database row implements Java 2 Map which should be used to access the data.
     * <p>Example:
     * <p>session.executeSelectingCall(new SQLCall("Select * from Employee");
     *
     * @see #executeNonSelectingCall(Call)
     */
    public Vector executeSelectingCall(Call call) throws DatabaseException {
        DataReadQuery query = new DataReadQuery();
        query.setCall(call);
        return (Vector)executeQuery(query);
    }

    /**
     * PUBLIC:
     * Execute the sql on the database and return the result.
     * It must return a value, if no value is return executeNonSelectingSQL must be used.
     * A vector of database rows is returned, database row implements Java 2 Map which should be used to access the data.
     * <p>Example:
     * <p>session.executeSelectingCall("Select * from Employee");
     *
     * @see #executeSelectingCall(Call)
     */
    public Vector executeSQL(String sqlString) throws DatabaseException {
        return executeSelectingCall(new SQLCall(sqlString));
    }

    /**
     * INTERNAL:
     * Return the lowlevel database accessor.
     * The database accesor is used for direct database access.
     */
    public synchronized Accessor getAccessor() {
        if ((accessor == null) && (project != null) && (project.getDatasourceLogin() != null)) {
            // PERF: lazy init, not always required.
            accessor = project.getDatasourceLogin().buildAccessor();
        }
        return accessor;
    }

    /**
     * INTERNAL:
     * Return the lowlevel database accessor.
     * The database accesor is used for direct database access.
     * If sessionBroker is used, the right accessor for this
     * broker will be returned.
     */
    public Accessor getAccessor(Class domainClass) {
        return getAccessor();
    }

    /**
     * INTERNAL:
     * Return the lowlevel database accessor.
     * The database accesor is used for direct database access.
     * If sessionBroker is used, the right accessor for this
     * broker will be returned based on the session name.
     */
    public Accessor getAccessor(String sessionName) {
        return getAccessor();
    }

    /**
     * PUBLIC:
     * Return the active session for the current active external (JTS) transaction.
     * This should only be used with JTS and will return the session if no external transaction exists.
     */
    public oracle.toplink.essentials.sessions.Session getActiveSession() {
        oracle.toplink.essentials.sessions.Session activeSession = getActiveUnitOfWork();
        if (activeSession == null) {
            activeSession = this;
        }

        return activeSession;
    }

    /**
     * PUBLIC:
     * Return the active unit of work for the current active external (JTS) transaction.
     * This should only be used with JTS and will return null if no external transaction exists.
     */
    public oracle.toplink.essentials.sessions.UnitOfWork getActiveUnitOfWork() {
        if (hasExternalTransactionController()) {
            return getExternalTransactionController().getActiveUnitOfWork();
        }

        /* Steven Vo:  CR# 2517
           Get from the server session since the external transaction controller could be
           null out from the client session by TL WebLogic 5.1 to provide non-jts transaction
           operations
          */
        if (isClientSession()) {
            return ((oracle.toplink.essentials.threetier.ClientSession)this).getParent().getActiveUnitOfWork();
        }

        return null;
    }

    /**
     * INTERNAL:
     * Returns the alias descriptors hashtable.
     */
    public Map getAliasDescriptors() {
        return project.getAliasDescriptors();
    }

    /**
     * INTERNAL:
     * Allow the session to be used from a session broker.
     */
    public AbstractSession getBroker() {
        return broker;
    }

    /**
     * INTERNAL:
     * The session that this query is executed against when not in transaction.
     * The session containing the shared identity map.
     * <p>
     * In most cases this is the root ServerSession or DatabaseSession.
     * <p>
     * In cases where objects are not to be cached in the global identity map
     * an alternate session may be returned:
     * <ul>
     * <li>A ClientSession if in transaction
     * <li>An isolated ClientSession or HistoricalSession
     * <li>A registered session of a root SessionBroker
     * </ul>
     */
    public AbstractSession getRootSession(DatabaseQuery query) {
        return getParentIdentityMapSession(query, false, true);
    }

    /**
     * INTERNAL:
     * Gets the next link in the chain of sessions followed by a query's check
     * early return, the chain of sessions with identity maps all the way up to
     * the root session.
     */
    public AbstractSession getParentIdentityMapSession(DatabaseQuery query) {
        return getParentIdentityMapSession(query, false, false);
    }

    /**
     * INTERNAL:
     * Gets the next link in the chain of sessions followed by a query's check
     * early return, the chain of sessions with identity maps all the way up to
     * the root session.
     * <p>
     * Used for session broker which delegates to registered sessions, or UnitOfWork
     * which checks parent identity map also.
     * @param canReturnSelf true when method calls itself.  If the path
     * starting at <code>this</code> is acceptable.  Sometimes true if want to
     * move to the first valid session, i.e. executing on ClientSession when really
     * should be on ServerSession.
     * @param terminalOnly return the session we will execute the call on, not
     * the next step towards it.
     * @return this if there is no next link in the chain
     */
    public AbstractSession getParentIdentityMapSession(DatabaseQuery query, boolean canReturnSelf, boolean terminalOnly) {
        return this;
    }

    /**
     * INTERNAL:
     * Gets the session which this query will be executed on.
     * Generally will be called immediately before the call is translated,
     * which is immediately before session.executeCall.
     * <p>
     * Since the execution session also knows the correct datasource platform
     * to execute on, it is often used in the mappings where the platform is
     * needed for type conversion, or where calls are translated.
     * <p>
     * Is also the session with the accessor.  Will return a ClientSession if
     * it is in transaction and has a write connection.
     * @return a session with a live accessor
     * @param query may store session name or reference class for brokers case
     */
    public AbstractSession getExecutionSession(DatabaseQuery query) {
        return this;
    }

    /**
     * INTERNAL:
     * The commit manager is used to resolve referncial integrity on commits of multiple objects.
     * All brokered sessions share the same commit manager.
     */
    public CommitManager getCommitManager() {
        if (hasBroker()) {
            return getBroker().getCommitManager();
        }

        // PERF: lazy init, not always required, not required for client sessions
        if (commitManager == null) {
            commitManager = new CommitManager(this);
        }
        return commitManager;
    }

    /**
     * INTERNAL:
     * Returns the set of read-only classes that gets assigned to each newly created UnitOfWork.
     *
     * @see oracle.toplink.essentials.sessions.Project#setDefaultReadOnlyClasses(Vector)
     */
    public Vector getDefaultReadOnlyClasses() {
        //Bug#3911318  All brokered sessions share the same DefaultReadOnlyClasses.
        if (hasBroker()) {
            return getBroker().getDefaultReadOnlyClasses();
        }
        return getProject().getDefaultReadOnlyClasses();
    }

    /**
     * ADVANCED:
     * Return the descriptor specified for the class.
     * If the class does not have a descriptor but implements an interface that is also implemented
     * by one of the classes stored in the hashtable, that descriptor will be stored under the
     * new class.
     */
    public ClassDescriptor getClassDescriptor(Class theClass) {
		ClassDescriptor desc = getDescriptor(theClass);
		if (desc instanceof ClassDescriptor) {
			return (ClassDescriptor)desc;
		} else {
			throw ValidationException.cannotCastToClass(desc, desc.getClass(), ClassDescriptor.class);
		}
	}

    /**
     * ADVANCED:
     * Return the descriptor specified for the object's class.
     */
    public ClassDescriptor getClassDescriptor(Object domainObject) {
		ClassDescriptor desc = getDescriptor(domainObject);
		if (desc instanceof ClassDescriptor) {
			return (ClassDescriptor)desc;
		} else {
			throw ValidationException.cannotCastToClass(desc, desc.getClass(), ClassDescriptor.class);
		}
	}

    /**
     * PUBLIC:
     * Return the descriptor for  the alias.
     * UnitOfWork delegates this to the parent
     */
    public ClassDescriptor getClassDescriptorForAlias(String alias) {
        return project.getClassDescriptorForAlias(alias);
	}

    /**
     * ADVANCED:
     * Return the descriptor specified for the class.
     * If the class does not have a descriptor but implements an interface that is also implemented
     * by one of the classes stored in the hashtable, that descriptor will be stored under the
     * new class.
     */
    public ClassDescriptor getDescriptor(Class theClass) {
        if (theClass == null) {
            return null;
        }

        // Optimize descriptor lookup through caching the last one accessed.
        ClassDescriptor lastDescriptor = this.lastDescriptorAccessed;
        if ((lastDescriptor != null) && (lastDescriptor.getJavaClass().equals(theClass))) {
            return lastDescriptor;
        }

        ClassDescriptor descriptor = (ClassDescriptor)getDescriptors().get(theClass);

        if ((descriptor == null) && hasBroker()) {
            // Also check the broker
            descriptor = getBroker().getDescriptor(theClass);
        }
        if (descriptor == null) {
            // Allow for an event listener to lazy register the descriptor for a class.
            getEventManager().missingDescriptor(theClass);
            descriptor = (ClassDescriptor)getDescriptors().get(theClass);
        }

        if (descriptor == null) {
            // This allows for the correct descriptor to be found if the class implements an interface,
            // or extends a class that a descriptor is register for.
            // This is used by EJB to find the descriptor for a stub and remote to unwrap it,
            // and by inheritance to allow for subclasses that have no additional state to not require a descriptor.
            if (!theClass.isInterface()) {
                Class[] interfaces = theClass.getInterfaces();
                for (int index = 0; index < interfaces.length; ++index) {
                    Class interfaceClass = (Class)interfaces[index];
                    descriptor = getDescriptor(interfaceClass);
                    if (descriptor != null) {
                        getDescriptors().put(interfaceClass, descriptor);
                        break;
                    }
                }
                if (descriptor == null) {
                    descriptor = getDescriptor(theClass.getSuperclass());
                }
            }
        }

        // Cache for optimization.
        this.lastDescriptorAccessed = descriptor;

        return descriptor;
    }

    /**
     * ADVANCED:
     * Return the descriptor specified for the object's class.
     */
    public ClassDescriptor getDescriptor(Object domainObject) {
        return getDescriptor(domainObject.getClass());        
    }

    /**
     * PUBLIC:
     * Return the descriptor for  the alias
     */
    public ClassDescriptor getDescriptorForAlias(String alias) {
        return project.getDescriptorForAlias(alias);
    }

    /**
     * ADVANCED:
     * Return all registered descriptors.
     */
    public Map getDescriptors() {
        return getProject().getDescriptors();
    }

    /**
     * ADVANCED:
     * Return all pre-defined not yet parsed EJBQL queries.
     * @see #getAllQueries()
     */
    public List getEjbqlPlaceHolderQueries() {
        // PERF: lazy init, not normally required.
        if (ejbqlPlaceHolderQueries == null) {
            ejbqlPlaceHolderQueries = new Vector();
        }
        return ejbqlPlaceHolderQueries;
    }

    /**
     * PUBLIC:
     * Return the event manager.
     * The event manager can be used to register for various session events.
     */
    public synchronized SessionEventManager getEventManager() {
        if (eventManager == null) {
            // PERF: lazy init.
            eventManager = new SessionEventManager(this);
        }
        return eventManager;
    }

    /**
     * INTERNAL:
     * Return a string which represents my ExceptionHandler's class
     * Added for F2104: Properties.xml
     * - gn
     */
    public String getExceptionHandlerClass() {
        String className = null;
        try {
            className = getExceptionHandler().getClass().getName();
        } catch (Exception exception) {
            return null;
        }
        return className;
    }

    /**
     * PUBLIC:
     * Return the ExceptionHandler.Exception handler can catch errors that occur on queries or during database access.
     */
    public ExceptionHandler getExceptionHandler() {
        return exceptionHandler;
    }

    /**
     * PUBLIC:
     * Used for JTS integration.  If your application requires to have JTS control transactions instead of TopLink an
     * external transaction controler must be specified.
     * TopLink provides JTS controlers for several JTS implementations including JTS 1.0, Weblogic 5.1 and WebSphere 3.0.
     *
     * @see oracle.toplink.essentials.transaction.JTATransactionController
     */
    public ExternalTransactionController getExternalTransactionController() {
        return externalTransactionController;
    }

    /**
     * PUBLIC:
     * The IdentityMapAccessor is the preferred way of accessing IdentityMap funcitons
     * This will return an object which implements an interface which exposes all public
     * IdentityMap functions.
     */
    public oracle.toplink.essentials.sessions.IdentityMapAccessor getIdentityMapAccessor() {
        return identityMapAccessor;
    }

    /**
     * INTERNAL:
     * Return the internally available IdentityMapAccessor instance.
     */
    public oracle.toplink.essentials.internal.sessions.IdentityMapAccessor getIdentityMapAccessorInstance() {
        return identityMapAccessor;
    }

    /**
     * PUBLIC:
     * Returns the integrityChecker.IntegrityChecker holds all the Descriptor Exceptions.
     */
    public IntegrityChecker getIntegrityChecker() {
        // BUG# 2700595 - Lazily create an IntegrityChecker if one has not already been created.
        if (integrityChecker == null) {
            integrityChecker = new IntegrityChecker();
        }

        return integrityChecker;
    }

    /**
     * PUBLIC:
     * Return the writer to which an accessor writes logged messages and SQL.
     * If not set, this reference defaults to a writer on System.out.
     *
     * @see #getSessionLog()
     */
    public Writer getLog() {
        return getSessionLog().getWriter();
    }

    /**
     * INTERNAL:
     * Return the name of the session: class name + system hashcode.
     * <p>
     * This should be the implementation of toString(), and also the
     * value should be calculated in the constructor for it is used all the
     * time.  However everything is lazily initialized now and the value is
     * transient for the system hashcode could vary?
     */
    public String getLogSessionString() {
        if (logSessionString == null) {
            StringWriter writer = new StringWriter();
            writer.write(getSessionTypeString());
            writer.write("(");
            writer.write(String.valueOf(System.identityHashCode(this)));
            writer.write(")");
            logSessionString = writer.toString();
        }
        return logSessionString;
    }

    /**
     * INTERNAL:
     * Returns the type of session, its class.
     * <p>
     * Override to hide from the user when they are using an internal subclass
     * of a known class.
     * <p>
     * A user does not need to know that their UnitOfWork is a
     * non-deferred UnitOfWork, or that their ClientSession is an
     * IsolatedClientSession.
     */
    public String getSessionTypeString() {
        return Helper.getShortClassName(getClass());
    }

    /**
     * INTERNAL:
     * Return the login, the login holds any database connection information given.
     * This has been replaced by getDatasourceLogin to make use of the Login interface
     * to support non-relational datasources,
     * if DatabaseLogin API is required it will need to be cast.
     */
    public DatabaseLogin getLogin() {
        try {
            return (DatabaseLogin)getDatasourceLogin();
        } catch (ClassCastException wrongType) {
            throw ValidationException.notSupportedForDatasource();
        }
    }

    /**
     * PUBLIC:
     * Return the login, the login holds any database connection information given.
     * This return the Login interface and may need to be cast to the datasource specific implementation.
     */
    public Login getDatasourceLogin() {
        return getProject().getDatasourceLogin();
    }

    /**
     * PUBLIC:
     * Return the name of the session.
     * This is used with the session broker, or to give the session a more meaningful name.
     */
    public String getName() {
        return name;
    }

    /**
     * ADVANCED:
     * Return the sequnce number from the database
     */
    public Number getNextSequenceNumberValue(Class domainClass) {
        return (Number)getSequencing().getNextValue(domainClass);
    }

    /**
     * INTERNAL:
     * Return the number of units of work connected.
     */
    public int getNumberOfActiveUnitsOfWork() {
        return numberOfActiveUnitsOfWork;
    }

    /**
     * PUBLIC:
     * Return the database platform currently connected to.
     * The platform is used for database specific behavoir.
     * NOTE: this must only be used for relational specific usage,
     * it will fail for non-relational datasources.
     */
    public DatabasePlatform getPlatform() {        
        // PERF: Cache the platform.
        if (platform == null) {
            platform = getDatasourceLogin().getPlatform();
        }
        return (DatabasePlatform)platform;
    }

    /**
     * PUBLIC:
     * Return the database platform currently connected to.
     * The platform is used for database specific behavoir.
     */
    public Platform getDatasourcePlatform() {
        // PERF: Cache the platform.
        if (platform == null) {
            platform = getDatasourceLogin().getDatasourcePlatform();
        }
        return platform;
    }
    
    /**
     * INTERNAL:
     * Marked internal as this is not customer API but helper methods for
     * accessing the server platform from within TopLink's other sessions types
     * (ie not DatabaseSession)
     */
    public ServerPlatform getServerPlatform(){
        return null;
    }

    /**
     * INTERNAL:
     * Return the database platform currently connected to
     * for specified class.
     * The platform is used for database specific behavoir.
     */
    public Platform getPlatform(Class domainClass) {
        // PERF: Cache the platform.
        if (platform == null) {
            platform = getDatasourcePlatform();
        }
        return platform;
    }

    /**
     * PUBLIC:
     * Return the profiler.
     * The profiler is a tool that can be used to determine performance bottlenecks.
     * The profiler can be queries to print summaries and configure for logging purposes.
     */
    public SessionProfiler getProfiler() {
        return profiler;
    }

    /**
     * PUBLIC:
     * Return the project, the project holds configuartion information including the descriptors.
     */
    public oracle.toplink.essentials.sessions.Project getProject() {
        return project;
    }

    /**
     * ADVANCED:
     * Allow for user defined properties.
     */
    public Map getProperties() {
        if (properties == null) {
            properties = new HashMap(5);
        }
        return properties;
    }

    /**
     * INTERNAL:
     * Allow to check for user defined properties.
     */
    public boolean hasProperties() {
        return ((properties != null) && !properties.isEmpty());
    }

    /**
     * ADVANCED:
     * Returns the user defined property.
     */
    public Object getProperty(String name) {
        return getProperties().get(name);
    }

    /**
     * ADVANCED:
     * Return all pre-defined queries.
     * @see #getAllQueries()
     */
    public Map getQueries() {
        // PERF: lazy init, not normally required.
        if (queries == null) {
            queries = new HashMap(5);
        }
        return queries;
    }

    /**
     * PUBLIC:
     * Return the pre-defined queries in this session.
     * A single vector containing all the queries is returned.
     *
     * @see #getQueries()
     */
    public Vector getAllQueries() {
        Vector allQueries = new Vector();
        for (Iterator vectors = getQueries().values().iterator(); vectors.hasNext();) {
            allQueries.addAll((Vector)vectors.next());
        }
        return allQueries;
    }

    /**
     * PUBLIC:
     * Return the query from the session pre-defined queries with the given name.
     * This allows for common queries to be pre-defined, reused and executed by name.
     */
    public DatabaseQuery getQuery(String name) {
        return getQuery(name, null);
    }

    /**
     * PUBLIC:
     * Return the query from the session pre-defined queries with the given name and argument types.
     * This allows for common queries to be pre-defined, reused and executed by name.
     * This method should be used if the Session has multiple queries with the same name but
     * different arguments.
     *
     * @see #getQuery(String)
     */
    public DatabaseQuery getQuery(String name, Vector arguments) {
        Vector queries = (Vector)getQueries().get(name);
        if ((queries == null) || queries.isEmpty()) {
            return null;
        }

        // Short circuit the simple, most common case of only one query.
        if (queries.size() == 1) {
            return (DatabaseQuery)queries.firstElement();
        }

        // CR#3754; Predrag; mar 19/2002;
        // We allow multiple named queries with the same name but
        // different argument set; we can have only one query with
        // no arguments; Vector queries is not sorted;
        // When asked for the query with no parameters the
        // old version did return the first query - wrong: 
        // return (DatabaseQuery) queries.firstElement();
        int argumentTypesSize = 0;
        if (arguments != null) {
            argumentTypesSize = arguments.size();
        }
        Vector argumentTypes = new Vector(argumentTypesSize);
        for (int i = 0; i < argumentTypesSize; i++) {
            argumentTypes.addElement(arguments.elementAt(i).getClass());
        }
        for (Enumeration queriesEnum = queries.elements(); queriesEnum.hasMoreElements();) {
            DatabaseQuery query = (DatabaseQuery)queriesEnum.nextElement();
            if (Helper.areTypesAssignable(argumentTypes, query.getArgumentTypes())) {
                return query;
            }
        }
        return null;
    }

    /**
     * INTERNAL:
     * Return the Sequencing object used by the session.
     */
    public Sequencing getSequencing() {
        return null;
    }

    /**
     * INTERNAL:
     * Return the session to be used for the class.
     * Used for compatibility with the session broker.
     */
    public AbstractSession getSessionForClass(Class domainClass) {
        if (hasBroker()) {
            return getBroker().getSessionForClass(domainClass);
        }
        return this;
    }

    /**
     * PUBLIC:
     * Return the session log to which an accessor logs messages and SQL.
     * If not set, this will default to a session log on a writer on System.out.
     */
    public SessionLog getSessionLog() {
        if (sessionLog == null) {
            setSessionLog(new DefaultSessionLog());
        }
        return sessionLog;
    }

    /**
     * INTERNAL:
     * The transaction mutex ensure mutual exclusion on transaction across multiple threads.
     */
    public synchronized ConcurrencyManager getTransactionMutex() {
        // PERF: not always required, defer.
        if (transactionMutex == null) {
            transactionMutex = new ConcurrencyManager();
        }
        return transactionMutex;
    }

    /**
     * PUBLIC:
     * Allow any WARNING level exceptions that occur within TopLink to be logged and handled by the exception handler.
     */
    public Object handleException(RuntimeException exception) throws RuntimeException {
        if ((exception instanceof TopLinkException)) {
            TopLinkException topLinkException = (TopLinkException)exception;
            if (topLinkException.getSession() == null) {
                topLinkException.setSession(this);
            }
            //Bug#3559280  Avoid logging an exception twice
            if (!topLinkException.hasBeenLogged()) {
                logThrowable(SessionLog.WARNING, null, exception);
                topLinkException.setHasBeenLogged(true);
            }
        } else {
            logThrowable(SessionLog.WARNING, null, exception);
        }
        if (hasExceptionHandler()) {
            return getExceptionHandler().handleException(exception);
        } else {
            throw exception;
        }
    }

    /**
     * INTERNAL:
     * Allow the session to be used from a session broker.
     */
    public boolean hasBroker() {
        return broker != null;
    }

    /**
     * ADVANCED:
     * Return true if a descriptor exists for the given class.
     */
    public boolean hasDescriptor(Class theClass) {
        if (theClass == null) {
            return false;
        }

        return getDescriptors().get(theClass) != null;
    }

    /**
     * PUBLIC:
     * Return if an exception handler is present.
     */
    public boolean hasExceptionHandler() {
        if (exceptionHandler == null) {
            return false;
        }
        return true;
    }

    /**
     * PUBLIC:
     * Used for JTA integration.  If your application requires to have JTA control transactions instead of TopLink an
     * external transaction controler must be specified.  TopLink provides JTA controlers for JTA 1.0 and application
     * servers.
     * @see oracle.toplink.essentials.transaction.JTATransactionController
     */
    public boolean hasExternalTransactionController() {
        return externalTransactionController != null;
    }

    /**
     * INTERNAL:
     * Set up the IdentityMapManager.  This method allows subclasses of Session to override
     * the default IdentityMapManager functionality.
     */
    public void initializeIdentityMapAccessor() {
        this.identityMapAccessor = new oracle.toplink.essentials.internal.sessions.IdentityMapAccessor(this, new IdentityMapManager(this));
    }

    /**
     * PUBLIC:
     * Insert the object and all of its privately owned parts into the database.
     * Insert should only be used if the application knows that the object is new,
     * otherwise writeObject should be used.
     * The insert operation can be customized through using an insert query.
     *
     * @exception DatabaseException if an error occurs on the database,
     * these include constraint violations, security violations and general database erros.
     *
     * @see InsertObjectQuery
     * @see #writeObject(Object)
     */
    public Object insertObject(Object domainObject) throws DatabaseException {
        InsertObjectQuery query = new InsertObjectQuery();
        query.setObject(domainObject);
        return executeQuery(query);
    }

    /**
     * INTERNAL:
     * Return the results from exeucting the database query.
     * The arguments should be a database row with raw data values.
     * This method is provided to allow subclasses to change the default querying behavoir.
     * All querying goes through this method.
     */
    public Object internalExecuteQuery(DatabaseQuery query, AbstractRecord databaseRow) throws DatabaseException {
        return query.execute(this, databaseRow);
    }

    /**
     * INTERNAL:
     * Returns true if the session is a session Broker.
     */
    public boolean isBroker() {
        return false;
    }

    /**
     * INTERNAL:
     * Returns true if the session is in a session Broker.
     */
    public boolean isInBroker() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if the class is defined as read-only.
     */
    public boolean isClassReadOnly(Class theClass) {
        ClassDescriptor descriptor = getDescriptor(theClass);
        return isClassReadOnly(theClass, descriptor);
    }

    /**
     * INTERNAL:
     * Return if the class is defined as read-only.
     * PERF: Pass descriptor to avoid re-lookup.
     */
    public boolean isClassReadOnly(Class theClass, ClassDescriptor descriptor) {
        if ((descriptor != null) && descriptor.shouldBeReadOnly()) {
            return true;
        }
        if (theClass != null) {
            return getDefaultReadOnlyClasses().contains(theClass);
        }
        return false;
    }

    /**
     * PUBLIC:
     * Return if this session is a client session.
     */
    public boolean isClientSession() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if this session is connected to the database.
     */
    public boolean isConnected() {
        if (getAccessor() == null) {
            return false;
        }

        return getAccessor().isConnected();
    }

    /**
     * PUBLIC:
     * Return if this session is a database session.
     */
    public boolean isDatabaseSession() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if this session is a distributed session.
     */
    public boolean isDistributedSession() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if a profiler is being used.
     */
    public boolean isInProfile() {
        return isInProfile;
    }

    /**
     * PUBLIC:
     * Allow for user deactive a profiler
     */
    public void setIsInProfile(boolean inProfile) {
        this.isInProfile = inProfile;
    }

    /**
     * PUBLIC:
     * Return if the session is currently in the progress of a database transaction.
     * Because nested transactions are allowed check if the transaction mutex has been aquired.
     */
    public boolean isInTransaction() {
        return getTransactionMutex().isAcquired();
    }

    /**
     * PUBLIC:
     * Return if this session is remote.
     */
    public boolean isRemoteSession() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if this session is a unit of work.
     */
    public boolean isRemoteUnitOfWork() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if this session is a server session.
     */
    public boolean isServerSession() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if this session is a session broker.
     */
    public boolean isSessionBroker() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if this session is a unit of work.
     */
    public boolean isUnitOfWork() {
        return false;
    }

    /**
     * ADVANCED:
     * Extract and return the primary key from the object.
     */
    public Vector keyFromObject(Object domainObject) throws ValidationException {
        ClassDescriptor descriptor = getDescriptor(domainObject);
        return keyFromObject(domainObject, descriptor);
    }

    /**
     * ADVANCED:
     * Extract and return the primary key from the object.
     */
    public Vector keyFromObject(Object domainObject, ClassDescriptor descriptor) throws ValidationException {
        if (descriptor == null) {
            throw ValidationException.missingDescriptor(domainObject.getClass().getName());
        }
        Object implemention = descriptor.getObjectBuilder().unwrapObject(domainObject, this);
        if (implemention == null) {
            return null;
        }
        return descriptor.getObjectBuilder().extractPrimaryKeyFromObject(implemention, this);
    }

    /**
     * PUBLIC:
     * Log the log entry.
     */
    public void log(SessionLogEntry entry) {
        if (shouldLog(entry.getLevel(), entry.getNameSpace())) {
            if (entry.getSession() == null) {// Used for proxy session.
                entry.setSession(this);
            }
            getSessionLog().log(entry);
        }
    }

    /**
     * Log a untranslated message to the TopLink log at FINER level.
     */
    public void logMessage(String message) {
        log(SessionLog.FINER, message, (Object[])null, null, false);
    }

    /**
     * INTERNAL:
     * A call back to do session specific preparation of a query.
     * <p>
     * The call back occurs soon before we clone the query for execution,
     * meaning that if this method needs to clone the query then the caller will
     * determine that it doesn't need to clone the query itself.
     */
    public DatabaseQuery prepareDatabaseQuery(DatabaseQuery query) {
        if (!isUnitOfWork() && query.isObjectLevelReadQuery()) {
            return ((ObjectLevelReadQuery)query).prepareOutsideUnitOfWork(this);
        } else {
            return query;
        }
    }
    
    /**
     * INTERNAL:
     * Allows for EJBQL strings to be parsed and added as named queries.  Should 
     * be called after descriptors have been initialized to ensure all mappings 
     * exist.  
     */
    public void processEJBQLQueries() {
        List queries = getEjbqlPlaceHolderQueries();
        processEJBQLQueries(queries);
        queries.clear();
    }
    
        /**
     * INTERNAL:
     * Allows for EJBQL strings to be parsed and added as named queries.  Should 
     * be called after descriptors have been initialized to ensure all mappings 
     * exist.  
     */
    public void processEJBQLQueries(List queries) {
        for (Iterator iterator = queries.iterator(); iterator.hasNext();) {
            EJBQLPlaceHolderQuery existingQuery = (EJBQLPlaceHolderQuery)iterator.next();
            this.addQuery(existingQuery.processEjbQLQuery(this));
        }
    }

    /**
     * PUBLIC:
     * Read all of the instances of the class from the database.
     * This operation can be customized through using a ReadAllQuery,
     * or through also passing in a selection criteria.
     *
     * @see ReadAllQuery
     * @see #readAllObjects(Class, Expression)
     */
    public Vector readAllObjects(Class domainClass) throws DatabaseException {
        ReadAllQuery query = new ReadAllQuery();
        query.setReferenceClass(domainClass);
        return (Vector)executeQuery(query);
    }

    /**
     * PUBLIC:
     * Read all of the instances of the class from the database return through execution the SQL string.
     * The SQL string must be a valid SQL select statement or selecting stored procedure call.
     * This operation can be customized through using a ReadAllQuery.
     *
     * @see ReadAllQuery
     */
    public Vector readAllObjects(Class domainClass, String sqlString) throws DatabaseException {
        ReadAllQuery query = new ReadAllQuery();
        query.setReferenceClass(domainClass);
        query.setSQLString(sqlString);
        return (Vector)executeQuery(query);
    }

    /**
     * PUBLIC:
     * Read all the instances of the class from the database returned through execution the Call string.
     * The Call can be an SQLCall or EJBQLCall.
     *
     * example: session.readAllObjects(Employee.class, new SQLCall("SELECT * FROM EMPLOYEE"));
     * @see Call
     */
    public Vector readAllObjects(Class referenceClass, Call aCall) throws DatabaseException {
        ReadAllQuery raq = new ReadAllQuery();
        raq.setReferenceClass(referenceClass);
        raq.setCall(aCall);
        return (Vector)executeQuery(raq);
    }

    /**
     * PUBLIC:
     * Read all of the instances of the class from the database matching the given expression.
     * This operation can be customized through using a ReadAllQuery.
     *
     * @see ReadAllQuery
     */
    public Vector readAllObjects(Class domainClass, Expression expression) throws DatabaseException {
        ReadAllQuery query = new ReadAllQuery();
        query.setReferenceClass(domainClass);
        query.setSelectionCriteria(expression);
        return (Vector)executeQuery(query);
    }

    /**
     * PUBLIC:
     * Read the first instance of the class from the database.
     * This operation can be customized through using a ReadObjectQuery,
     * or through also passing in a selection criteria.
     *
     * @see ReadObjectQuery
     * @see #readAllObjects(Class, Expression)
     */
    public Object readObject(Class domainClass) throws DatabaseException {
        ReadObjectQuery query = new ReadObjectQuery();
        query.setReferenceClass(domainClass);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Read the first instance of the class from the database return through execution the SQL string.
     * The SQL string must be a valid SQL select statement or selecting stored procedure call.
     * This operation can be customized through using a ReadObjectQuery.
     *
     * @see ReadObjectQuery
     */
    public Object readObject(Class domainClass, String sqlString) throws DatabaseException {
        ReadObjectQuery query = new ReadObjectQuery();
        query.setReferenceClass(domainClass);
        query.setSQLString(sqlString);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Read the first instance of the class from the database returned through execution the Call string.
     * The Call can be an SQLCall or EJBQLCall.
     *
     * example: session.readObject(Employee.class, new SQLCall("SELECT * FROM EMPLOYEE"));
     * @see SQLCall
     * @see EJBQLCall
     */
    public Object readObject(Class domainClass, Call aCall) throws DatabaseException {
        ReadObjectQuery query = new ReadObjectQuery();
        query.setReferenceClass(domainClass);
        query.setCall(aCall);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Read the first instance of the class from the database matching the given expression.
     * This operation can be customized through using a ReadObjectQuery.
     *
     * @see ReadObjectQuery
     */
    public Object readObject(Class domainClass, Expression expression) throws DatabaseException {
        ReadObjectQuery query = new ReadObjectQuery();
        query.setReferenceClass(domainClass);
        query.setSelectionCriteria(expression);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Use the example object to consruct a read object query by the objects primary key.
     * This will read the object from the database with the same primary key as the object
     * or null if no object is found.
     */
    public Object readObject(Object object) throws DatabaseException {
        ReadObjectQuery query = new ReadObjectQuery();
        query.setSelectionObject(object);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Refresh the attributes of the object and of all of its private parts from the database.
     * The object will be pessimisticly locked on the database for the duration of the transaction.
     * If the object is already locked this method will wait until the lock is released.
     * A no wait option is available through setting the lock mode.
     * @see #refreshAndLockObject(Object, lockMode)
     */
    public Object refreshAndLockObject(Object object) throws DatabaseException {
        ReadObjectQuery query = new ReadObjectQuery();
        query.setSelectionObject(object);
        query.refreshIdentityMapResult();
        query.cascadePrivateParts();
        query.setLockMode(ObjectBuildingQuery.LOCK);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Refresh the attributes of the object and of all of its private parts from the database.
     * The object will be pessimisticly locked on the database for the duration of the transaction.
     * <p>Lock Modes: ObjectBuildingQuery.NO_LOCK, LOCK, LOCK_NOWAIT
     */
    public Object refreshAndLockObject(Object object, short lockMode) throws DatabaseException {
        ReadObjectQuery query = new ReadObjectQuery();
        query.setSelectionObject(object);
        query.refreshIdentityMapResult();
        query.cascadePrivateParts();
        query.setLockMode(lockMode);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Refresh the attributes of the object and of all of its private parts from the database.
     * This can be used to ensure the object is up to date with the database.
     * Caution should be used when using this to make sure the application has no un commited
     * changes to the object.
     */
    public Object refreshObject(Object object) throws DatabaseException {
        return refreshAndLockObject(object, ObjectBuildingQuery.NO_LOCK);
    }

    /**
     * PUBLIC:
     * Release the session.
     * This does nothing by default, but allows for other sessions such as the ClientSession to do something.
     */
    public void release() {
    }

    /**
     * INTERNAL:
     * Release the unit of work, if lazy release the connection.
     */
    public void releaseUnitOfWork(UnitOfWorkImpl unitOfWork) {
        // Nothing is required by default, allow subclasses to do cleanup.
        setNumberOfActiveUnitsOfWork(getNumberOfActiveUnitsOfWork() - 1);
    }


    /**
     * PUBLIC:
     * Remove the user defined property.
     */
    public void removeProperty(String property) {
        getProperties().remove(property);
    }

    /**
     * PUBLIC:
     * Remove all queries with the given queryName regardless of the argument types.
     *
     * @see #removeQuery(String, Vector)
     */
    public void removeQuery(String queryName) {
        getQueries().remove(queryName);
    }

    /**
     * PUBLIC:
     * Remove the specific query with the given queryName and argumentTypes.
     */
    public void removeQuery(String queryName, Vector argumentTypes) {
        Vector queries = (Vector)getQueries().get(queryName);
        if (queries == null) {
            return;
        } else {
            DatabaseQuery query = null;
            for (Enumeration enumtr = queries.elements(); enumtr.hasMoreElements();) {
                query = (DatabaseQuery)enumtr.nextElement();
                if (Helper.areTypesAssignable(argumentTypes, query.getArgumentTypes())) {
                    break;
                }
            }
            if (query != null) {
                queries.remove(query);
            }
        }
    }

    /**
     * PROTECTED:
     * Attempts to rollback the running internally started external transaction.
     * Returns true only in one case -
     * extenal transaction has been internally rolled back during this method call:
     * wasJTSTransactionInternallyStarted()==true in the beginning of this method and
     * wasJTSTransactionInternallyStarted()==false in the end of this method.
     */
    protected boolean rollbackExternalTransaction() {
        boolean externalTransactionHasRolledBack = false;
        if (hasExternalTransactionController() && wasJTSTransactionInternallyStarted()) {
            try {
                getExternalTransactionController().rollbackTransaction(this);
            } catch (RuntimeException exception) {
                handleException(exception);
            }
            if (!wasJTSTransactionInternallyStarted()) {
                externalTransactionHasRolledBack = true;
                log(SessionLog.FINER, SessionLog.TRANSACTION, "external_transaction_has_rolled_back_internally");
            }
        }
        return externalTransactionHasRolledBack;
    }

    /**
     * PUBLIC:
     * Rollback the active database transaction.
     * This allows a group of database modification to be commited or rolledback as a unit.
     * All writes/deletes will be sent to the database be will not be visible to other users until commit.
     * Although databases do not allow nested transaction,
     * TopLink supports nesting through only committing to the database on the outer commit.
     *
     * @exception DatabaseException if the database connection is lost or the rollback fails.
     * @exception ConcurrencyException if this session is not within a transaction.
     */
    public void rollbackTransaction() throws DatabaseException, ConcurrencyException {
        // Ensure release of mutex and call subclass specific release.
        try {
            if (!getTransactionMutex().isNested()) {
                getEventManager().preRollbackTransaction();
                basicRollbackTransaction();
                getEventManager().postRollbackTransaction();
            }
        } finally {
            getTransactionMutex().release();

            // If there is no db transaction in progress
            // if there is an active external transaction 
            // which was started internally - it should be rolled back internally, too.
            if (!isInTransaction()) {
                rollbackExternalTransaction();
            }
        }
    }

    /**
     * INTERNAL:
     * Set the accessor.
     */
    public void setAccessor(Accessor accessor) {
        this.accessor = accessor;
    }

    /**
     * INTERNAL:
     * Allow the session to be used from a session broker.
     */
    public void setBroker(AbstractSession broker) {
        this.broker = broker;
    }

    /**
     * INTERNAL:
     * The commit manager is used to resolve referncial integrity on commits of multiple objects.
     */
    public void setCommitManager(CommitManager commitManager) {
        this.commitManager = commitManager;
    }

    /**
     * INTERNAL:
     * Set the event manager.
     * The event manager can be used to register for various session events.
     */
    public void setEventManager(SessionEventManager eventManager) {
        if (eventManager != null) {
            this.eventManager = eventManager;
        } else {
            this.eventManager = new SessionEventManager();
        }
        this.eventManager.setSession(this);
    }

    /**
     * PUBLIC:
     * Set the exceptionHandler.
     * Exception handler can catch errors that occur on queries or during database access.
     */
    public void setExceptionHandler(ExceptionHandler exceptionHandler) {
        this.exceptionHandler = exceptionHandler;
    }

    /**
     * Used for JTS integration internally by ServerPlatform.
     */
    public void setExternalTransactionController(ExternalTransactionController externalTransactionController) {
        this.externalTransactionController = externalTransactionController;
        if (externalTransactionController == null) {
            return;
        }
        externalTransactionController.setSession(this);
    }

    /**
     * PUBLIC:
     * set the integrityChecker. IntegrityChecker holds all the Descriptor Exceptions.
     */
    public void setIntegrityChecker(IntegrityChecker integrityChecker) {
        this.integrityChecker = integrityChecker;
    }

    /**
     * PUBLIC:
     * Set the writer to which an accessor writes logged messages and SQL.
     * If not set, this reference defaults to a writer on System.out.
     *
     * @see #setSessionLog(SessionLog)
     */
    public void setLog(Writer log) {
        getSessionLog().setWriter(log);
    }

    /**
     * PUBLIC:
     * Set the login.
     */
    public void setLogin(DatabaseLogin login) {
        setDatasourceLogin(login);
    }

    /**
     * PUBLIC:
     * Set the login.
     */
    public void setLogin(Login login) {
        setDatasourceLogin(login);
    }

    /**
     * PUBLIC:
     * Set the login.
     */
    public void setDatasourceLogin(Login login) {
        getProject().setDatasourceLogin(login);
    }

    /**
     * PUBLIC:
     * Set the name of the session.
     * This is used with the session broker.
     */
    public void setName(String name) {
        this.name = name;
    }

    protected void setNumberOfActiveUnitsOfWork(int numberOfActiveUnitsOfWork) {
        this.numberOfActiveUnitsOfWork = numberOfActiveUnitsOfWork;
    }

    /**
     * PUBLIC:
     * Set the profiler for the session.
     * This allows for performance operations to be profiled.
     */
    public void setProfiler(SessionProfiler profiler) {
        this.profiler = profiler;
        if (profiler != null) {
            profiler.setSession(this);
            setIsInProfile(getProfiler().getProfileWeight() != SessionProfiler.NONE);
            // Clear cached flag that bybasses the profiler check.
            getIdentityMapAccessorInstance().getIdentityMapManager().clearCacheAccessPreCheck();
        } else {
            setIsInProfile(false);
        }
    }

    /**
     * INTERNAL:
     * Set the project, the project holds configuartion information including the descriptors.
     */
    public void setProject(oracle.toplink.essentials.sessions.Project project) {
        this.project = project;
    }

    /**
     * INTERNAL:
     * Set the user defined properties.
     */
    public void setProperties(Hashtable properties) {
        this.properties = properties;
    }

    /**
     * PUBLIC:
     * Allow for user defined properties.
     */
    public void setProperty(String propertyName, Object propertyValue) {
        getProperties().put(propertyName, propertyValue);
    }

    protected void setQueries(Hashtable queries) {
        this.queries = queries;
    }

    /**
     * PUBLIC:
     * Set the session log to which an accessor logs messages and SQL.
     * If not set, this will default to a session log on a writer on System.out.
     * To enable logging, log level can not be OFF.
     * Also set a backpointer to this session in SessionLog.  To avoid a sessionLog
     * being shared by more than one session, it needs to be cloned.
     *
     * @see #logMessage(String)
     */
    public void setSessionLog(SessionLog sessionLog) {
        this.sessionLog = (SessionLog)((AbstractSessionLog)sessionLog).clone();
        if (this.sessionLog != null) {
            this.sessionLog.setSession(this);
        }
    }

    protected void setTransactionMutex(ConcurrencyManager transactionMutex) {
        this.transactionMutex = transactionMutex;
    }

    /**
     * INTERNAL:
     * Return if a JTS transaction was started by the session.
     * The session will start a JTS transaction if a unit of work or transaction is begun without a JTS transaction present.
     */
    public void setWasJTSTransactionInternallyStarted(boolean wasJTSTransactionInternallyStarted) {
        this.wasJTSTransactionInternallyStarted = wasJTSTransactionInternallyStarted;
    }

    /**
     * PUBLIC:
     * Return if logging is enabled (false if log level is OFF)
     */
    public boolean shouldLogMessages() {
        if (getLogLevel(null) == SessionLog.OFF) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * INTERNAL:
     * Start the operation timing.
     */
    public void startOperationProfile(String operationName) {
        if (isInProfile()) {
            getProfiler().startOperationProfile(operationName);
        }
    }

    /**
     * Print the connection status with the session.
     */
    public String toString() {
        StringWriter writer = new StringWriter();
        writer.write(getSessionTypeString() + "(" + Helper.cr() + "\t" + getAccessor() + Helper.cr() + "\t" + getDatasourcePlatform() + ")");
        return writer.toString();
    }

    /**
     * INTERNAL:
     * Unwrap the object if required.
     * This is used for the wrapper policy support and EJB.
     */
    public Object unwrapObject(Object proxy) {
        return getDescriptor(proxy).getObjectBuilder().unwrapObject(proxy, this);
    }

    /**
     * PUBLIC:
     * Update the object and all of its privately owned parts in the database.
     * Update should only be used if the application knows that the object is new,
     * otherwise writeObject should be used.
     * The update operation can be customized through using an update query.
     *
     * @exception DatabaseException if an error occurs on the database,
     * these include constraint violations, security violations and general database erros.
     * @exception OptimisticLockException if the object's descriptor is using optimistic locking and
     * the object has been updated or deleted by another user since it was last read.
     *
     * @see UpdateObjectQuery
     * @see #writeObject(Object)
     */
    public Object updateObject(Object domainObject) throws DatabaseException, OptimisticLockException {
        UpdateObjectQuery query = new UpdateObjectQuery();
        query.setObject(domainObject);
        return executeQuery(query);
    }

    /**
     * INTERNAL:
     * This method will be used to update the query with any settings required
     * For this session.  It can also be used to validate execution.
     */
    public void validateQuery(DatabaseQuery query) {
        // a no-op for this class
    }

    /**
     * TESTING:
     * This is used by testing code to ensure that a deletion was successful.
     */
    public boolean verifyDelete(Object domainObject) {
        ObjectBuilder builder = getDescriptor(domainObject).getObjectBuilder();
        Object implementation = builder.unwrapObject(domainObject, this);

        return builder.verifyDelete(implementation, this);
    }

    /**
     * INTERNAL:
     * Return if a JTS transaction was started by the session.
     * The session will start a JTS transaction if a unit of work or transaction is begun without a JTS transaction present.
     */
    public boolean wasJTSTransactionInternallyStarted() {
        return wasJTSTransactionInternallyStarted;
    }

    /**
     * INTERNAL:
     * Wrap the object if required.
     * This is used for the wrapper policy support and EJB.
     */
    public Object wrapObject(Object implementation) {
        return getDescriptor(implementation).getObjectBuilder().wrapObject(implementation, this);
    }

    /**
     * INTERNAL:
     * Write all of the objects and all of their privately owned parts in the database.
     * The allows for a group of new objects to be commited as a unit.
     * The objects will be commited through a single transactions and any
     * foreign keys/circular references between the objects will be resolved.
     */
    protected void writeAllObjects(IdentityHashtable domainObjects) throws DatabaseException, OptimisticLockException {
        getCommitManager().commitAllObjects(domainObjects);
    }

    /**
     * INTERNAL:
     * Write all of the objects and all of their privately owned parts in the database.
     * The allows for a group of new objects to be commited as a unit.
     * The objects will be commited through a single transactions and any
     * foreign keys/circular references between the objects will be resolved.
     */
    protected void writeAllObjectsWithChangeSet(UnitOfWorkChangeSet uowChangeSet) throws DatabaseException, OptimisticLockException {
        getCommitManager().commitAllObjectsWithChangeSet(uowChangeSet);
    }

    /**
     * PUBLIC:
     * Write the object and all of its privately owned parts in the database.
     * Write will determine if an insert or an update should be done,
     * it may go to the database to determine this (by default will check the identity map).
     * The write operation can be customized through using an write query.
     *
     * @exception DatabaseException if an error occurs on the database,
     * these include constraint violations, security violations and general database erros.
     * @exception OptimisticLockException if the object's descriptor is using optimistic locking and
     * the object has been updated or deleted by another user since it was last read.
     *
     * @see WriteObjectQuery
     * @see #insertObject(Object)
     * @see #updateObject(Object)
     */
    public Object writeObject(Object domainObject) throws DatabaseException, OptimisticLockException {
        WriteObjectQuery query = new WriteObjectQuery();
        query.setObject(domainObject);
        return executeQuery(query);
    }

    /**
     * INTERNAL:
     * This method notifies the accessor that a particular sets of writes has
     * completed.  This notification can be used for such thing as flushing the
     * batch mechanism
     */
    public void writesCompleted() {
        getAccessor().writesCompleted(this);
    }

    /**
     * PUBLIC:
     * <p>
     * Return the log level
     * </p><p>
     *
     * @return the log level
     * </p><p>
     * @param category  the string representation of a TopLink category, e.g. "sql", "transaction" ...
     * </p>
     */
    public int getLogLevel(String category) {
        return getSessionLog().getLevel(category);
    }

    /**
     * PUBLIC:
     * <p>
     * Return the log level
     * </p><p>
     * @return the log level
     * </p>
     */
    public int getLogLevel() {
        return getSessionLog().getLevel();
    }

    /**
     * PUBLIC:
     * <p>
     * Set the log level
     * </p><p>
     *
     * @param level     the new log level
     * </p>
     */
    public void setLogLevel(int level) {
        getSessionLog().setLevel(level);
    }

    /**
     * PUBLIC:
     * <p>
     * Check if a message of the given level would actually be logged.
     * </p><p>
     *
     * @return true if the given message level will be logged
     * </p><p>
     * @param level  the log request level
     * @param category  the string representation of a TopLink category
     * </p>
     */
    public boolean shouldLog(int Level, String category) {
        return getSessionLog().shouldLog(Level, category);
    }

    /**
     * PUBLIC:
     * <p>
     * Log a message with level and category that needs to be translated.
     * </p><p>
     *
     * @param level  the log request level value
     * </p><p>
     * @param message  the string message
     * </p><p>
     * @param category  the string representation of a TopLink category.
     * </p>
     */
    public void log(int level, String category, String message) {
        if (!shouldLog(level, category)) {
            return;
        }
        log(level, category, message, (Object[])null);
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, category and a parameter that needs to be translated.
     * </p><p>
     *
     * @param level  the log request level value
     * </p><p>
     * @param message  the string message
     * </p><p>
     * @param category  the string representation of a TopLink category.
     * </p><p>
     * @param param  a parameter of the message
     * </p>
     */
    public void log(int level, String category, String message, Object param) {
        if (!shouldLog(level, category)) {
            return;
        }
        log(level, category, message, new Object[] { param });
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, category and two parameters that needs to be translated.
     * </p><p>
     *
     * @param level  the log request level value
     * </p><p>
     * @param message  the string message
     * </p><p>
     * @param category  the string representation of a TopLink category.
     * </p><p>
     * @param param1  a parameter of the message
     * </p><p>
     * @param param2  second parameter of the message
     * </p>
     */
    public void log(int level, String category, String message, Object param1, Object param2) {
        if (!shouldLog(level, category)) {
            return;
        }
        log(level, category, message, new Object[] { param1, param2 });
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, category and three parameters that needs to be translated.
     * </p><p>
     *
     * @param level  the log request level value
     * </p><p>
     * @param message  the string message
     * </p><p>
     * @param category  the string representation of a TopLink category.
     * </p><p>
     * @param param1  a parameter of the message
     * </p><p>
     * @param param2  second parameter of the message
     * </p><p>
     * @param param3  third parameter of the message
     * </p>
     */
    public void log(int level, String category, String message, Object param1, Object param2, Object param3) {
        if (!shouldLog(level, category)) {
            return;
        }
        log(level, category, message, new Object[] { param1, param2, param3 });
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, category and an array of parameters that needs to be translated.
     * </p><p>
     *
     * @param level  the log request level value
     * </p><p>
     * @param message  the string message
     * </p><p>
     * @param category  the string representation of a TopLink category.
     * </p><p>
     * @param params  array of parameters to the message
     * </p>
     */
    public void log(int level, String category, String message, Object[] params) {
        log(level, category, message, params, null);
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, category, parameters and accessor that needs to be translated.
     * </p><p>
     *
     * @param level  the log request level value
     * </p><p>
     * @param message  the string message
     * </p><p>
     * @param params  array of parameters to the message
     * </p><p>
     * @param accessor  the connection that generated the log entry
     * </p><p>
     * @param category  the string representation of a TopLink category.
     * </p>
     */
    public void log(int level, String category, String message, Object[] params, Accessor accessor) {
        log(level, category, message, params, accessor, true);
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, category, parameters and accessor.  shouldTranslate determines if the message needs to be translated.
     * </p><p>
     *
     * @param level  the log request level value
     * </p><p>
     * @param message  the string message
     * </p><p>
     * @param params  array of parameters to the message
     * </p><p>
     * @param accessor  the connection that generated the log entry
     * </p><p>
     * @param category  the string representation of a TopLink category.
     * </p><p>
     * @param shouldTranslate  true if the message needs to be translated.
     * </p>
     */
    public void log(int level, String category, String message, Object[] params, Accessor accessor, boolean shouldTranslate) {
        if (shouldLog(level, category)) {
            startOperationProfile(SessionProfiler.Logging);
            log(new SessionLogEntry(level, category, this, message, params, accessor, shouldTranslate));
            endOperationProfile(SessionProfiler.Logging);
        }
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, parameters and accessor that needs to be translated.
     * </p><p>
     *
     * @param level  the log request level value
     * </p><p>
     * @param message  the string message
     * </p><p>
     * @param params  array of parameters to the message
     * </p><p>
     * @param accessor  the connection that generated the log entry
     * </p>
     */
    public void log(int level, String message, Object[] params, Accessor accessor) {
        log(level, message, params, accessor, true);
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, parameters and accessor.  shouldTranslate determines if the message needs to be translated.
     * </p><p>
     *
     * @param level  the log request level value
     * </p><p>
     * @param message  the string message
     * </p><p>
     * @param params  array of parameters to the message
     * </p><p>
     * @param accessor  the connection that generated the log entry
     * </p><p>
     * @param shouldTranslate  true if the message needs to be translated.
     * </p>
     */
    public void log(int level, String message, Object[] params, Accessor accessor, boolean shouldTranslate) {
        if (shouldLog(level, null)) {
            startOperationProfile(SessionProfiler.Logging);
            log(new SessionLogEntry(level, this, message, params, accessor, shouldTranslate));
            endOperationProfile(SessionProfiler.Logging);
        }
    }

    /**
     * PUBLIC:
     * <p>
     * Log a throwable with level and category.
     * </p><p>
     *
     * @param level  the log request level value
     * </p><p>
     * @param category  the string representation of a TopLink category.
     * </p><p>
     * @param throwable  a Throwable
     * </p>
     */
    public void logThrowable(int level, String category, Throwable throwable) {
        // Must not create the log if not logging as is a performance issue.
        if (shouldLog(level, category)) {
            startOperationProfile(SessionProfiler.Logging);
            log(new SessionLogEntry(this, level, category, throwable));
            endOperationProfile(SessionProfiler.Logging);
        }
    }

    /**
     * PUBLIC:
     * <p>
     * This method is called when a severe level message needs to be logged.
     * The message will be translated
     * </p><p>
     *
     * @param message  the message key
     * </p>
     */
    public void severe(String message, String category) {
        log(SessionLog.SEVERE, category, message);
    }

    /**
     * PUBLIC:
     * <p>
     * This method is called when a warning level message needs to be logged.
     * The message will be translated
     * </p><p>
     *
     * @param message  the message key
     * </p>
     */
    public void warning(String message, String category) {
        log(SessionLog.WARNING, category, message);
    }

    /**
     * PUBLIC:
     * <p>
     * This method is called when a info level message needs to be logged.
     * The message will be translated
     * </p><p>
     *
     * @param message  the message key
     * </p>
     */
    public void info(String message, String category) {
        log(SessionLog.INFO, category, message);
    }

    /**
     * PUBLIC:
     * <p>
     * This method is called when a config level message needs to be logged.
     * The message will be translated
     * </p><p>
     *
     * @param message  the message key
     * </p>
     */
    public void config(String message, String category) {
        log(SessionLog.CONFIG, category, message);
    }

    /**
     * PUBLIC:
     * <p>
     * This method is called when a fine level message needs to be logged.
     * The message will be translated
     * </p><p>
     *
     * @param message  the message key
     * </p>
     */
    public void fine(String message, String category) {
        log(SessionLog.FINE, category, message);
    }

    /**
     * PUBLIC:
     * <p>
     * This method is called when a finer level message needs to be logged.
     * The message will be translated
     * </p><p>
     *
     * @param message  the message key
     * </p>
     */
    public void finer(String message, String category) {
        log(SessionLog.FINER, category, message);
    }

    /**
     * PUBLIC:
     * <p>
     * This method is called when a finest level message needs to be logged.
     * The message will be translated
     * </p><p>
     *
     * @param message  the message key
     * </p>
     */
    public void finest(String message, String category) {
        log(SessionLog.FINEST, category, message);
    }

    /**
     * PUBLIC:
     * Allow any SEVERE level exceptions that occur within TopLink to be logged and handled by the exception handler.
     */
    public Object handleSevere(RuntimeException exception) throws RuntimeException {
        logThrowable(SessionLog.SEVERE, null, exception);
        if (hasExceptionHandler()) {
            return getExceptionHandler().handleException(exception);
        } else {
            throw exception;
        }
    }
}
