/*******************************************************************************
 * Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
 * which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 *
 *     05/28/2008-1.0M8 Andrei Ilitchev
 *        - 224964: Provide support for Proxy Authentication through JPA.
 *        Added setProperties method to be used in case properties couldn't be passed to createEM method.
 *        The properties now set to the uow's parent - not to the uow itself.
 *        In case there's no active transaction, close method now releases uow.
 *        UowImpl was amended to allow value holders instantiation even after it has been released,
 *        the parent ClientSession is released, too.
 *     03/19/2009-2.0 Michael O'Brien  
 *       - 266912: JPA 2.0 Metamodel API (part of the JSR-317 EJB 3.1 Criteria API)
 *     07/13/2009-2.0 Guy Pelletier 
 *       - 277039: JPA 2.0 Cache Usage Settings
 *     02/08/2012-2.4 Guy Pelletier 
 *       - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
 *     14/05/2012-2.4 Guy Pelletier  
 *       - 376603: Provide for table per tenant support for multitenant applications
 *     06/20/2012-2.5 Guy Pelletier 
 *       - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
 *     08/24/2012-2.5 Guy Pelletier 
 *       - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
 *     09/13/2012-2.5 Guy Pelletier 
 *       - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
 *     08/11/2012-2.5 Guy Pelletier  
 *       - 393867: Named queries do not work when using EM level Table Per Tenant Multitenancy.
 ******************************************************************************/
package org.eclipse.persistence.internal.jpa;

import java.util.*;

import javax.persistence.*;
import javax.persistence.OptimisticLockException;
import javax.persistence.criteria.CriteriaDelete;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaUpdate;
import javax.persistence.metamodel.Metamodel;
import javax.sql.DataSource;

import org.eclipse.persistence.annotations.CacheKeyType;
import org.eclipse.persistence.config.*;
import org.eclipse.persistence.descriptors.*;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.internal.databaseaccess.Accessor;
import org.eclipse.persistence.internal.descriptors.OptimisticLockingPolicy;
import org.eclipse.persistence.internal.helper.BasicTypeHelperImpl;
import org.eclipse.persistence.internal.identitymaps.CacheId;
import org.eclipse.persistence.internal.jpa.querydef.CriteriaQueryImpl;
import org.eclipse.persistence.internal.jpa.querydef.CriteriaUpdateImpl;
import org.eclipse.persistence.internal.jpa.querydef.CriteriaDeleteImpl;
import org.eclipse.persistence.internal.jpa.transaction.*;
import org.eclipse.persistence.internal.localization.ExceptionLocalization;
import org.eclipse.persistence.internal.sessions.*;
import org.eclipse.persistence.jpa.JpaEntityManager;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.queries.*;
import org.eclipse.persistence.sessions.*;
import org.eclipse.persistence.sessions.UnitOfWork.CommitOrderType;
import org.eclipse.persistence.sessions.broker.SessionBroker;
import org.eclipse.persistence.sessions.factories.SessionManager;
import org.eclipse.persistence.sessions.server.ConnectionPolicy;
import org.eclipse.persistence.sessions.server.Server;
import org.eclipse.persistence.sessions.server.ServerSession;

/**
 * <p>
 * <b>Purpose</b>: Contains the implementation of the EntityManager.
 * <p>
 * <b>Description</b>: This class provides the implementation for the combined
 * EclipseLink and JPA EntityManager class.
 * <p>
 * <b>Responsibilities</b>: It is responsible for tracking transaction state and
 * the objects within that transaction.
 * 
 * @see javax.persistence.EntityManager
 * @see org.eclipse.persistence.jpa.JpaEntityManager
 * 
 * @author gyorke
 * @since TopLink Essentials - JPA 1.0
 */
public class EntityManagerImpl implements org.eclipse.persistence.jpa.JpaEntityManager {
    protected enum OperationType {FIND, REFRESH, LOCK};
    
    /** Allows transparent transactions across JTA and local transactions. */
    protected TransactionWrapperImpl transaction;

    /** Store if this entity manager has been closed. */
    protected boolean isOpen;

    /** Stores the UnitOfWork representing the persistence context. */
    protected RepeatableWriteUnitOfWork extendedPersistenceContext;

    /** Stores a session used for read-only queries. */
    protected AbstractSession readOnlySession;
    
    /**
     * References the DatabaseSession that this deployment is using.
     */
    protected AbstractSession databaseSession;

    /**
     * References to the parent factory that has created this entity manager.
     * Ensures that the factory is not garbage collected.
     */
    protected EntityManagerFactoryDelegate factory;

    /**
     * Join existing transaction property, allows reading through write
     * connection.
     */
    protected boolean beginEarlyTransaction;

    /** Local properties passed from createEntityManager. */
    protected Map properties;

    /** Flush mode property, allows flush before query to be avoided. */
    protected FlushModeType flushMode;

    /**
     * Reference mode property, allows weak unit of work references to allow
     * garbage collection during a transaction.
     */
    protected ReferenceMode referenceMode;

    /**
     * Connection policy used to create ClientSession, allows using a different
     * pool/connection/exclusive connections.
     * Not used in SessionBroker case (composite persistence unit case).
     */
    protected ConnectionPolicy connectionPolicy;

    /**
     * In case of composite persistence unit this map is used instead of connectionPolicy attribute.
     * Member sessions' ConnectionPolicies keyed by sessions' names (composite members' persistence unit names).
     * Used only in SessionBroker case (composite persistence unit case): in that case guaranteed to be always non null.
     */
    protected Map<String, ConnectionPolicy> connectionPolicies;

    /**
     * Keep a list of openQueries that are executed in this entity manager.
     */
    protected WeakHashMap<QueryImpl, QueryImpl> openQueriesMap;
    
    /**
     * Property to avoid resuming unit of work if going to be closed on commit
     * anyway.
     */
    protected boolean closeOnCommit;

    /**
     * Property to avoid discover new objects in unit of work if application
     * always uses persist.
     */
    protected boolean persistOnCommit;
    
    /**
     * Property to avoid writing to the cache on commit (merge)
     */
    protected boolean cacheStoreBypass;

    /**
     * The FlashClearCache mode to be used. Relevant only in case call to flush
     * method followed by call to clear method.
     * 
     * @see org.eclipse.persistence.config.FlushClearCache
     */
    protected String flushClearCache;

    /** Determine if does-exist should be performed on persist. */
    protected boolean shouldValidateExistence;

    /** Allow updates to be ordered by id to avoid possible deadlocks. */
    protected org.eclipse.persistence.sessions.UnitOfWork.CommitOrderType commitOrder;
    
    protected boolean commitWithoutPersistRules;
    
    /** Tracks if this EntityManager should automatically associate with the transaction or not*/
    protected SynchronizationType syncType;
        
    abstract static class PropertyProcessor {
        abstract void process(String name, Object value, EntityManagerImpl em);
    }
    static Map<String, PropertyProcessor> processors = new HashMap() {
        {
            put(EntityManagerProperties.JOIN_EXISTING_TRANSACTION, new PropertyProcessor() {void process(String name, Object value, EntityManagerImpl em) {
                em.beginEarlyTransaction = "true".equalsIgnoreCase(getPropertiesHandlerProperty(name, (String)value));
            }});
            put(EntityManagerProperties.PERSISTENCE_CONTEXT_REFERENCE_MODE, new PropertyProcessor() {void process(String name, Object value, EntityManagerImpl em) {
                em.referenceMode = ReferenceMode.valueOf(getPropertiesHandlerProperty(name, (String)value));
                if (em.hasActivePersistenceContext()) {
                    em.extendedPersistenceContext.log(SessionLog.WARNING, SessionLog.PROPERTIES, "entity_manager_sets_property_while_context_is_active", new Object[]{name});
                }
            }});
            put(EntityManagerProperties.PERSISTENCE_CONTEXT_FLUSH_MODE, new PropertyProcessor() {void process(String name, Object value, EntityManagerImpl em) {
                em.flushMode = FlushModeType.valueOf(getPropertiesHandlerProperty(name, (String)value));
            }});
            put(EntityManagerProperties.PERSISTENCE_CONTEXT_CLOSE_ON_COMMIT, new PropertyProcessor() {void process(String name, Object value, EntityManagerImpl em) {
                em.closeOnCommit = "true".equalsIgnoreCase(getPropertiesHandlerProperty(name, (String)value));
                if(em.hasActivePersistenceContext()) {
                    em.extendedPersistenceContext.setResumeUnitOfWorkOnTransactionCompletion(!em.closeOnCommit);
                }
            }});
            put(EntityManagerProperties.PERSISTENCE_CONTEXT_PERSIST_ON_COMMIT, new PropertyProcessor() {void process(String name, Object value, EntityManagerImpl em) {
                em.persistOnCommit = "true".equalsIgnoreCase(getPropertiesHandlerProperty(name, (String)value));
                if(em.hasActivePersistenceContext()) {
                    em.extendedPersistenceContext.setShouldDiscoverNewObjects(em.persistOnCommit);
                }
            }});
            put(EntityManagerProperties.PERSISTENCE_CONTEXT_COMMIT_WITHOUT_PERSIST_RULES, new PropertyProcessor() {void process(String name, Object value, EntityManagerImpl em) {
                em.commitWithoutPersistRules = "true".equalsIgnoreCase(getPropertiesHandlerProperty(name, (String)value));
                if (em.hasActivePersistenceContext()) {
                    em.extendedPersistenceContext.setDiscoverUnregisteredNewObjectsWithoutPersist(em.commitWithoutPersistRules);
                }
            }});
            put(EntityManagerProperties.VALIDATE_EXISTENCE, new PropertyProcessor() {void process(String name, Object value, EntityManagerImpl em) {
                em.shouldValidateExistence = "true".equalsIgnoreCase(getPropertiesHandlerProperty(name, (String)value));
                if (em.hasActivePersistenceContext()) {
                    em.extendedPersistenceContext.setShouldValidateExistence(em.shouldValidateExistence);
                }
            }});
            put(EntityManagerProperties.ORDER_UPDATES, new PropertyProcessor() {void process(String name, Object value, EntityManagerImpl em) {
                if ("true".equalsIgnoreCase(getPropertiesHandlerProperty(name, (String)value))) {
                    em.commitOrder = CommitOrderType.ID;
                } else {
                    em.commitOrder = CommitOrderType.NONE;
                }
                if (em.hasActivePersistenceContext()) {
                    em.extendedPersistenceContext.setCommitOrder(em.commitOrder);
                }
            }});
            put(EntityManagerProperties.PERSISTENCE_CONTEXT_COMMIT_ORDER, new PropertyProcessor() {void process(String name, Object value, EntityManagerImpl em) {
                em.commitOrder = CommitOrderType.valueOf(getPropertiesHandlerProperty(name, (String)value).toUpperCase());
                if (em.hasActivePersistenceContext()) {
                    em.extendedPersistenceContext.setCommitOrder(em.commitOrder);
                }
            }});
            put(EntityManagerProperties.FLUSH_CLEAR_CACHE, new PropertyProcessor() {void process(String name, Object value, EntityManagerImpl em) {
                em.flushClearCache = getPropertiesHandlerProperty(name, (String)value);
                if( em.hasActivePersistenceContext()) {
                    em.extendedPersistenceContext.setFlushClearCache(em.flushClearCache);
                }
            }});
            put(QueryHints.CACHE_STORE_MODE, new PropertyProcessor() {void process(String name, Object value, EntityManagerImpl em) {
                // This property could be a string or an enum.
                em.cacheStoreBypass = value.equals(CacheStoreMode.BYPASS) || value.equals(CacheStoreMode.BYPASS.name());
                if(em.hasActivePersistenceContext()) {
                    em.extendedPersistenceContext.setShouldStoreByPassCache(em.cacheStoreBypass);
                }
            }});

            PropertyProcessor connectionPolicyPropertyProcessor = new PropertyProcessor() {void process(String name, Object value, EntityManagerImpl em) {
                // Property used to create ConnectionPolicy has changed - already existing ConnectionPolicy should be removed.
                // A new one will be created when the new active persistence context is created.
                em.connectionPolicy = null;
                if (em.hasActivePersistenceContext()) {
                    em.extendedPersistenceContext.log(SessionLog.WARNING, SessionLog.PROPERTIES, "entity_manager_sets_property_while_context_is_active", new Object[]{name});
                }
            }};
            put(EntityManagerProperties.EXCLUSIVE_CONNECTION_MODE, connectionPolicyPropertyProcessor);
            put(EntityManagerProperties.EXCLUSIVE_CONNECTION_IS_LAZY, connectionPolicyPropertyProcessor);
            put(EntityManagerProperties.JTA_DATASOURCE, connectionPolicyPropertyProcessor);
            put(EntityManagerProperties.NON_JTA_DATASOURCE, connectionPolicyPropertyProcessor);
            put(EntityManagerProperties.JDBC_DRIVER, connectionPolicyPropertyProcessor);
            put(EntityManagerProperties.JDBC_URL, connectionPolicyPropertyProcessor);
            put(EntityManagerProperties.JDBC_USER, connectionPolicyPropertyProcessor);
            put(EntityManagerProperties.JDBC_PASSWORD, connectionPolicyPropertyProcessor);
            put(EntityManagerProperties.CONNECTION_POLICY, connectionPolicyPropertyProcessor);

            put(EntityManagerProperties.ORACLE_PROXY_TYPE, new PropertyProcessor() {void process(String name, Object value, EntityManagerImpl em) {
                if (em.hasActivePersistenceContext()) {
                    em.extendedPersistenceContext.log(SessionLog.WARNING, SessionLog.PROPERTIES, "entity_manager_sets_property_while_context_is_active", new Object[]{name});
                }
            }});

            put(EntityManagerProperties.COMPOSITE_UNIT_PROPERTIES, new PropertyProcessor() {void process(String name, Object value, EntityManagerImpl em) {
                if( em.connectionPolicies != null) {
                    Map mapOfProperties = (Map)value;
                    Iterator it = mapOfProperties.keySet().iterator();
                    while (it.hasNext()) {
                        String sessionName = (String)it.next();
                        if (em.connectionPolicies.containsKey(sessionName)) {
                            // Property used to create ConnectionPolicy has changed - already existing ConnectionPolicy should be removed.
                            // A new one will be created when the new active persistence context is created.
                            em.connectionPolicies.put(sessionName, null);
                        }
                    }
                    if (em.hasActivePersistenceContext()) {
                        em.extendedPersistenceContext.log(SessionLog.WARNING, SessionLog.PROPERTIES, "entity_manager_sets_property_while_context_is_active", new Object[]{name});
                    }
                }
            }});
        }
    };

    /**
     * Constructor returns an EntityManager assigned to the a particular
     * DatabaseSession.
     * 
     * @param sessionName
     *            the DatabaseSession name that should be used. This constructor
     *            can potentially throw EclipseLink exceptions regarding the
     *            existence, or errors with the specified session.
     */
    public EntityManagerImpl(String sessionName) {
        this(SessionManager.getManager().getSession(sessionName), null, SynchronizationType.SYNCHRONIZED);
    }

    /** 
     * Return the weak reference to the open queries.
     */
    protected Map<QueryImpl, QueryImpl> getOpenQueriesMap() {
        if (openQueriesMap == null) {
            openQueriesMap = new WeakHashMap<QueryImpl, QueryImpl>();
        }
        
        return openQueriesMap;
    }
    
    /** 
     * Return the weak reference to the open queries.
     */
    protected Set<QueryImpl> getOpenQueriesSet() {
        return getOpenQueriesMap().keySet();
    }
    
    /**
     * Queries that leave the connection and are executed against this entity
     * manager will be added here. On rollback or commit any left over open 
     * queries should be closed.
     * 
     * @param query
     */
    public void addOpenQuery(QueryImpl query) {
        getOpenQueriesMap().put(query, query);
    	
        // If there is an open entity transaction, tag the query to it to be closed
        // on commit or rollback.
        Object transaction = checkForTransaction(false);        
        if (transaction != null && transaction instanceof EntityTransactionImpl) {
            ((EntityTransactionImpl) transaction).addOpenQuery(query);
        }
    }
    
    /**
     * Constructor called from the EntityManagerFactory to create an
     * EntityManager
     * 
     * @param databaseSession
     *            the databaseSession assigned to this deployment.
     */
    public EntityManagerImpl(AbstractSession databaseSession, SynchronizationType syncType) {
        this(databaseSession, null, syncType);
    }

    /**
     * Constructor called from the EntityManagerFactory to create an
     * EntityManager
     * 
     * @param databaseSession
     *            the databaseSession assigned to this deployment. Note: The
     *            properties argument is provided to allow properties to be
     *            passed into this EntityManager, but there are currently no
     *            such properties implemented
     */
    public EntityManagerImpl(AbstractSession databaseSession, Map properties, SynchronizationType syncType) {
        this(new EntityManagerFactoryImpl(databaseSession).unwrap(), properties, syncType);
    }

    /**
     * Constructor called from the EntityManagerFactory to create an
     * EntityManager
     * 
     * @param factory
     *            the EntityMangerFactoryImpl that created this entity manager.
     *            Note: The properties argument is provided to allow properties
     *            to be passed into this EntityManager, but there are currently
     *            no such properties implemented
     */
    public EntityManagerImpl(EntityManagerFactoryDelegate factory, Map properties, SynchronizationType syncType) {
        this.factory = factory;
        this.databaseSession = factory.getAbstractSession();
        this.beginEarlyTransaction = factory.getBeginEarlyTransaction();
        this.closeOnCommit = factory.getCloseOnCommit();
        this.flushMode = factory.getFlushMode();
        this.persistOnCommit = factory.getPersistOnCommit();
        this.commitWithoutPersistRules = factory.getCommitWithoutPersistRules();
        this.referenceMode = factory.getReferenceMode();
        this.flushClearCache = factory.getFlushClearCache();
        this.shouldValidateExistence = factory.shouldValidateExistence();
        this.commitOrder = factory.getCommitOrder();
        this.isOpen = true;
        this.cacheStoreBypass = false;
        this.syncType = syncType;
        initialize(properties);
    }

    /**
     * Initialize the state after construction.
     */
    protected void initialize(Map properties) {
        detectTransactionWrapper();
        // Cache default ConnectionPolicy. If ConnectionPolicy property(ies) specified 
        // then connectionPolicy will be set to null and re-created when when active persistence context is created. 
        if(this.databaseSession.isServerSession()) {
            this.connectionPolicy = ((ServerSession)this.databaseSession).getDefaultConnectionPolicy();
        } else if (this.databaseSession.isBroker()) {
            SessionBroker broker = (SessionBroker)this.databaseSession;
            this.connectionPolicies = new HashMap(broker.getSessionsByName().size());
            Iterator<Map.Entry<String, AbstractSession>> it = broker.getSessionsByName().entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry<String, AbstractSession> entry = it.next();
                this.connectionPolicies.put(entry.getKey(), ((ServerSession)entry.getValue()).getDefaultConnectionPolicy());
            }
        }
        // bug 236249: In JPA session.setProperty() throws
        // UnsupportedOperationException.
        if (properties != null) {
            this.properties = new HashMap(properties);
            if(!properties.isEmpty()) {
                processProperties();
            }
        }
    }

    /**
     * Clear the persistence context, causing all managed entities to become
     * detached. Changes made to entities that have not been flushed to the
     * database will not be persisted.
     */
    public void clear() {
        try {
            verifyOpen();
            if (this.extendedPersistenceContext != null) {
                if (checkForTransaction(false) == null) {
                    //     259993: WebSphere 7.0 during a JPAEMPool.putEntityManager() afterCompletion callback
                    // may attempt to clear an entityManager in lifecyle/state 4 with a transaction commit active  
                    // that is in the middle of a commit for an insert or update by calling em.clear(true).  
                    //     We only clear the entityManager if we are in the states 
                    // (Birth == 0, WriteChangesFailed==3, Death==5 or AfterExternalTransactionRolledBack==6).
                    // If we are in one of the following *Pending states (1,2 and 4) we defer the clear() to the release() call later.
                    // Note: the single state CommitTransactionPending==2 may never happen as a result of an em.clear
                    if(this.extendedPersistenceContext.isSynchronized() && 
                            ( this.extendedPersistenceContext.isCommitPending() 
                            || this.extendedPersistenceContext.isAfterWriteChangesButBeforeCommit() 
                            || this.extendedPersistenceContext.isMergePending())) {
                        // when jta transaction afterCompleteion callback will have completed merge,
                        // the uow will be released.
                        // Change sets will be cleared, but the cache will be kept.
                        // uow still could be used for instantiating of ValueHolders
                        // after it's released.
                        extendedPersistenceContext.setResumeUnitOfWorkOnTransactionCompletion(false);
                    } else {                    
                        // clear all change sets and cache
                        this.extendedPersistenceContext.clearForClose(true);
                        this.extendedPersistenceContext.release();
                        this.extendedPersistenceContext.getParent().release();
                    }
                    this.extendedPersistenceContext = null;
                } else {
                    // clear all change sets created after the last flush and
                    // cache
                    this.extendedPersistenceContext.clear(true);
                }
            }
        } catch (RuntimeException exception) {
            setRollbackOnly();
            throw exception;
        }
    }

    /**
     * Internal method called by EntityTransactionImpl class in case of
     * transaction rollback. The caller is responsible for releasing
     * extendedPersistenceContext and it's parent.
     */
    public void removeExtendedPersistenceContext() {
        this.extendedPersistenceContext = null;
    }

    /**
     * If in a transaction this method will check for existence and register the
     * object if it is new. The instance of the entity provided will become
     * managed.
     * 
     * @param entity
     * @throws IllegalArgumentException
     *             if the given Object is not an entity.
     */
    public void persist(Object entity) {
        try {
            verifyOpen();
            if (entity == null) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { entity }));
            }
            try {
                getActivePersistenceContext(checkForTransaction(false)).registerNewObjectForPersist(entity, new IdentityHashMap());
            } catch (RuntimeException exception) {
                if (exception instanceof ValidationException) {
                    throw new EntityExistsException(exception.getLocalizedMessage(), exception);
                }
                throw exception;
            }
        } catch (RuntimeException exception) {
            setRollbackOnly();
            throw exception;
        }
    }

    /**
     * Merge the state of the given entity into the current persistence context,
     * using the unqualified class name as the entity name.
     * 
     * @param entity
     * @return the instance that the state was merged to
     */
    public <T> T merge(T entity) {
        try {
            verifyOpen();
            return (T) mergeInternal(entity);
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * Merge the state of the given entity into the current persistence context,
     * using the unqualified class name as the entity name.
     * 
     * @param entity
     * @return the instance that the state was merged to
     * @throws IllegalArgumentException
     *             if given Object is not an entity or is a removed entity
     */
    protected Object mergeInternal(Object entity) {
        if (entity == null) {
            throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { entity }));
        }
        Object merged = null;
        UnitOfWorkImpl context = getActivePersistenceContext(checkForTransaction(false));
        try {
            merged = context.mergeCloneWithReferences(entity, MergeManager.CASCADE_BY_MAPPING, true);
        } catch (org.eclipse.persistence.exceptions.OptimisticLockException ole) {
            throw new javax.persistence.OptimisticLockException(ole);
        }
        return merged;
    }

    /**
     * Remove the instance.
     * 
     * @param entity
     * @throws IllegalArgumentException
     *             if Object passed in is not an entity
     */
    public void remove(Object entity) {
        try {
            verifyOpen();
            if (entity == null) { // gf732 - check for null
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { entity }));
            }
            try {
                getActivePersistenceContext(checkForTransaction(false)).performRemove(entity, new IdentityHashMap());
            } catch (RuntimeException e) {
                throw e;
            }
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * Find by primary key.
     * 
     * @param entityClass
     *            - the entity class to find.
     * @param primaryKey
     *            - the entity primary key value, or primary key class, or a
     *            List of primary key values.
     * @return the found entity instance or null if the entity does not exist
     * @throws IllegalArgumentException
     *             if the first argument does not denote an entity type or the
     *             second argument is not a valid type for that entity's primary
     *             key.
     */
    public <T> T find(Class<T> entityClass, Object primaryKey) {
        return find(entityClass, primaryKey, null, getQueryHints(entityClass, OperationType.FIND));
    }

    /**
     * Find by primary key, using the specified properties. Search for an entity
     * of the specified class and primary key. If the entity instance is
     * contained in the persistence context it is returned from there. If a
     * vendor-specific property or hint is not recognized, it is silently
     * ignored.
     * 
     * @param entityClass
     * @param primaryKey
     * @param properties
     *            standard and vendor-specific properties
     * @return the found entity instance or null if the entity does not exist
     * @throws IllegalArgumentException
     *             if the first argument does not denote an entity type or the
     *             second argument is is not a valid type for that entity's
     *             primary key or is null
     * 
     * @since Java Persistence API 2.0
     */
    public <T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties) {
        return find(entityClass, primaryKey, null, properties);
    }

    /**
     * Find by primary key and lock. Search for an entity of the specified class
     * and primary key and lock it with respect to the specified lock type. If
     * the entity instance is contained in the persistence context it is
     * returned from there. If the entity is found within the persistence
     * context and the lock mode type is pessimistic and the entity has a
     * version attribute, the persistence provider must perform optimistic
     * version checks when obtaining the database lock. If these checks fail,
     * the OptimisticLockException will be thrown. If the lock mode type is
     * pessimistic and the entity instance is found but cannot be locked: - the
     * PessimisticLockException will be thrown if the database locking failure
     * causes transaction-level rollback. - the LockTimeoutException will be
     * thrown if the database locking failure causes only statement-level
     * rollback
     * 
     * @param entityClass
     * @param primaryKey
     * @param lockMode
     * @return the found entity instance or null if the entity does not exist
     * @throws IllegalArgumentException
     *             if the first argument does not denote an entity type or the
     *             second argument is not a valid type for that entity's primary
     *             key or is null
     * @throws TransactionRequiredException
     *             if there is no transaction and a lock mode other than NONE is
     *             set
     * @throws OptimisticLockException
     *             if the optimistic version check fails
     * @throws PessimisticLockException
     *             if pessimistic locking fails and the transaction is rolled
     *             back
     * @throws LockTimeoutException
     *             if pessimistic locking fails and only the statement is rolled
     *             back
     * @throws PersistenceException
     *             if an unsupported lock call is made
     */
    public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode) {
        return find(entityClass, primaryKey, lockMode, getQueryHints(entityClass, OperationType.FIND));
    }

    /**
     * Find by primary key and lock. Search for an entity of the specified class
     * and primary key and lock it with respect to the specified lock type. If
     * the entity instance is contained in the persistence context it is
     * returned from there. If the entity is found within the persistence
     * context and the lock mode type is pessimistic and the entity has a
     * version attribute, the persistence provider must perform optimistic
     * version checks when obtaining the database lock. If these checks fail,
     * the OptimisticLockException will be thrown. If the lock mode type is
     * pessimistic and the entity instance is found but cannot be locked: - the
     * PessimisticLockException will be thrown if the database locking failure
     * causes transaction-level rollback. - the LockTimeoutException will be
     * thrown if the database locking failure causes only statement-level
     * rollback If a vendor-specific property or hint is not recognized, it is
     * silently ignored. Portable applications should not rely on the standard
     * timeout hint. Depending on the database in use and the locking mechanisms
     * used by the provider, the hint may or may not be observed.
     * 
     * @param entityClass
     * @param primaryKey
     * @param lockMode
     * @param properties
     *            standard and vendor-specific properties and hints
     * @return the found entity instance or null if the entity does not exist
     * @throws IllegalArgumentException
     *             if the first argument does not denote an entity type or the
     *             second argument is not a valid type for that entity's primary
     *             key or is null
     * @throws TransactionRequiredException
     *             if there is no transaction and a lock mode other than NONE is
     *             set
     * @throws OptimisticLockException
     *             if the optimistic version check fails
     * @throws PessimisticLockException
     *             if pessimistic locking fails and the transaction is rolled
     *             back
     * @throws LockTimeoutException
     *             if pessimistic locking fails and only the statement is rolled
     *             back
     * @throws PersistenceException
     *             if an unsupported lock call is made
     */
    public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode, Map<String, Object> properties) {
        try {
            verifyOpen();
            if (lockMode != null && !lockMode.equals(LockModeType.NONE)){
                checkForTransaction(true);
            }
            AbstractSession session = this.databaseSession;
            ClassDescriptor descriptor = session.getDescriptor(entityClass);
            // PERF: Avoid uow creation for read-only.
            if (descriptor == null || descriptor.isDescriptorTypeAggregate()) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("unknown_bean_class", new Object[] { entityClass }));
            }
            if (!descriptor.shouldBeReadOnly() || !descriptor.isSharedIsolation()) {
                session = (AbstractSession) getActiveSession();
            } else {
                session = (AbstractSession) getReadOnlySession();
            }

            // Be sure to use the descriptor from the active session.
            if (descriptor.hasTablePerMultitenantPolicy()) {
                descriptor = session.getDescriptor(entityClass);
            }
            return (T) findInternal(descriptor, session, primaryKey, lockMode, properties);
        } catch (LockTimeoutException e) {
            throw e;
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * Find by primary key.
     * 
     * @param entityClass
     *            - the entity class to find.
     * @param primaryKey
     *            - the entity primary key value, or primary key class, or a
     *            List of primary key values.
     * @return the found entity instance or null, if the entity does not exist.
     * @throws IllegalArgumentException
     *             if the first argument does not indicate an entity or if the
     *             second argument is not a valid type for that entity's
     *             primaryKey.
     */
    public Object find(String entityName, Object primaryKey) {
        try {
            verifyOpen();
            AbstractSession session = (AbstractSession) getActiveSession();
            ClassDescriptor descriptor = session.getDescriptorForAlias(entityName);
            if (descriptor == null || descriptor.isDescriptorTypeAggregate()) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("unknown_entitybean_name", new Object[] { entityName }));
            }
            return findInternal(descriptor, session, primaryKey, null, null);
        } catch (LockTimeoutException e) {
            throw e;
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * Find by primary key.
     * 
     * @param entityClass
     *            - the entity class to find.
     * @param primaryKey
     *            - the entity primary key value, or primary key class, or a
     *            List of primary key values.
     * @return the found entity instance or null, if the entity does not exist.
     * @throws IllegalArgumentException
     *             if the first argument does not denote an entity type or the
     *             second argument is not a valid type for that entity's primary
     *             key.
     */
    protected Object findInternal(ClassDescriptor descriptor, AbstractSession session, Object id, LockModeType lockMode, Map<String, Object> properties) {
        if (id == null) { // gf721 - check for null PK
            throw new IllegalArgumentException(ExceptionLocalization.buildMessage("null_pk"));
        }

        Object primaryKey;
        if (id instanceof List) {
            if (descriptor.getCacheKeyType() == CacheKeyType.ID_VALUE) {
                if (((List)id).isEmpty()) {
                    primaryKey = null;
                } else {
                    primaryKey = ((List)id).get(0);
                }
            } else {
                primaryKey = new CacheId(((List)id).toArray());
            }
        } else if (id instanceof CacheId) {
            primaryKey = id;
        } else {
            CMPPolicy policy = descriptor.getCMPPolicy();
            Class pkClass = policy.getPKClass();
            if ((pkClass != null) && (pkClass != id.getClass()) && (!BasicTypeHelperImpl.getInstance().isStrictlyAssignableFrom(pkClass, id.getClass()))) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("invalid_pk_class", new Object[] { descriptor.getCMPPolicy().getPKClass(), id.getClass() }));
            }
            primaryKey = policy.createPrimaryKeyFromId(id, session);
        }
        
        // If the LockModeType is PESSIMISTIC*, check the unitofwork cache and return the entity if it has previously been locked
        // Must avoid using the new JPA 2.0 Enum values directly to allow JPA 1.0 jars to still work.
        if (lockMode != null && (lockMode.name().equals(ObjectLevelReadQuery.PESSIMISTIC_READ) || lockMode.name().equals(ObjectLevelReadQuery.PESSIMISTIC_WRITE)
                || lockMode.name().equals(ObjectLevelReadQuery.PESSIMISTIC_FORCE_INCREMENT))) {
            // PERF: check if the UnitOfWork has pessimistically locked objects to avoid a cache query
            if (session.isUnitOfWork() && ((UnitOfWorkImpl)session).hasPessimisticLockedObjects()) {
                ReadObjectQuery query = new ReadObjectQuery();
                query.setReferenceClass(descriptor.getJavaClass());
                query.setSelectionId(primaryKey);
                query.checkCacheOnly();
                Object cachedEntity = session.executeQuery(query);
                if (cachedEntity != null && ((UnitOfWorkImpl)session).isPessimisticLocked(cachedEntity)) {
                    return cachedEntity;
                }
            }
        }

        // Get the read object query and apply the properties to it.
        // PERF: use descriptor defined query to avoid extra query creation.
        ReadObjectQuery query = descriptor.getQueryManager().getReadObjectQuery();

        if (query == null) {
            // The properties/query hints and setIsExecutionClone etc. is set
            // in the getReadObjectQuery.
            query = getReadObjectQuery(descriptor.getJavaClass(), primaryKey, properties);
        } else {
            query.checkPrepare(session, null);
            query = (ReadObjectQuery) query.clone();

            // Apply the properties if there are some.
            QueryHintsHandler.apply(properties, query, session.getLoader(), session);

            query.setIsExecutionClone(true);
            query.setSelectionId(primaryKey);
        }

        // Apply any EclipseLink defaults if they haven't been set through
        // the properties.
        if (properties == null || ( !properties.containsKey(QueryHints.CACHE_USAGE) && !properties.containsKey(QueryHints.CACHE_RETRIEVE_MODE) && !properties.containsKey(QueryHints.CACHE_STORE_MODE) 
                && !properties.containsKey("javax.persistence.cacheRetrieveMode") && !properties.containsKey("javax.persistence.cacheStoreMode"))) {
            query.conformResultsInUnitOfWork();
        }

        return executeQuery(query, lockMode, session);
    }

    /**
     * Synchronize the persistence context with the underlying database.
     */
    public void flush() {
        try {
            // Based on spec definition 3 possible exceptions are thrown
            // IllegalState by verifyOpen,
            // TransactionRequired by check for transaction
            // PersistenceException for all others.
            // but there is a tck test that checks for illegal state exception
            // and the
            // official statement is that the spec 'intended' for
            // IllegalStateException to be raised.

            verifyOpen();
            try {
                try {
                    getActivePersistenceContext(checkForTransaction(true)).writeChanges();
                } catch (org.eclipse.persistence.exceptions.OptimisticLockException eclipselinkOLE) {
                    throw new OptimisticLockException(eclipselinkOLE);
                }
            } catch (EclipseLinkException e) {
                throw new PersistenceException(e);
            }
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    protected void detectTransactionWrapper() {
        if (this.databaseSession.hasExternalTransactionController()) {
            setJTATransactionWrapper();
        } else {
            setEntityTransactionWrapper();
        }
    }

    /**
     * Execute the locking query.
     */
    private Object executeQuery(ReadObjectQuery query, LockModeType lockMode, AbstractSession session) {
        // Make sure we set the lock mode type if there is one. It will
        // be handled in the query prepare statement. Setting the lock mode
        // will validate that a valid locking policy is in place if needed. If
        // a true value is returned it indicates that we were unable to set the
        // lock mode, throw an exception.
        if (lockMode != null && query.setLockModeType(lockMode.name(), session)) {
            throw new PersistenceException(ExceptionLocalization.buildMessage("ejb30-wrong-lock_called_without_version_locking-index", null));
        }

        Object result = null;

        try {
            result = session.executeQuery(query);
        } catch (DatabaseException e) {
            // If we catch a database exception as a result of executing a
            // pessimistic locking query we need to ask the platform which
            // JPA 2.0 locking exception we should throw. It will be either
            // be a PessimisticLockException or a LockTimeoutException (if
            // the query was executed using a wait timeout value)
            if (lockMode != null && lockMode.name().contains(ObjectLevelReadQuery.PESSIMISTIC_)) {
                // ask the platform if it is a lock timeout
                if (query.getExecutionSession().getPlatform().isLockTimeoutException(e)) {
                    throw new LockTimeoutException(e);
                } else {
                    throw new PessimisticLockException(e);
                }
            } else {
                throw e;
            }
        }

        return result;
    }

    /**
     * Refresh the state of the instance from the database.
     * 
     * @param entity
     *            instance registered in the current persistence context.
     */
    public void refresh(Object entity) {
        refresh(entity, null, getQueryHints(entity, OperationType.REFRESH));
    }

    /**
     * Refresh the state of the instance from the database, using the specified
     * properties, and overwriting changes made to the entity, if any. If a
     * vendor-specific property or hint is not recognized, it is silently
     * ignored.
     * 
     * @param entity
     * @param properties
     *            standard and vendor-specific properties
     * @throws IllegalArgumentException
     *             if the instance is not an entity or the entity is not managed
     * @throws TransactionRequiredException
     *             if invoked on a container-managed entity manager of type
     *             PersistenceContextType.TRANSACTION and there is no
     *             transaction.
     * @throws EntityNotFoundException
     *             if the entity no longer exists in the database
     * 
     * @since Java Persistence API 2.0
     */
    public void refresh(Object entity, Map<String, Object> properties) {
        refresh(entity, null, properties);
    }

    /**
     * Refresh the state of the instance from the database, overwriting changes
     * made to the entity, if any, and lock it with respect to given lock mode
     * type. If the lock mode type is pessimistic and the entity instance is
     * found but cannot be locked: - the PessimisticLockException will be thrown
     * if the database locking failure causes transaction-level rollback. - the
     * LockTimeoutException will be thrown if the database locking failure
     * causes only statement-level rollback.
     * 
     * @param entity
     * @param lockMode
     * @throws IllegalArgumentException
     *             if the instance is not an entity or the entity is not managed
     * @throws TransactionRequiredException
     *             if there is no transaction
     * @throws EntityNotFoundException
     *             if the entity no longer exists in the database
     * @throws PessimisticLockException
     *             if pessimistic locking fails and the transaction is rolled
     *             back
     * @throws LockTimeoutException
     *             if pessimistic locking fails and only the statement is rolled
     *             back
     * @throws PersistenceException
     *             if an unsupported lock call is made
     */
    public void refresh(Object entity, LockModeType lockMode) {
        refresh(entity, lockMode, getQueryHints(entity, OperationType.REFRESH));
    }

    /**
     * Refresh the state of the instance from the database, overwriting changes
     * made to the entity, if any, and lock it with respect to given lock mode
     * type. If the lock mode type is pessimistic and the entity instance is
     * found but cannot be locked: - the PessimisticLockException will be thrown
     * if the database locking failure causes transaction-level rollback. - the
     * LockTimeoutException will be thrown if the database locking failure
     * causes only statement-level rollback If a vendor-specific property or
     * hint is not recognized, it is silently ignored. Portable applications
     * should not rely on the standard timeout hint. Depending on the database
     * in use and the locking mechanisms used by the provider, the hint may or
     * may not be observed.
     * 
     * @param entity
     * @param lockMode
     * @param properties
     *            standard and vendor-specific properties and hints
     * @throws IllegalArgumentException
     *             if the instance is not an entity or the entity is not managed
     * @throws TransactionRequiredException
     *             if there is no transaction
     * @throws EntityNotFoundException
     *             if the entity no longer exists in the database
     * @throws PessimisticLockException
     *             if pessimistic locking fails and the transaction is rolled
     *             back
     * @throws LockTimeoutException
     *             if pessimistic locking fails and only the statement is rolled
     *             back
     * @throws PersistenceException
     *             if an unsupported lock call is made
     */
    public void refresh(Object entity, LockModeType lockMode, Map<String, Object> properties) {
        try {
            verifyOpen();
            boolean validateExistence =  (lockMode != null && !lockMode.equals(LockModeType.NONE));
            UnitOfWorkImpl uow = getActivePersistenceContext(checkForTransaction(validateExistence));
            if (!contains(entity, uow)) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cant_refresh_not_managed_object", new Object[] { entity }));
            }

            // Get the read object query and apply the properties to it.
            ReadObjectQuery query = getReadObjectQuery(entity, properties);

            // Apply any EclipseLink defaults if they haven't been set through
            // the properties.
            if (properties == null || !properties.containsKey(QueryHints.REFRESH)) {
                query.refreshIdentityMapResult();
            }

            if (properties == null || !properties.containsKey(QueryHints.REFRESH_CASCADE)) {
                query.cascadeByMapping();
            }

            Object refreshedEntity = executeQuery(query, lockMode, uow);
            if (refreshedEntity == null) {
                // bug5955326, ReadObjectQuery will now ensure the object is
                // invalidated if refresh returns null.
                throw new EntityNotFoundException(ExceptionLocalization.buildMessage("entity_no_longer_exists_in_db", new Object[] { entity }));
            }
        } catch (LockTimeoutException e) {
            throw e;
        } catch (RuntimeException exception) {
            setRollbackOnly();
            throw exception;
        }
    }

    /**
     * Check if the instance belongs to the current persistence context.
     * 
     * @param entity
     * @return
     * @throws IllegalArgumentException
     *             if given Object is not an entity
     */
    public boolean contains(Object entity) {
        try {
            verifyOpen();
            if (entity == null) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { entity }));
            }
            ClassDescriptor descriptor = this.databaseSession.getDescriptors().get(entity.getClass());
            if (descriptor == null || descriptor.isDescriptorTypeAggregate()) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { entity }));
            }

            if ((!hasActivePersistenceContext())) {
                return false;
            }

            return contains(entity, getActivePersistenceContext(checkForTransaction(false)));
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * Check if the instance belongs to the current persistence context.
     */
    protected boolean contains(Object entity, UnitOfWork uow) {
        return ((UnitOfWorkImpl) uow).isObjectRegistered(entity) && !((UnitOfWorkImpl) uow).isObjectDeleted(entity);
    }

    public javax.persistence.Query createDescriptorNamedQuery(String queryName, Class descriptorClass) {
        return createDescriptorNamedQuery(queryName, descriptorClass, null);
    }

    public javax.persistence.Query createDescriptorNamedQuery(String queryName, Class descriptorClass, List argumentTypes) {
        try {
            verifyOpen();
            ClassDescriptor descriptor = this.databaseSession.getDescriptor(descriptorClass);
            if (descriptor != null) {
                DatabaseQuery query = descriptor.getQueryManager().getLocalQueryByArgumentTypes(queryName, argumentTypes);
                if (query != null) {
                    return new EJBQueryImpl(query, this);
                }
            }
            return null;
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * Create an instance of Query for executing a named query (in EJBQL or
     * native SQL).
     * 
     * @param name
     *            the name of a query defined in metadata
     * @return the new query instance
     */
    public Query createNamedQuery(String name) {
        try {
            verifyOpen();
            EJBQueryImpl query = new EJBQueryImpl(name, this, true);
            query.getDatabaseQueryInternal();
            return query;
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * Create an instance of TypedQuery for executing a
     * named query (in the Java Persistence query language
     * or in native SQL).
     * @param name the name of a query defined in metadata
     * @param resultClass the type of the query result
     * @return the new query instance
     * @throws IllegalArgumentException if a query has not been
     *      defined with the given name or if the query string is
     *      found to be invalid
     */
    public <T> TypedQuery<T> createNamedQuery(String name, Class<T> resultClass){
        return (TypedQuery<T>) createNamedQuery(name);
    }

    /**
     * Create an instance of StoredProcedureQuery for executing a
     * stored procedure in the database.
     * @param name name assigned to the stored procedure query
     * in metadata
     * @return the new stored procedure query instance
     * @throws IllegalArgumentException if a query has not been
     * defined with the given name
     */
    public StoredProcedureQuery createNamedStoredProcedureQuery(String name) {
        try {
            verifyOpen();
            StoredProcedureQueryImpl query = new StoredProcedureQueryImpl(name, this);
            query.getDatabaseQueryInternal();
            return query;
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }
    
    /**
     * Create an instance of Query for executing a native SQL query.
     * 
     * @param sqlString
     *            a native SQL query string
     * @return the new query instance
     */
    public Query createNativeQuery(String sqlString) {
        try {
            verifyOpen();
            return new EJBQueryImpl(EJBQueryImpl.buildSQLDatabaseQuery(sqlString, this.databaseSession.getLoader(), this.databaseSession), this);
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * This method is used to create a query using SQL. The class, must be the
     * expected return type.
     */
    public Query createNativeQuery(String sqlString, Class resultType) {
        try {
            verifyOpen();
            return new EJBQueryImpl(EJBQueryImpl.buildSQLDatabaseQuery(resultType, sqlString, this.databaseSession.getLoader(), this.databaseSession), this);
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * Create an instance of Query for executing a native SQL query.
     * 
     * @param sqlString
     *            a native SQL query string
     * @param resultSetMapping
     *            the name of the result set mapping
     * @return the new query instance
     * @throws IllegalArgumentException
     *             if query string is not valid
     */
    public Query createNativeQuery(String sqlString, String resultSetMapping) {
        try {
            verifyOpen();
            ResultSetMappingQuery query = new ResultSetMappingQuery();
            query.setSQLResultSetMappingName(resultSetMapping);
            query.setSQLString(sqlString);
            query.setIsUserDefined(true);
            return new EJBQueryImpl(query, this);
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * This method returns the current session to the requestor. The current
     * session will be a the active UnitOfWork within a transaction and will be
     * a 'scrap' UnitOfWork outside of a transaction. The caller is concerned
     * about the results then the getSession() or getUnitOfWork() API should be
     * called.
     */
    public Session getActiveSession() {
        return getActivePersistenceContext(checkForTransaction(false));
    }
    
    /**
     * This method returns the current session to the requestor. The current
     * session will be a the active UnitOfWork within a transaction and will be
     * a 'scrap' UnitOfWork outside of a transaction. The caller is concerned
     * about the results then the getSession() or getUnitOfWork() API should be
     * called.
     */
    public AbstractSession getActiveSessionIfExists() {
        // When requesting an active session, if there isn't one but we have
        // table per tenant descriptors, make sure we return one. The 'scrap'
        // session will not be initialized for table per tenant multitenancy.
        if (hasActivePersistenceContext() || getAbstractSession().hasTablePerTenantDescriptors()) {
            return (AbstractSession) getActiveSession();
        } else {
            return getAbstractSession();
        }
    }

    /**
     * Return the underlying provider object for the EntityManager, if
     * available. The result of this method is implementation specific.
     */
    public Object getDelegate() {
        try {
            verifyOpen();
            return this;
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * Get the flush mode that applies to all objects contained in the
     * persistence context.
     * 
     * @return flushMode
     */
    public FlushModeType getFlushMode() {
        try {
            verifyOpen();
            return flushMode;
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * This method will return the active UnitOfWork
     */
    public UnitOfWork getUnitOfWork() {
        return getActivePersistenceContext(checkForTransaction(false));
    }

    /**
     * This method will return a Session outside of a transaction and null
     * within a transaction.
     */
    public Session getSession() {
        if (checkForTransaction(false) == null) {
            return this.databaseSession.acquireNonSynchronizedUnitOfWork(this.referenceMode);
        }
        return null;
    }

    /**
     * Returns the resource-level transaction object. The EntityTransaction
     * instance may be used serially to begin and commit multiple transactions.
     * 
     * @return EntityTransaction instance
     * @throws IllegalStateException
     *             if invoked on a JTA EntityManager.
     */
    public javax.persistence.EntityTransaction getTransaction() {
        try {
            return ((TransactionWrapper) transaction).getTransaction();
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * The method search for user defined property passed in from EntityManager,
     * if it is not found then search for it from EntityManagerFactory
     * properties.
     * 
     * @param name
     * @return
     */
    public Object getProperty(String name) {
        Object propertyValue = null;
        if (name == null) {
            return null;
        }
        if (this.properties != null) {
            propertyValue = this.properties.get(name);
        }
        if (propertyValue == null) {
            propertyValue = this.factory.getAbstractSession().getProperty(name);
        }
        return propertyValue;
    }

    /**
     * Build a selection query for the primary key values.
     */
    protected ReadObjectQuery getReadObjectQuery(Class referenceClass, Object primaryKey, Map properties) {
        ReadObjectQuery query = getReadObjectQuery(properties);
        query.setReferenceClass(referenceClass);
        query.setSelectionId(primaryKey);
        return query;
    }

    /**
     * Build a selection query using the given properties.
     */
    protected ReadObjectQuery getReadObjectQuery(Map properties) {
        ReadObjectQuery query = new ReadObjectQuery();

        // Apply the properties if there are some.
        QueryHintsHandler.apply(properties, query, this.databaseSession.getDatasourcePlatform().getConversionManager().getLoader(), this.databaseSession);
        query.setIsExecutionClone(true);
        return query;
    }

    /**
     * Build a selection query for the given entity.
     */
    protected ReadObjectQuery getReadObjectQuery(Object entity, Map properties) {
        ReadObjectQuery query = getReadObjectQuery(properties);
        query.setSelectionObject(entity);
        return query;
    }

    /**
     * Get an instance, whose state may be lazily fetched. If the requested
     * instance does not exist in the database, throws EntityNotFoundException
     * when the instance state is first accessed. (The container is permitted to
     * throw EntityNotFoundException when get is called.) The application should
     * not expect that the instance state will be available upon detachment,
     * unless it was accessed by the application while the entity manager was
     * open.
     * 
     * @param entityClass
     * @param primaryKey
     * @return the found entity instance.
     * @throws IllegalArgumentException
     *             if the first argument does not denote an entity type or the
     *             second argument is not a valid type for that entity's primary
     *             key.
     * @throws EntityNotFoundException
     *             if the entity state cannot be accessed.
     */
    public <T> T getReference(Class<T> entityClass, Object primaryKey) {
        try {
            verifyOpen();
            UnitOfWork session = (UnitOfWork) getActiveSession();
            Object reference = session.getReference(entityClass, primaryKey);
            if (reference == null) {
                Object[] args = { primaryKey };
                String message = ExceptionLocalization.buildMessage("no_entities_retrieved_for_get_reference", args);
                throw new javax.persistence.EntityNotFoundException(message);
            }
            return (T) reference;
        } catch (RuntimeException exception) {
            setRollbackOnly();
            throw exception;
        }
    }

    /**
     * Return a read-only session (client session) for read-only operations.
     */
    public Session getReadOnlySession() {
        if (this.extendedPersistenceContext != null && this.extendedPersistenceContext.isActive()) {
            return this.extendedPersistenceContext.getParent();
        }
        if (this.readOnlySession != null) {
            return this.readOnlySession;
        }
        if (this.databaseSession.isServerSession()) {
            this.readOnlySession = ((ServerSession)this.databaseSession).acquireClientSession(connectionPolicy, properties);
            return this.readOnlySession;
        } else if(this.databaseSession.isBroker()) {
            this.readOnlySession = ((SessionBroker)this.databaseSession).acquireClientSessionBroker(this.connectionPolicies, (Map)this.properties.get(EntityManagerProperties.COMPOSITE_UNIT_PROPERTIES));
            return this.readOnlySession;
        } else {
            // currently this can't happen - the databaseSession is either ServerSession or SessionBroker. 
            return this.databaseSession;
        }
    }

    /**
     * Return the underlying database session
     */
    public DatabaseSessionImpl getDatabaseSession() {
        return (DatabaseSessionImpl)this.databaseSession;
    }

    /**
     * Return the underlying database session
     */
    public AbstractSession getAbstractSession() {
        return this.databaseSession;
    }

    /**
     * INTERNAL:
     * Set the underlying database session
     */
    public void setAbstractSession(AbstractSession session) {
        this.databaseSession = session;
    }

    /**
     * Return the underlying server session, throws ClassCastException if it's not a ServerSession.
     */
    public ServerSession getServerSession() {
        return (ServerSession)this.databaseSession;
    }

    /**
     * Return the underlying session broker, throws ClassCastException if it's not a SessionBroker.
     */
    public SessionBroker getSessionBroker() {
        return (SessionBroker)this.databaseSession;
    }

    /**
     * Return the member DatabaseSessionImpl that maps cls in session broker.
     * Return null if either not a session broker or cls is not mapped.
     * Session broker implement composite persistence unit.
     */
    public DatabaseSessionImpl getMemberDatabaseSession(Class cls) {
        if(this.databaseSession.isBroker()) {
            return (DatabaseSessionImpl)((SessionBroker)this.databaseSession).getSessionForClass(cls);
        } else {
            return null;
        }
    }
    
    /**
     * Return the member ServerSession that maps cls in session broker.
     * Return null if either not a session broker or cls is not mapped.
     * Session broker implement composite persistence unit.
     */
    public ServerSession getMemberServerSession(Class cls) {
        if(this.databaseSession.isBroker()) {
            return (ServerSession)((SessionBroker)this.databaseSession).getSessionForClass(cls);
        } else {
            return null;
        }
    }
    
    /**
     * Return the name of member session that maps cls.
     * Return null if either not a session broker or cls is not mapped.
     * Session broker implement composite persistence unit.
     */
    public String getMemberSessionName(Class cls) {
        if(this.databaseSession.isBroker()) {
            return ((SessionBroker)this.databaseSession).getSessionForClass(cls).getName();
        } else {
            return null;
        }
    }

    /**
     * This method is used to create a query using a EclipseLink Expression and
     * the return type.
     */
    public javax.persistence.Query createQuery(Expression expression, Class resultType) {
        try {
            verifyOpen();
            DatabaseQuery query = createQueryInternal(expression, resultType);
            return new EJBQueryImpl(query, this);
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * This method is used to create a query using a EclipseLink DatabaseQuery.
     */
    public javax.persistence.Query createQuery(DatabaseQuery databaseQuery) {
        try {
            verifyOpen();
            return new EJBQueryImpl(databaseQuery, this);
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }
    

    /** 
     * @see EntityManager#createQuery(javax.persistence.criteria.CriteriaQuery)
     * @since Java Persistence 2.0
     */
    public <T> TypedQuery<T> createQuery(CriteriaQuery<T> criteriaQuery) {
        try{
            verifyOpen();
            return new EJBQueryImpl<T>(((CriteriaQueryImpl<T>)criteriaQuery).translate(), this);
        }catch (RuntimeException e){
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * This method is used to create a query using a EclipseLink by example.
     */
    public javax.persistence.Query createQueryByExample(Object exampleObject) {
        try {
            verifyOpen();
            ReadAllQuery query = new ReadAllQuery(exampleObject.getClass());
            query.setExampleObject(exampleObject);
            return new EJBQueryImpl(query, this);
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * This method is used to create a query using a EclipseLink Call.
     */
    public javax.persistence.Query createQuery(Call call) {
        try {
            verifyOpen();
            DataReadQuery query = new DataReadQuery(call);
            return new EJBQueryImpl(query, this);
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }
    
    /**
     * This method is used to create a query using a EclipseLink Call.
     */
    public javax.persistence.Query createQuery(Call call, Class entityClass) {
        try {
            verifyOpen();
            ReadAllQuery query = new ReadAllQuery(entityClass, call);
            return new EJBQueryImpl(query, this);
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * Create an instance of Query for executing an JPQL query.
     * 
     * @param jpqlString
     *            an JPQL query string
     * @return the new query instance
     */
    public Query createQuery(String jpqlString) {
        try {
            verifyOpen();
            EJBQueryImpl ejbqImpl;

            try {
                ejbqImpl = new EJBQueryImpl(jpqlString, this);
            } catch (JPQLException exception) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("wrap_ejbql_exception") + ": " + exception.getLocalizedMessage(), exception);
            }

            return ejbqImpl;
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * Create an instance of TypedQuery for executing a
     * Java Persistence query language statement.
     * @param qlString a Java Persistence query string
     * @param resultClass the type of the query result
     * @return the new query instance
     * @throws IllegalArgumentException if the query string is found
     *         to be invalid
     */
    public <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass){
        return (TypedQuery<T>) this.createQuery(qlString);
    }

    /**
     * This method is used to create a query using a EclipseLink Expression and
     * the return type.
     */
    protected DatabaseQuery createQueryInternal(Expression expression, Class resultType) {
        ReadAllQuery query = new ReadAllQuery(resultType);
        query.setSelectionCriteria(expression);
        return query;
    }
    
    /**
     * Create an instance of <code>StoredProcedureQuery</code> for executing a
     * stored procedure in the database.
     * <p>Parameters must be registered before the stored procedure can be 
     * executed.
     * <p>If the stored procedure returns one or more result sets, any result 
     * set will be returned as a list of type Object[].
     * 
     * @param procedureName name of the stored procedure in the database
     * @return the new stored procedure query instance
     * @throws IllegalArgumentException if a stored procedure of the given name 
     *         does not exist (or the query execution will fail)
     * @since EclipseLink 2.5/Java Persistence 2.1
     */
    public StoredProcedureQuery createStoredProcedureQuery(String procedureName) {
        try {
            verifyOpen();
            StoredProcedureCall call = new StoredProcedureCall();
            call.setProcedureName(procedureName);
            return new StoredProcedureQueryImpl(StoredProcedureQueryImpl.buildResultSetMappingQuery(new ArrayList<SQLResultSetMapping>(), call), this);
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }
    
    /**
     * Create an instance of <code>StoredProcedureQuery</code> for executing a
     * stored procedure in the database.
     * <p>Parameters must be registered before the stored procedure can be 
     * executed.
     * <p>The <code>resultClass</code> arguments must be specified in the order 
     * in which the result sets will be returned by the stored procedure
     * invocation.
     * 
     * @param procedureName name of the stored procedure in the database
     * @param resultClasses classes to which the result sets produced by the 
     *        stored procedure are to be mapped
     * @return the new stored procedure query instance
     * @throws IllegalArgumentException if a stored procedure of the given name 
     *         does not exist (or the query execution will fail)
     * @since EclipseLink 2.5/Java Persistence 2.1
     */
    public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class... resultClasses) {
        try {
            verifyOpen();
            StoredProcedureCall call = new StoredProcedureCall();
            call.setProcedureName(procedureName);
            call.setHasMultipleResultSets(resultClasses.length > 1);
            
            List<SQLResultSetMapping> sqlResultSetMappings = new ArrayList<SQLResultSetMapping>();
            for (Class resultClass : resultClasses) {
                sqlResultSetMappings.add(new SQLResultSetMapping(resultClass));
            }
           
            return new StoredProcedureQueryImpl(StoredProcedureQueryImpl.buildResultSetMappingQuery(sqlResultSetMappings, call), this);
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * Create an instance of <code>StoredProcedureQuery</code> for executing a
     * stored procedure in the database.
     * <p>Parameters must be registered before the stored procedure can be 
     * executed.
     * <p>The <code>resultSetMapping</code> arguments must be specified in the 
     * order in which the result sets will be returned by the stored procedure 
     * invocation.
     * 
     * @param procedureName name of the stored procedure in the database
     * @param resultSetMappings the names of the result set mappings
     *        to be used in mapping result sets
     *        returned by the stored procedure
     * @return the new stored procedure query instance
     * @throws IllegalArgumentException if a stored procedure or
     *         result set mapping of the given name does not exist
     *         (or the query execution will fail)
     * @since EclipseLink 2.5/Java Persistence 2.1
     */
    public StoredProcedureQuery createStoredProcedureQuery(String procedureName, String... resultSetMappings) {
        try {
            verifyOpen();
            StoredProcedureCall call = new StoredProcedureCall();
            call.setProcedureName(procedureName);
            call.setHasMultipleResultSets(resultSetMappings.length > 1);
            
            List<String> sqlResultSetMappingNames = new ArrayList<String>();
            for (String resultSetMapping : resultSetMappings) {
                sqlResultSetMappingNames.add(resultSetMapping);
            }
           
            return new StoredProcedureQueryImpl(StoredProcedureQueryImpl.buildResultSetMappingNameQuery(sqlResultSetMappingNames, call), this);
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * <p>
     * Closes this EntityManager.
     * 
     * <p>
     * After invoking this method, all methods on the instance will throw an
     * {@link IllegalStateException} except for {@link #isOpen}, which will
     * return <code>false</code> .
     * </p>
     * 
     * <p>
     * This should be called when a method is finished with the EntityManager in
     * a bean-managed transaction environment or when executed outside a
     * container. Closing of the EntityManager is handled by the container when
     * using container-managed transactions.
     * </p>
     */
    public void close() {
        try {
            verifyOpen();
            closeOpenQueries();
            this.isOpen = false;
            this.factory = null;
            this.databaseSession = null;
            // Do not invalidate the metaModel field 
            // (a reopened emf will re-populate the same metaModel)
            // (a new persistence unit will generate a new metaModel)
            if (this.extendedPersistenceContext != null) {
                // bug210677, checkForTransaction returns null in
                // afterCompletion - in this case check for uow being
                // synchronized.
                if (checkForTransaction(false) == null && !this.extendedPersistenceContext.isSynchronized()) {
                    // uow.release clears change sets but keeps the cache.
                    // uow still could be used for instantiating of ValueHolders
                    // after it's released.
                    this.extendedPersistenceContext.release();
                    this.extendedPersistenceContext.getParent().release();
                } else {
                    // when commit will be called uow will be released, all
                    // change sets will be cleared, but the cache will be kept.
                    // uow still could be used for instantiating of ValueHolders
                    // after it's released.
                    this.extendedPersistenceContext.setResumeUnitOfWorkOnTransactionCompletion(false);
                }
                this.extendedPersistenceContext = null;
            }
            if (this.readOnlySession != null) {
                this.readOnlySession.release();
                this.readOnlySession = null;
            }
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }
    
    /**
     * Close any open queries executed against this entity manager.0
     */
    protected void closeOpenQueries() {
        for (QueryImpl openQuery : getOpenQueriesSet()) {
            openQuery.close();
        }
    }

    /**
     * Internal method. Indicates whether flushMode is AUTO.
     * 
     * @return boolean
     */
    public boolean isFlushModeAUTO() {
        return flushMode == FlushModeType.AUTO;
    }

    /**
     * Indicates whether or not this entity manager and its entity manager factory 
     * are open. Returns
     * <code>true</code> until a call to {@link #close} is made.
     */
    public boolean isOpen() {
        return isOpen && factory.isOpen();
    }

    /**
     * Set the lock mode for an entity object contained in the persistence
     * context.
     * 
     * @param entity
     * @param lockMode
     * @throws PersistenceException
     *             if an unsupported lock call is made
     * @throws IllegalArgumentException
     *             if the instance is not an entity or is a detached entity
     * @throws javax.persistence.TransactionRequiredException
     *             if there is no transaction
     */
    public void lock(Object entity, LockModeType lockMode) {
        lock(entity, lockMode, getQueryHints(entity, OperationType.LOCK));
    }

    /**
     * Set the lock mode for an entity object contained in the persistence
     * context.
     * 
     * @param entity
     * @param lockMode
     * @throws PersistenceException
     *             if an unsupported lock call is made
     * @throws IllegalArgumentException
     *             if the instance is not an entity or is a detached entity
     * @throws javax.persistence.TransactionRequiredException
     *             if there is no transaction
     */
    public void lock(Object entity, LockModeType lockMode, Map<String, Object> properties) {
        try {
            verifyOpen();

            if (entity == null) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { entity }));
            }

            UnitOfWork uow = getActivePersistenceContext(checkForTransaction(true)); // Throws TransactionRequiredException if no active transaction.

            if (!contains(entity, uow)) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cant_lock_not_managed_object", new Object[] { entity }));
            }

            if (lockMode == null || lockMode.name().equals(ObjectLevelReadQuery.NONE)) {
                // Nothing to do
                return;
            }
            // Must avoid using the new JPA 2.0 Enum values directly to allow JPA 1.0 jars to still work.
            if (lockMode.name().equals(ObjectLevelReadQuery.PESSIMISTIC_READ) || lockMode.name().equals(ObjectLevelReadQuery.PESSIMISTIC_WRITE )
                    || lockMode.name().equals(ObjectLevelReadQuery.PESSIMISTIC_FORCE_INCREMENT)) {
                
                // return if the entity has previously been pessimistically locked
                if (((UnitOfWorkImpl)uow).isPessimisticLocked(entity)) {
                    return;
                }
                
                // Get the read object query and apply the properties to it.
                ReadObjectQuery query = getReadObjectQuery(entity, properties);

                // Apply any EclipseLink defaults if they haven't been set
                // through
                // the properties.
                if (properties == null || !properties.containsKey(QueryHints.REFRESH)) {
                    query.refreshIdentityMapResult();
                }

                if (properties == null || !properties.containsKey(QueryHints.REFRESH_CASCADE)) {
                    query.cascadePrivateParts();
                }

                executeQuery(query, lockMode, getActivePersistenceContext(checkForTransaction(false)));
            } else {
                RepeatableWriteUnitOfWork context = getActivePersistenceContext(checkForTransaction(false));
                ClassDescriptor descriptor = context.getDescriptor(entity);
                OptimisticLockingPolicy lockingPolicy = descriptor.getOptimisticLockingPolicy();
                if ((lockingPolicy == null) || !(lockingPolicy instanceof VersionLockingPolicy)) {
                    throw new PersistenceException(ExceptionLocalization.buildMessage("ejb30-wrong-lock_called_without_version_locking-index", null));
                }

                context.forceUpdateToVersionField(entity, (lockMode == LockModeType.WRITE || lockMode.name().equals(ObjectLevelReadQuery.OPTIMISTIC_FORCE_INCREMENT)));
            }
        } catch (LockTimeoutException e) {
            throw e;
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    public void verifyOpen() {
        if (!this.isOpen || !this.factory.isOpen()) {
            throw new IllegalStateException(ExceptionLocalization.buildMessage("operation_on_closed_entity_manager"));
        }
    }
    
    /**
     * used to save having to constantly use a try/catch to call setRollbackOnly 
     */
    public void verifyOpenWithSetRollbackOnly() {
        if (!this.isOpen || !this.factory.isOpen()) {
            setRollbackOnly();
            throw new IllegalStateException(ExceptionLocalization.buildMessage("operation_on_closed_entity_manager"));
        }
    }

    public RepeatableWriteUnitOfWork getActivePersistenceContext(Object txn) {
        // use local uow as it will be local to this EM and not on the txn
        if (this.extendedPersistenceContext == null || !this.extendedPersistenceContext.isActive()) {
            AbstractSession client;
            if(this.databaseSession.isServerSession()) {
                createConnectionPolicy();
                client = ((ServerSession)this.databaseSession).acquireClientSession(this.connectionPolicy, this.properties);
            } else if(this.databaseSession.isBroker()) {
                Map mapOfProperties = null;
                if (properties != null) {
                    mapOfProperties = (Map)this.properties.get(EntityManagerProperties.COMPOSITE_UNIT_PROPERTIES);
                }
                createConnectionPolicies(mapOfProperties);
                client = ((SessionBroker)this.databaseSession).acquireClientSessionBroker(this.connectionPolicies, mapOfProperties);
            } else {
                // currently this can't happen - the databaseSession is either ServerSession or SessionBroker. 
                client = this.databaseSession;
            }
            this.extendedPersistenceContext = client.acquireRepeatableWriteUnitOfWork(this.referenceMode);
            this.extendedPersistenceContext.setResumeUnitOfWorkOnTransactionCompletion(!this.closeOnCommit);
            this.extendedPersistenceContext.setShouldDiscoverNewObjects(this.persistOnCommit);
            this.extendedPersistenceContext.setDiscoverUnregisteredNewObjectsWithoutPersist(this.commitWithoutPersistRules);
            this.extendedPersistenceContext.setFlushClearCache(this.flushClearCache);
            this.extendedPersistenceContext.setShouldValidateExistence(this.shouldValidateExistence);
            this.extendedPersistenceContext.setCommitOrder(this.commitOrder);
            this.extendedPersistenceContext.setShouldCascadeCloneToJoinedRelationship(true);
            this.extendedPersistenceContext.setShouldStoreByPassCache(this.cacheStoreBypass);
            if (txn != null) {
                // if there is a txn, it means we have been marked to join with it.  
                // All that is left is to register the UOW with the transaction
                transaction.registerIfRequired(this.extendedPersistenceContext);
            }
            if (client.shouldLog(SessionLog.FINER, SessionLog.TRANSACTION)) {
                client.log(SessionLog.FINER, SessionLog.TRANSACTION, "acquire_unit_of_work_with_argument", String.valueOf(System.identityHashCode(this.extendedPersistenceContext)));
            }
        }
        if (this.beginEarlyTransaction && txn != null && !this.extendedPersistenceContext.isInTransaction()) {
            // gf3334, force persistence context early transaction
            this.extendedPersistenceContext.beginEarlyTransaction();
        }
        return this.extendedPersistenceContext;
    }

    /**
     * Use this method to set properties into existing EntityManager that are
     * normally passed to createEntityManager method. Note that if the method
     * called when active persistence context already exists then properties
     * used to create persistence context will be ignored until the new
     * persistence context is created (that happens either after transaction
     * rolled back or after clear method was called).
     */
    public void setProperties(Map properties) {
        verifyOpenWithSetRollbackOnly();
        this.properties = properties;
        if(this.hasActivePersistenceContext()) {
            this.extendedPersistenceContext.getParent().setProperties(properties);
        }
        if(properties == null || properties.isEmpty()) {
            return;
        }
        processProperties();
    }
    

    /**
     * @see EntityManager#setProperty(java.lang.String, java.lang.Object)
     */
    public void setProperty(String propertyName, Object value) {
        verifyOpenWithSetRollbackOnly();
        if(propertyName == null || value == null) {
            return;
        }
        
        if (this.properties == null) {
            if(this.hasActivePersistenceContext()) {
                // getProperties method always returns non-null Map
                this.properties = this.extendedPersistenceContext.getParent().getProperties();
            } else {
                this.properties = new HashMap();
            }
        }
        
        properties.put(propertyName, value);
        
        // If there is an extended persistence context update table per tenant descriptors.
        if (hasActivePersistenceContext()) {
            extendedPersistenceContext.updateTablePerTenantDescriptors(propertyName, value);
        } 
        
        // Re-process the property.
        PropertyProcessor processor = processors.get(propertyName);
        if(processor != null) {
            processor.process(propertyName, value, this);
        }
    }

    /**
     * This method is used in contains to check if we already have a persistence
     * context. If there is no active persistence context the method returns
     * false
     */
    public boolean hasActivePersistenceContext() {
        if (this.extendedPersistenceContext == null || !this.extendedPersistenceContext.isActive()) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Return the current, joined transaction object. If validateExistence is true throw
     * an error if there is no joined transaction, otherwise return null.
     */
    protected Object checkForTransaction(boolean validateExistence) {
        Object txn = this.transaction.checkForTransaction(validateExistence);
        //use transaction.isJoinedToTransaction EM is open verification.
        if ((txn != null) && !transaction.isJoinedToTransaction(this.extendedPersistenceContext)) {
            if (validateExistence) {
                throw new TransactionRequiredException(ExceptionLocalization.buildMessage("cannot_use_transaction_on_unsynced_pc"));
            }
            return null;
        }
        return txn;
    }

    public boolean shouldFlushBeforeQuery() {
        return (checkForTransaction(false)!= null);
    }

    /**
     * Indicate the early transaction should be forced to start. This allows for
     * reading through the write connection. As a side effect, this will also
     * prevent anything from being cached.
     */
    public boolean shouldBeginEarlyTransaction() {
        return this.beginEarlyTransaction;
    }

    /**
     * Indicate to the EntityManager that a JTA transaction is active. This
     * method should be called on a JTA application managed EntityManager that
     * was created outside the scope of the active transaction to associate it
     * with the current JTA transaction.
     * 
     * @throws javax.persistence.TransactionRequiredException
     *             if there is no transaction.
     */
    public void joinTransaction() {
        try {
            verifyOpen();
            //An EntityTransactionWrapper throws an exception, while
            //if using JTA and extendedPersistenceContext is active, then this will have the UOW register with the transaction.
            //If there is no context, the JTATransactionWrapper will register a listener with the transaction to keep track of when
            //it completes.  Any UOW created while the transaction is still active will then automatically register/join with it.
            transaction.registerIfRequired(this.extendedPersistenceContext);
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * Internal method. Sets transaction to rollback only.
     */
    protected void setRollbackOnly() {
        this.transaction.setRollbackOnlyInternal();
    }

    /**
     * Process the local EntityManager properties only. The persistence unit
     * properties are processed by the factory.
     */
    protected void processProperties() {
        Iterator<Map.Entry<String, Object>> it = this.properties.entrySet().iterator();
        while(it.hasNext()) {
            Map.Entry<String, Object> entry = it.next();
            PropertyProcessor processor = processors.get(entry.getKey());
            if(processor != null) {
                processor.process(entry.getKey(), entry.getValue(), this);
            }
        }
    }

    /**
     * Get the local EntityManager property from the properties Map. This only
     * searches the local Map. The persistence unit properties are processed by
     * the EntityManagerFactory.
     */
    protected String getPropertiesHandlerProperty(String name) {
        return PropertiesHandler.getPropertyValue(name, this.properties, false);
    }

    /**
     * Verifies and (if required) translates the value.
     */
    protected static String getPropertiesHandlerProperty(String name, String value) {
        return PropertiesHandler.getPropertyValue(name, value);
    }

    protected void setEntityTransactionWrapper() {
        transaction = new EntityTransactionWrapper(this);
    }

    /**
     * Set the flush mode that applies to all objects contained in the
     * persistence context.
     * 
     * @param flushMode
     */
    public void setFlushMode(FlushModeType flushMode) {
        try {
            verifyOpen();
            this.flushMode = flushMode;
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    protected void setJTATransactionWrapper() {
        transaction = new JTATransactionWrapper(this);
        //if this is not unsynchronized and there is a transaction, this EM needs to join it
        if (syncType == null || syncType.equals(SynchronizationType.SYNCHRONIZED)) {
            //use the wrapper's checkForTransaction as this.checkForTransaction does an unnecessary isJoined check
            if (transaction.checkForTransaction(false) != null) {
                //extendedPersistenceContext should be null, which will force the wrapper to register with the transaction
                transaction.registerIfRequired(this.extendedPersistenceContext);
            }
        }
    }

    /**
     * Create connection policy using properties.
     * Default connection policy created if no connection properties specified.
     * Should be called only in case this.databaseSession is a ServerSession.
     */
    protected void createConnectionPolicy() {
        if (this.connectionPolicy == null) {
            this.connectionPolicy = createConnectionPolicy((ServerSession)this.databaseSession, this.properties);
        }
    }
    
    /**
     * Create connection policy using properties.
     * Default connection policy created if no connection properties specified.
     * Should be called only in case this.databaseSession is a SessionBroker.
     */
    protected void createConnectionPolicies(Map mapOfProperties) {
        // Because the method called only in SessionBroker case this.connectionPolicies is guaranteed to be non null.
        Iterator<Map.Entry<String, ConnectionPolicy>> it = this.connectionPolicies.entrySet().iterator();
        while (it.hasNext()) {
            // key - sessionName, value - ConnectionPolicy
            Map.Entry<String, ConnectionPolicy> entry = it.next();
            if (entry.getValue() == null) {
                // ConnectionPolicy is null - should be recreated
                Map properties = null;
                if (mapOfProperties != null) {
                    properties = (Map)mapOfProperties.get(entry.getKey());
                }
                ConnectionPolicy connectionPolicy = createConnectionPolicy((ServerSession)this.databaseSession.getSessionForName(entry.getKey()), properties);
                this.connectionPolicies.put(entry.getKey(), connectionPolicy);
            }
        }
    }
    
    /**
     * Create connection policy using properties.
     * Default connection policy created if no connection properties specified.
     */
    protected static ConnectionPolicy createConnectionPolicy(ServerSession serverSession, Map properties) {
        ConnectionPolicy policy = serverSession.getDefaultConnectionPolicy();

        if (properties == null || properties.isEmpty()) {
            return policy;
        }

        // Search only the properties map - serverSession's properties have been
        // already processed.
        ConnectionPolicy policyFromProperties = (ConnectionPolicy) properties.get(EntityManagerProperties.CONNECTION_POLICY);
        if (policyFromProperties != null) {
            policy = policyFromProperties;
        }

        // Note that serverSession passed into the methods below only because it
        // carries the SessionLog into which the debug info should be written.
        // The property is search for in the passed properties map only (not in
        // serverSession, not in System.properties).
        ConnectionPolicy newPolicy = null;
        String isLazyString = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(EntityManagerProperties.EXCLUSIVE_CONNECTION_IS_LAZY, properties, serverSession, false);
        if (isLazyString != null) {
            boolean isLazy = Boolean.parseBoolean(isLazyString);
            if (policy.isLazy() != isLazy) {
                if (newPolicy == null) {
                    newPolicy = (ConnectionPolicy) policy.clone();
                }
                newPolicy.setIsLazy(isLazy);
            }
        }
        ConnectionPolicy.ExclusiveMode exclusiveMode = EntityManagerSetupImpl.getConnectionPolicyExclusiveModeFromProperties(properties, serverSession, false);
        if (exclusiveMode != null) {
            if (!exclusiveMode.equals(policy.getExclusiveMode())) {
                if (newPolicy == null) {
                    newPolicy = (ConnectionPolicy) policy.clone();
                }
                newPolicy.setExclusiveMode(exclusiveMode);
            }
        }

        String user = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(EntityManagerProperties.JDBC_USER, properties, serverSession, false);
        String password = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(EntityManagerProperties.JDBC_PASSWORD, properties, serverSession, false);
        String driver = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(EntityManagerProperties.JDBC_DRIVER, properties, serverSession, false);
        String connectionString = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(EntityManagerProperties.JDBC_URL, properties, serverSession, false);

        // find the jta datasource
        Object jtaDataSourceObj = EntityManagerFactoryProvider.getConfigPropertyLogDebug(EntityManagerProperties.JTA_DATASOURCE, properties, serverSession, false);
        DataSource jtaDataSource = null;
        String jtaDataSourceName = null;
        if (jtaDataSourceObj != null) {
            if (jtaDataSourceObj instanceof DataSource) {
                jtaDataSource = (DataSource) jtaDataSourceObj;
            } else if (jtaDataSourceObj instanceof String) {
                jtaDataSourceName = (String) jtaDataSourceObj;
            }
        }

        // find the non jta datasource
        Object nonjtaDataSourceObj = EntityManagerFactoryProvider.getConfigPropertyLogDebug(EntityManagerProperties.NON_JTA_DATASOURCE, properties, serverSession, false);
        DataSource nonjtaDataSource = null;
        String nonjtaDataSourceName = null;
        if (nonjtaDataSourceObj != null) {
            if (nonjtaDataSourceObj instanceof DataSource) {
                nonjtaDataSource = (DataSource) nonjtaDataSourceObj;
            } else if (nonjtaDataSourceObj instanceof String) {
                nonjtaDataSourceName = (String) nonjtaDataSourceObj;
            }
        }

        if (user != null || password != null || driver != null || connectionString != null || jtaDataSourceObj != null || nonjtaDataSourceObj != null) {
            // Validation: Can't specify jdbcDriver, connectionString with a
            // DataSource
            boolean isDefaultConnectorRequired = isPropertyToBeAdded(driver) || isPropertyToBeAdded(connectionString);
            boolean isJNDIConnectorRequired = isPropertyToBeAdded(jtaDataSource, jtaDataSourceName) || isPropertyToBeAdded(nonjtaDataSource, nonjtaDataSourceName);
            if (isDefaultConnectorRequired && isJNDIConnectorRequired) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("entity_manager_properties_conflict_default_connector_vs_jndi_connector", new Object[] {}));
            }

            DatasourceLogin login = (DatasourceLogin) policy.getLogin();
            if (login == null) {
                if (policy.getPoolName() != null) {
                    login = (DatasourceLogin) serverSession.getConnectionPool(policy.getPoolName()).getLogin();
                } else {
                    login = (DatasourceLogin) serverSession.getDatasourceLogin();
                }
            }

            // Validation: Can't specify jdbcDriver, connectionString if
            // externalTransactionController is used - this requires
            // externalConnectionPooling
            if (login.shouldUseExternalTransactionController() && isDefaultConnectorRequired) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("entity_manager_properties_conflict_default_connector_vs_external_transaction_controller", new Object[] {}));
            }

            javax.sql.DataSource dataSource = null;
            String dataSourceName = null;
            if (isJNDIConnectorRequired) {
                if (login.shouldUseExternalTransactionController()) {
                    if (isPropertyToBeAdded(jtaDataSource, jtaDataSourceName)) {
                        dataSource = jtaDataSource;
                        dataSourceName = jtaDataSourceName;
                    }
                    // validation: Can't change externalTransactionController
                    // state - will ignore data source that doesn't match the
                    // flag.
                    if (isPropertyToBeAdded(nonjtaDataSource, nonjtaDataSourceName)) {
                        serverSession.log(SessionLog.WARNING, SessionLog.PROPERTIES, "entity_manager_ignores_nonjta_data_source");
                    }
                } else {
                    if (isPropertyToBeAdded(nonjtaDataSource, nonjtaDataSourceName)) {
                        dataSource = nonjtaDataSource;
                        dataSourceName = nonjtaDataSourceName;
                    }
                    // validation: Can't change externalTransactionController
                    // state - will ignore data source that doesn't match the
                    // flag.
                    if (isPropertyToBeAdded(jtaDataSource, jtaDataSourceName)) {
                        serverSession.log(SessionLog.WARNING, SessionLog.PROPERTIES, "entity_manager_ignores_jta_data_source");
                    }
                }
            }

            // isNew...Required == null means no change required; TRUE -
            // newValue substitute oldValue by newValue; FALSE - remove
            // oldValue.
            Boolean isNewUserRequired = isPropertyValueToBeUpdated(login.getUserName(), user);
            // if isNewUserRequired==null then isNewPasswordRequired==null, too:
            // don't create a new ConnectionPolicy if the same user/password passed to both createEMF and createEM
            Boolean isNewPasswordRequired = null;
            // if user name should be removed from properties then password
            // should be removed, too.
            if (isNewUserRequired != null) {
                if(isNewUserRequired) {
                    if(password != null) {
                        // can't compare the passed (un-encrypted) password with the existing encrypted one, therefore
                        // use the new password if it's not an empty string. 
                        isNewPasswordRequired = password.length() > 0;
                    }
                } else {
                    // user should be removed -> remove password as well
                    isNewPasswordRequired = Boolean.FALSE;
                }
            }
            DefaultConnector oldDefaultConnector = null;
            if (login.getConnector() instanceof DefaultConnector) {
                oldDefaultConnector = (DefaultConnector) login.getConnector();
            }
            boolean isNewDefaultConnectorRequired = oldDefaultConnector == null && isDefaultConnectorRequired;
            JNDIConnector oldJNDIConnector = null;
            if (login.getConnector() instanceof JNDIConnector) {
                oldJNDIConnector = (JNDIConnector) login.getConnector();
            }
            boolean isNewJNDIConnectorRequired = oldJNDIConnector == null && isJNDIConnectorRequired;
            Boolean isNewDriverRequired = null;
            Boolean isNewConnectionStringRequired = null;
            if (isNewDefaultConnectorRequired) {
                isNewDriverRequired = isPropertyValueToBeUpdated(null, driver);
                isNewConnectionStringRequired = isPropertyValueToBeUpdated(null, connectionString);
            } else {
                if (oldDefaultConnector != null) {
                    isNewDriverRequired = isPropertyValueToBeUpdated(oldDefaultConnector.getDriverClassName(), driver);
                    isNewConnectionStringRequired = isPropertyValueToBeUpdated(oldDefaultConnector.getConnectionString(), connectionString);
                }
            }
            Boolean isNewDataSourceRequired = null;
            if (isNewJNDIConnectorRequired) {
                isNewDataSourceRequired = Boolean.TRUE;
            } else {
                if (oldJNDIConnector != null) {
                    if (dataSource != null) {
                        if (!dataSource.equals(oldJNDIConnector.getDataSource())) {
                            isNewDataSourceRequired = Boolean.TRUE;
                        }
                    } else if (dataSourceName != null) {
                        if (!dataSourceName.equals(oldJNDIConnector.getName())) {
                            isNewDataSourceRequired = Boolean.TRUE;
                        }
                    }
                }
            }

            if (isNewUserRequired != null || isNewPasswordRequired != null || isNewDriverRequired != null || isNewConnectionStringRequired != null || isNewDataSourceRequired != null) {
                // a new login required - so a new policy required, too.
                if (newPolicy == null) {
                    newPolicy = (ConnectionPolicy) policy.clone();
                }
                // the new policy must have a new login - not to override the
                // existing one in the original ConnectionPolicy that is likely
                // shared.
                DatasourceLogin newLogin = (DatasourceLogin) newPolicy.getLogin();
                // sometimes ConnectionPolicy.clone clones the login , too -
                // sometimes it doesn't.
                if (newPolicy.getLogin() == null || newPolicy.getLogin() == policy.getLogin()) {
                    newLogin = login.clone();
                    newPolicy.setLogin(newLogin);
                }
                // because it uses a new login the connection policy should not
                // be pooled.
                newPolicy.setPoolName(null);

                if (isNewUserRequired != null) {
                    if (isNewUserRequired) {
                        newLogin.setProperty("user", user);
                    } else {
                        newLogin.getProperties().remove("user");
                    }
                }
                if (isNewPasswordRequired != null) {
                    if (isNewPasswordRequired) {
                        newLogin.setProperty("password", password);
                    } else {
                        newLogin.getProperties().remove("password");
                    }
                }
                if (isNewDefaultConnectorRequired) {
                    newLogin.setConnector(new DefaultConnector());
                    newLogin.setUsesExternalConnectionPooling(false);
                } else if (isNewJNDIConnectorRequired) {
                    newLogin.setConnector(new JNDIConnector());
                    newLogin.setUsesExternalConnectionPooling(true);
                }
                if (isDefaultConnectorRequired) {
                    DefaultConnector defaultConnector = (DefaultConnector) newLogin.getConnector();
                    if (isNewDriverRequired != null) {
                        if (isNewDriverRequired) {
                            defaultConnector.setDriverClassName(driver);
                        } else {
                            defaultConnector.setDriverClassName(null);
                        }
                    }
                    if (isNewConnectionStringRequired != null) {
                        if (isNewConnectionStringRequired) {
                            defaultConnector.setDatabaseURL(connectionString);
                        } else {
                            defaultConnector.setDatabaseURL(null);
                        }
                    }
                } else if (isNewDataSourceRequired != null) {
                    JNDIConnector jndiConnector = (JNDIConnector) newLogin.getConnector();
                    if (isNewDataSourceRequired) {
                        if (dataSource != null) {
                            jndiConnector.setDataSource(dataSource);
                        } else {
                            // dataSourceName != null
                            jndiConnector.setName(dataSourceName);
                        }
                    }
                }
            }
        }

        if (newPolicy != null) {
            return newPolicy;
        } else {
            return policy;
        }
    }

    /**
     * Indicates whether the underlying session is a session broker. 
     * Session broker implement composite persistence unit.
     */
    public boolean isBroker() {
        return this.databaseSession.isBroker();
    }
    
    /**
     * Property value is to be added if it's non null and not an empty string.
     */
    protected static boolean isPropertyToBeAdded(String value) {
        return value != null && value.length() > 0;
    }

    protected static boolean isPropertyToBeAdded(DataSource ds, String dsName) {
        return ds != null || (dsName != null && dsName.length() > 0);
    }

    /**
     * Property value of an empty string indicates that the existing property
     * should be removed.
     */
    protected static boolean isPropertyToBeRemoved(String value) {
        return value != null && value.length() == 0;
    }

    /**
     * @return null: no change; TRUE: substitute oldValue by newValue; FALSE:
     *         remove oldValue
     */
    protected static Boolean isPropertyValueToBeUpdated(String oldValue, String newValue) {
        if (newValue == null) {
            // no new value - no change
            return null;
        } else {
            // new value is a non empty string
            if (newValue.length() > 0) {
                if (oldValue != null) {
                    if (newValue.equals(oldValue)) {
                        // new and old values are equal - no change.
                        return null;
                    } else {
                        // new and old values are different - change old value
                        // for new value.
                        return Boolean.TRUE;
                    }
                } else {
                    // no old value - change for new value.
                    return Boolean.TRUE;
                }
            } else {
                // new value is an empty string - if old value exists it should
                // be substituted with new value..
                if (oldValue != null) {
                    return Boolean.FALSE;
                } else {
                    return null;
                }
            }
        }
    }

    /**
     * Remove the given entity from the persistence context, causing a managed
     * entity to become detached. Unflushed changes made to the entity if any
     * (including removal of the entity), will not be synchronized to the
     * database. Entities which previously referenced the detached entity will
     * continue to reference it.
     * 
     * @param entity
     * @throws IllegalArgumentException
     *             if the instance is not an entity
     * 
     * @since Java Persistence 2.0
     */
    public void detach(Object entity) {
        try {
            verifyOpen();
            if (entity == null) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { entity }));
            }
            ClassDescriptor descriptor = this.databaseSession.getDescriptors().get(entity.getClass());
            if (descriptor == null || descriptor.isDescriptorTypeAggregate()) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] { entity }));
            }
            UnitOfWorkImpl uowImpl = (UnitOfWorkImpl) getUnitOfWork();
            uowImpl.unregisterObject(entity, 0, true);
        } catch (RuntimeException exception) {
            setRollbackOnly();
            throw exception;
        }

    }

    /**
     * Return an instance of CriteriaBuilder for the creation of
     * Criteria API Query objects.
     * @return CriteriaBuilder instance
     * @throws IllegalStateException if the entity manager has
     * been closed.
     * @see javax.persistence.EntityManager#getCriteriaBuilder()
     * @since Java Persistence 2.0
     */
    public CriteriaBuilder getCriteriaBuilder() {
        verifyOpenWithSetRollbackOnly();
        // defer to the parent entityManagerFactory
        return this.factory.getCriteriaBuilder();
    }
    
    /**
     * Before any find or refresh operation, gather any persistence unit 
     * properties that should be applied to the query. 
     */
    protected HashMap<String, Object> getQueryHints(Object entity, OperationType operation) {
        HashMap<String, Object> queryHints = null;
    
        // If the entity is null or there are no properties just return null.
        // Individual methods will handle the entity = null case, although we
        // could likely do it here as well.
        if (entity != null && properties != null) {
            queryHints = new HashMap<String, Object>();
            
            if (properties.containsKey(QueryHints.PESSIMISTIC_LOCK_TIMEOUT)) {
                queryHints.put(QueryHints.PESSIMISTIC_LOCK_TIMEOUT, properties.get(QueryHints.PESSIMISTIC_LOCK_TIMEOUT));
            }
            
            // Ignore the JPA cache settings if the eclipselink setting has
            // been specified.
            if (! properties.containsKey(QueryHints.CACHE_USAGE)) { 
                // If the descriptor is isolated then it is not cacheable so ignore 
                // the properties. A null descriptor case will be handled in the 
                // individual operation methods so no need to worry about it here.
                Class cls = entity instanceof Class ? (Class) entity : entity.getClass();
                ClassDescriptor descriptor = getActiveSession().getDescriptor(cls);
            
                if (descriptor != null && ! descriptor.isIsolated()) {
                    if (operation != OperationType.LOCK) {
                        // For a find operation, apply the javax.persistence.cache.retrieveMode
                        if (operation == OperationType.FIND) {
                            if (properties.containsKey(QueryHints.CACHE_RETRIEVE_MODE)) {
                                queryHints.put(QueryHints.CACHE_RETRIEVE_MODE, properties.get(QueryHints.CACHE_RETRIEVE_MODE));
                            } else if (properties.containsKey("javax.persistence.cacheRetrieveMode")) { // support legacy property
                                Session activeSession = getActiveSession();
                                if (activeSession != null) {
                                    // log deprecation info
                                    String[] properties = new String[] { QueryHints.CACHE_RETRIEVE_MODE, "javax.persistence.cacheRetrieveMode" }; 
                                    ((AbstractSession)activeSession).log(SessionLog.INFO, SessionLog.TRANSACTION, "deprecated_property", properties);
                                }
                                queryHints.put(QueryHints.CACHE_RETRIEVE_MODE, properties.get("javax.persistence.cacheRetrieveMode"));
                            }
                        }
                        
                        // For both find and refresh operations, apply javax.persistence.cache.storeMode 
                        if (properties.containsKey(QueryHints.CACHE_STORE_MODE)) {
                            queryHints.put(QueryHints.CACHE_STORE_MODE, properties.get(QueryHints.CACHE_STORE_MODE));
                        } else if (properties.containsKey("javax.persistence.cacheStoreMode")) { // support legacy property
                            Session activeSession = getActiveSession();
                            if (activeSession != null) {
                                // log deprecation info
                                String[] properties = new String[] { QueryHints.CACHE_STORE_MODE, "javax.persistence.cacheStoreMode" }; 
                                ((AbstractSession)activeSession).log(SessionLog.INFO, SessionLog.TRANSACTION, "deprecated_property", properties);
                            }
                            queryHints.put(QueryHints.CACHE_STORE_MODE, properties.get("javax.persistence.cacheStoreMode"));
                        }
                    }
                }
            }
        }
        
        return queryHints;
    }
    
    /**
     * Return an instance of Metamodel interface for access to the
     * metamodel of the persistence unit.
     * @return Metamodel instance
     * @throws IllegalStateException if the entity manager has
     * been closed.
     * @see javax.persistence.EntityManager#getMetamodel()
     * @since Java Persistence 2.0
     */
    public Metamodel getMetamodel() {
        verifyOpenWithSetRollbackOnly();
        // defer to the parent entityManagerFactory
        return this.factory.getMetamodel();
    }

    /**
     * Return the entity manager factory for the entity manager.
     * 
     * @return EntityManagerFactory instance
     * @throws IllegalStateException
     *             if the entity manager has been closed.
     * 
     * @since Java Persistence API 2.0
     */
    public EntityManagerFactory getEntityManagerFactory() {
        try {
            verifyOpen();
            return factory;
        } catch (RuntimeException e) {
            setRollbackOnly();
            throw e;
        }
    }

    /**
     * @see javax.persistence.EntityManager#getLockMode(java.lang.Object)
     * @since Java Persistence API 2.0
     */
    public LockModeType getLockMode(Object entity) {
        try {
            verifyOpen();
            checkForTransaction(true);
            UnitOfWorkImpl uowImpl = getActivePersistenceContext(checkForTransaction(false));
            LockModeType lockMode = LockModeType.NONE;

            if (!contains(entity, uowImpl)) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cant_getLockMode_of_not_managed_object", new Object[] { entity }));
            }
            Boolean optimistickLock = (Boolean) uowImpl.getOptimisticReadLockObjects().get(entity);
            if (optimistickLock != null) {
                if (optimistickLock.equals(Boolean.FALSE)) {
                    lockMode = LockModeType.OPTIMISTIC;
                } else {
                    // The entity is present in the map and its version is
                    // marked for increment.
                    // The lockMode can be OPTIMISTIC_FORCE_INCREMENT ||
                    // PESSIMISTIC_FORCE_INCREMENT
                    if (uowImpl.getPessimisticLockedObjects().get(entity) != null) {
                        lockMode = LockModeType.PESSIMISTIC_FORCE_INCREMENT;
                    } else {
                        lockMode = LockModeType.OPTIMISTIC_FORCE_INCREMENT;
                    }
                }
            } else { // Not optimistically locked
                if (uowImpl.getPessimisticLockedObjects().get(entity) != null) {
                    lockMode = LockModeType.PESSIMISTIC_WRITE;
                }
            }
            return lockMode;
        } catch (RuntimeException exception) {
            setRollbackOnly();
            throw exception;
        }
    }

    /**
     * Get the properties and associated values that are in effect for the
     * entity manager. Changing the contents of the map does not change the
     * configuration in effect.
     * 
     * @since Java Persistence API 2.0
     */
    public Map<String, Object> getProperties() {
        Map sessionMap = new HashMap(getAbstractSession().getProperties());
        if (this.properties != null) {
            sessionMap.putAll(this.properties);
        }
        return Collections.unmodifiableMap(sessionMap);

    }

    /**
     * Get the names of the properties that are supported for use with the
     * entity manager. These correspond to properties and hints that may be
     * passed to the methods of the EntityManager interface that take a
     * properties argument or used with the PersistenceContext annotation. These
     * properties include all standard entity manager hints and properties as
     * well as vendor-specific ones supported by the provider. These properties
     * may or may not currently be in effect.
     * 
     * @return property names
     * 
     * @since Java Persistence API 2.0
     */
    public Set<String> getSupportedProperties() {

        return EntityManagerProperties.getSupportedProperties();
    }

    /**
     * Return an object of the specified type to allow access to the
     * provider-specific API. If the provider's EntityManager implementation
     * does not support the specified class, the PersistenceException is thrown.
     * 
     * @param cls
     *            the class of the object to be returned. This is normally
     *            either the underlying EntityManager implementation class or an
     *            interface that it implements.
     * @return an instance of the specified class
     * @throws PersistenceException
     *             if the provider does not support the call.
     * 
     * @since Java Persistence API 2.0
     */
    public <T> T unwrap(Class<T> cls) {
        try {
            if (cls.equals(UnitOfWork.class) || cls.equals(UnitOfWorkImpl.class) || cls.equals(RepeatableWriteUnitOfWork.class)) {
                return (T) this.getUnitOfWork();
            } else if (cls.equals(JpaEntityManager.class) || cls.equals(EntityManagerImpl.class)) {
                return (T) this;
            } else if (cls.equals(Session.class) || cls.equals(AbstractSession.class)) {            
                return (T) this.getAbstractSession();
            } else if (cls.equals(DatabaseSession.class) || cls.equals(DatabaseSessionImpl.class)) {            
                return (T) this.getDatabaseSession();
            } else if (cls.equals(Server.class) || cls.equals(ServerSession.class)) {            
                return (T) this.getServerSession();
            } else if (cls.equals(SessionBroker.class)) {            
                return (T) this.getSessionBroker();
            } else if (cls.equals(java.sql.Connection.class)) {
                UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl) this.getUnitOfWork();
                if(unitOfWork.isInTransaction() || unitOfWork.getParent().isExclusiveIsolatedClientSession()) {
                    return (T) unitOfWork.getAccessor().getConnection();
                }
                if (checkForTransaction(false) != null) { 
                    unitOfWork.beginEarlyTransaction();
                    Accessor accessor = unitOfWork.getAccessor();
                    // Ensure external connection is acquired.
                    accessor.incrementCallCount(unitOfWork.getParent());
                    accessor.decrementCallCount();
                    return (T) accessor.getConnection();
                }
                return null;
            } else if (cls.getName().equals("javax.resource.cci.Connection")) {                
                UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl) this.getUnitOfWork();
                if(unitOfWork.isInTransaction() || unitOfWork.getParent().isExclusiveIsolatedClientSession()) {
                    return (T) unitOfWork.getAccessor().getConnection();
                }
                if (checkForTransaction(false) != null) { 
                    unitOfWork.beginEarlyTransaction();
                    Accessor accessor = unitOfWork.getAccessor();
                    // Ensure external connection is acquired.
                    accessor.incrementCallCount(unitOfWork.getParent());
                    accessor.decrementCallCount();
                    return (T) accessor.getDatasourceConnection();
                }
                return null;
            }
            throw new PersistenceException(ExceptionLocalization.buildMessage("unable_to_unwrap_jpa", new String[]{EntityManager.class.getName(), cls.getName()}));

        } catch (RuntimeException e) {
            throw e;
        }
    }
    
    /**
     * This method will load the passed entity or collection of entities using the passed AttributeGroup.
     * In case of collection all members should be either entities of the same type
     * or have a common inheritance hierarchy mapped root class.
     * The AttributeGroup should correspond to the entity type. 
     * 
     * @param entityOrEntities
     */
    public void load(Object entityOrEntities, AttributeGroup group) {
        verifyOpen();
        getActivePersistenceContext(checkForTransaction(false)).load(entityOrEntities, group);
    }
    
    /**
     * This method will return copy the passed entity using the passed AttributeGroup.
     * In case of collection all members should be either entities of the same type
     * or have a common inheritance hierarchy mapped root class.
     * The AttributeGroup should correspond to the entity type. 
     * 
     * @param entityOrEntities
     */
    public Object copy(Object entityOrEntities, AttributeGroup group) {
        verifyOpen();
        return getActivePersistenceContext(checkForTransaction(false)).copy(entityOrEntities, group);
    }
    
    /**
     * INTERNAL:
     * Load/fetch the unfetched object.  This method is used by the ClassWaver..
     */
    public static void processUnfetchedAttribute(FetchGroupTracker entity, String attributeName) {
        String errorMsg = entity._persistence_getFetchGroup().onUnfetchedAttribute(entity, attributeName);
        if(errorMsg != null) {
            throw new javax.persistence.EntityNotFoundException(errorMsg);
        }
    }

    /**
     * INTERNAL:
     * Load/fetch the unfetched object.  This method is used by the ClassWeaver.
     */
    public static void processUnfetchedAttributeForSet(FetchGroupTracker entity, String attributeName) {
        String errorMsg = entity._persistence_getFetchGroup().onUnfetchedAttributeForSet(entity, attributeName);
        if(errorMsg != null) {
            throw new javax.persistence.EntityNotFoundException(errorMsg);
        }
    }
    
    public Query createQuery(CriteriaUpdate updateQuery) {
        try{
            verifyOpen();
            return new EJBQueryImpl(((CriteriaUpdateImpl)updateQuery).translate(), this);
        }catch (RuntimeException e){
            setRollbackOnly();
            throw e;
        }
    }

    public Query createQuery(CriteriaDelete deleteQuery) {
        try{
            verifyOpen();
            return new EJBQueryImpl(((CriteriaDeleteImpl)deleteQuery).translate(), this);
        }catch (RuntimeException e){
            setRollbackOnly();
            throw e;
        }
    }

    public boolean isJoinedToTransaction() {
        verifyOpen();
        return transaction.isJoinedToTransaction(this.extendedPersistenceContext);
    }

    public <T> EntityGraph<T> createEntityGraph(Class<T> rootType) {
        ClassDescriptor descriptor = getAbstractSession().getDescriptor(rootType);
        if (descriptor == null || descriptor.isAggregateDescriptor()){
            throw new IllegalArgumentException(ExceptionLocalization.buildMessage("unknown_bean_class", new Object[]{rootType.getName()}));
        }
        return new EntityGraphImpl<T>(new AttributeGroup(null, rootType, true), descriptor);
    }

    public EntityGraph createEntityGraph(String graphName) {
        AttributeGroup group = this.getAbstractSession().getAttributeGroups().get(graphName);
        if (group == null){
            return null;
        }
        ClassDescriptor descriptor = this.getAbstractSession().getDescriptor(group.getType());
        return new EntityGraphImpl(group.clone(), descriptor);
    }

    public EntityGraph getEntityGraph(String graphName) {
        AttributeGroup group = this.getAbstractSession().getAttributeGroups().get(graphName);
        if (group == null){
            throw new IllegalArgumentException(ExceptionLocalization.buildMessage("no_entity_graph_of_name", new Object[]{graphName}));
        }
        return new EntityGraphImpl(group);
    }

    @Override
    public <T> List<EntityGraph<? super T>> getEntityGraphs(Class<T> entityClass) {
        ClassDescriptor descriptor = getAbstractSession().getDescriptor(entityClass);
        if (descriptor == null || descriptor.isAggregateDescriptor()){
            throw new IllegalArgumentException(ExceptionLocalization.buildMessage("unknown_bean_class", new Object[]{entityClass.getName()}));
        }
        List<EntityGraph<? super T>> result = new ArrayList<EntityGraph<? super T>>();
        for (AttributeGroup group : descriptor.getAttributeGroups().values()){
            result.add(new EntityGraphImpl(group));
        }
        if (descriptor.hasInheritance()){
            while(descriptor.getInheritancePolicy().getParentDescriptor() != null){
                descriptor = descriptor.getInheritancePolicy().getParentDescriptor();
                for (AttributeGroup group : descriptor.getAttributeGroups().values()){
                    result.add(new EntityGraphImpl(group));
                }
            }
        }
        return result;
    }

    /**
     * INTERNAL:
     * Tracks if this EntityManager should automatically associate with the transaction or not
     * @return the syncType
     */
    public SynchronizationType getSyncType() {
        return syncType;
    }
}
