/*******************************************************************************
 * Copyright (c) 1998, 2018 Oracle and/or its affiliates, IBM Corporation. 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 updateConnectionPolicy method to support EXCLUSIVE_CONNECTION property.
 *        Some methods, like setSessionEventListener called from deploy still used predeploy properties,
 *        that meant it was impossible to set listener through createEMF property in SE case with an agent - fixed that.
 *        Also if creating / closing the same emSetupImpl many times (24 in my case) "java.lang.OutOfMemoryError: PermGen space" resulted:
 *        partially fixed partially worked around this - see a big comment in predeploy method.
 *     12/23/2008-1.1M5 Michael O'Brien 
 *        - 253701: add persistenceInitializationHelper field used by undeploy() to clear the JavaSECMPInitializer
 *     10/14/2009-2.0      Michael O'Brien 
 *        - 266912: add Metamodel instance field as part of the JPA 2.0 implementation
 *     10/21/2009-2.0 Guy Pelletier 
 *       - 290567: mappedbyid support incomplete
 *     cdelahun - Bug 214534: changes to allow JMSPublishingTransportManager configuration through properties
 *     05/14/2010-2.1 Guy Pelletier 
 *       - 253083: Add support for dynamic persistence using ORM.xml/eclipselink-orm.xml
 *     04/01/2011-2.3 Guy Pelletier 
 *       - 337323: Multi-tenant with shared schema support (part 2)
 *     06/30/2011-2.3.1 Guy Pelletier 
 *       - 341940: Add disable/enable allowing native queries
 *     09/20/2011-2.3.1 Guy Pelletier 
 *       - 357476: Change caching default to ISOLATED for multitenant's using a shared EMF. 
 *     08/01/2012-2.5 Chris Delahunt
 *       - 371950: Metadata caching 
 *     12/24/2012-2.5 Guy Pelletier 
 *       - 389090: JPA 2.1 DDL Generation Support
 *     01/08/2013-2.5 Guy Pelletier 
 *       - 389090: JPA 2.1 DDL Generation Support
 *     01/11/2013-2.5 Guy Pelletier 
 *       - 389090: JPA 2.1 DDL Generation Support
 *     01/16/2013-2.5 Guy Pelletier 
 *       - 389090: JPA 2.1 DDL Generation Support
 *     01/24/2013-2.5 Guy Pelletier 
 *       - 389090: JPA 2.1 DDL Generation Support
 *     02/04/2013-2.5 Guy Pelletier 
 *       - 389090: JPA 2.1 DDL Generation Support
 *     02/19/2013-2.5 Guy Pelletier 
 *       - 389090: JPA 2.1 DDL Generation Support
 *     08/11/2014-2.5 Rick Curtis 
 *       - 440594: Tolerate invalid NamedQuery at EntityManager creation.
 *     11/20/2014-2.5 Rick Curtis
 *       - 452187: Support multiple ClassLoaders to load properties.
 *     01/05/2015 Rick Curtis
 *       - 455683: Automatically detect target server
  *     01/13/2015 - Rick Curtis  
 *       - 438871 : Add support for writing statement terminator character(s) when generating ddl to script.
 *     02/19/2015 - Rick Curtis  
 *       - 458877 : Add national character support
 *     03/04/2015 - Will Dazey 
 *       - 460862 : Added support for JTA schema generation without JTA-DS
 *     09/03/2015 - Will Dazey
 *       - 456067 : Added support for defining query timeout units
 *****************************************************************************/  
package org.eclipse.persistence.internal.jpa;

import static org.eclipse.persistence.config.PersistenceUnitProperties.DDL_GENERATION;
import static org.eclipse.persistence.config.PersistenceUnitProperties.NONE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_CREATE_ACTION;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_CREATE_DATABASE_SCHEMAS;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_CREATE_SCRIPT_SOURCE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_CREATE_SOURCE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_DATABASE_ACTION;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_DROP_ACTION;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_DROP_AND_CREATE_ACTION;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_DROP_SCRIPT_SOURCE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_DROP_SOURCE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_METADATA_SOURCE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_METADATA_THEN_SCRIPT_SOURCE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_NONE_ACTION;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_SCRIPTS_ACTION;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_SCRIPTS_CREATE_TARGET;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_SCRIPTS_DROP_TARGET;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_SCRIPT_SOURCE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_SCRIPT_THEN_METADATA_SOURCE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_SQL_LOAD_SCRIPT_SOURCE;
import static org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.generateDefaultTables;
import static org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.getConfigProperty;
import static org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.getConfigPropertyAsString;
import static org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug;
import static org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.getConfigPropertyLogDebug;
import static org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.hasConfigProperty;
import static org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.login;
import static org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.mergeMaps;
import static org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.translateOldProperties;
import static org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.warnOldProperties;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.persistence.ValidationMode;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.CollectionAttribute;
import javax.persistence.metamodel.ListAttribute;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.MapAttribute;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.SetAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.spi.ClassTransformer;
import javax.persistence.spi.PersistenceUnitInfo;
import javax.persistence.spi.PersistenceUnitTransactionType;

import org.eclipse.persistence.annotations.IdValidation;
import org.eclipse.persistence.config.BatchWriting;
import org.eclipse.persistence.config.CacheCoordinationProtocol;
import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.config.ExclusiveConnectionMode;
import org.eclipse.persistence.config.LoggerType;
import org.eclipse.persistence.config.ParserType;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.config.ProfilerType;
import org.eclipse.persistence.config.PropertiesUtils;
import org.eclipse.persistence.config.RemoteProtocol;
import org.eclipse.persistence.config.SessionCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.MultitenantPolicy;
import org.eclipse.persistence.descriptors.SchemaPerMultitenantPolicy;
import org.eclipse.persistence.descriptors.partitioning.PartitioningPolicy;
import org.eclipse.persistence.dynamic.DynamicClassLoader;
import org.eclipse.persistence.eis.EISConnectionSpec;
import org.eclipse.persistence.eis.EISLogin;
import org.eclipse.persistence.eis.EISPlatform;
import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.EclipseLinkException;
import org.eclipse.persistence.exceptions.EntityManagerSetupException;
import org.eclipse.persistence.exceptions.ExceptionHandler;
import org.eclipse.persistence.exceptions.IntegrityException;
import org.eclipse.persistence.exceptions.PersistenceUnitLoadingException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.databaseaccess.BatchWritingMechanism;
import org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor;
import org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform;
import org.eclipse.persistence.internal.databaseaccess.Platform;
import org.eclipse.persistence.internal.descriptors.OptimisticLockingPolicy;
import org.eclipse.persistence.internal.descriptors.OptimisticLockingPolicy.LockOnChange;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.ConcurrencyManager;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.helper.JPAClassLoaderHolder;
import org.eclipse.persistence.internal.helper.JPAConversionManager;
import org.eclipse.persistence.internal.jpa.deployment.BeanValidationInitializationHelper;
import org.eclipse.persistence.internal.jpa.deployment.PersistenceUnitProcessor;
import org.eclipse.persistence.internal.jpa.deployment.SEPersistenceUnitInfo;
import org.eclipse.persistence.internal.jpa.jdbc.DataSourceImpl;
import org.eclipse.persistence.internal.jpa.metadata.MetadataHelper;
import org.eclipse.persistence.internal.jpa.metadata.MetadataLogger;
import org.eclipse.persistence.internal.jpa.metadata.MetadataProcessor;
import org.eclipse.persistence.internal.jpa.metadata.MetadataProject;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataAsmFactory;
import org.eclipse.persistence.internal.jpa.metadata.xml.XMLEntityMappingsReader;
import org.eclipse.persistence.internal.jpa.metamodel.ManagedTypeImpl;
import org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl;
import org.eclipse.persistence.internal.jpa.metamodel.proxy.AttributeProxyImpl;
import org.eclipse.persistence.internal.jpa.metamodel.proxy.CollectionAttributeProxyImpl;
import org.eclipse.persistence.internal.jpa.metamodel.proxy.ListAttributeProxyImpl;
import org.eclipse.persistence.internal.jpa.metamodel.proxy.MapAttributeProxyImpl;
import org.eclipse.persistence.internal.jpa.metamodel.proxy.SetAttributeProxyImpl;
import org.eclipse.persistence.internal.jpa.metamodel.proxy.SingularAttributeProxyImpl;
import org.eclipse.persistence.internal.jpa.weaving.PersistenceWeaver;
import org.eclipse.persistence.internal.jpa.weaving.TransformerFactory;
import org.eclipse.persistence.internal.localization.ExceptionLocalization;
import org.eclipse.persistence.internal.localization.LoggingLocalization;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedClassForName;
import org.eclipse.persistence.internal.security.PrivilegedGetDeclaredField;
import org.eclipse.persistence.internal.security.PrivilegedGetDeclaredFields;
import org.eclipse.persistence.internal.security.PrivilegedGetDeclaredMethod;
import org.eclipse.persistence.internal.security.PrivilegedGetValueFromField;
import org.eclipse.persistence.internal.security.PrivilegedMethodInvoker;
import org.eclipse.persistence.internal.security.PrivilegedNewInstanceFromClass;
import org.eclipse.persistence.internal.security.SecurableObjectHolder;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.DatabaseSessionImpl;
import org.eclipse.persistence.internal.sessions.PropertiesHandler;
import org.eclipse.persistence.internal.sessions.remote.RemoteConnection;
import org.eclipse.persistence.jpa.metadata.FileBasedProjectCache;
import org.eclipse.persistence.jpa.metadata.MetadataSource;
import org.eclipse.persistence.jpa.metadata.ProjectCache;
import org.eclipse.persistence.jpa.metadata.XMLMetadataSource;
import org.eclipse.persistence.logging.AbstractSessionLog;
import org.eclipse.persistence.logging.DefaultSessionLog;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.platform.database.converters.StructConverter;
import org.eclipse.persistence.platform.database.events.DatabaseEventListener;
import org.eclipse.persistence.platform.database.partitioning.DataPartitioningCallback;
import org.eclipse.persistence.platform.server.CustomServerPlatform;
import org.eclipse.persistence.platform.server.ServerPlatform;
import org.eclipse.persistence.platform.server.ServerPlatformBase;
import org.eclipse.persistence.platform.server.ServerPlatformUtils;
import org.eclipse.persistence.queries.QueryResultsCachePolicy;
import org.eclipse.persistence.sequencing.Sequence;
import org.eclipse.persistence.sessions.Connector;
import org.eclipse.persistence.sessions.DatabaseLogin;
import org.eclipse.persistence.sessions.DatasourceLogin;
import org.eclipse.persistence.sessions.DefaultConnector;
import org.eclipse.persistence.sessions.ExternalTransactionController;
import org.eclipse.persistence.sessions.JNDIConnector;
import org.eclipse.persistence.sessions.Project;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.SessionEventListener;
import org.eclipse.persistence.sessions.SessionProfiler;
import org.eclipse.persistence.sessions.broker.SessionBroker;
import org.eclipse.persistence.sessions.coordination.MetadataRefreshListener;
import org.eclipse.persistence.sessions.coordination.RemoteCommandManager;
import org.eclipse.persistence.sessions.coordination.TransportManager;
import org.eclipse.persistence.sessions.coordination.jms.JMSPublishingTransportManager;
import org.eclipse.persistence.sessions.coordination.jms.JMSTopicTransportManager;
import org.eclipse.persistence.sessions.coordination.rmi.RMITransportManager;
import org.eclipse.persistence.sessions.factories.SessionManager;
import org.eclipse.persistence.sessions.factories.XMLSessionConfigLoader;
import org.eclipse.persistence.sessions.remote.RemoteSession;
import org.eclipse.persistence.sessions.remote.rmi.RMIConnection;
import org.eclipse.persistence.sessions.remote.rmi.RMIServerSessionManager;
import org.eclipse.persistence.sessions.remote.rmi.RMIServerSessionManagerDispatcher;
import org.eclipse.persistence.sessions.serializers.JavaSerializer;
import org.eclipse.persistence.sessions.serializers.Serializer;
import org.eclipse.persistence.sessions.server.ConnectionPolicy;
import org.eclipse.persistence.sessions.server.ConnectionPool;
import org.eclipse.persistence.sessions.server.ExternalConnectionPool;
import org.eclipse.persistence.sessions.server.ReadConnectionPool;
import org.eclipse.persistence.sessions.server.ServerSession;
import org.eclipse.persistence.tools.profiler.PerformanceMonitor;
import org.eclipse.persistence.tools.profiler.PerformanceProfiler;
import org.eclipse.persistence.tools.profiler.QueryMonitor;
import org.eclipse.persistence.tools.schemaframework.SchemaManager;
import org.eclipse.persistence.tools.tuning.SafeModeTuner;
import org.eclipse.persistence.tools.tuning.SessionTuner;
import org.eclipse.persistence.tools.tuning.StandardTuner;

/**
 * INTERNAL:
 * This class handles deployment of a persistence unit.
 * In predeploy the meta-data is processed and weaver transformer is returned to allow weaving of the persistent classes.
 * In deploy the project and session are initialize and registered.
 */
public class EntityManagerSetupImpl implements MetadataRefreshListener {
    /*
     * Design Pattern in use: Builder pattern
     * EntityManagerSetupImpl, MetadataProcessor and MetadataProject
     * play the role of director, builder and product respectively.
     * See processORMetadata which is the factory method.
     * 
     */

    // this name should uniquely identify the persistence unit
    protected String persistenceUnitUniqueName;
    // session name should uniquely identify the session
    protected String sessionName;

    protected MetadataProcessor processor = null;
    /** Holds a reference to the weaver class transformer so it can be cleared after login. */
    protected PersistenceWeaver weaver = null;
    protected PersistenceUnitInfo persistenceUnitInfo = null;
    // count a number of open factories that use this object.
    protected int factoryCount = 0;
    protected AbstractSession session = null;
    // true if predeploy called by createContainerEntityManagerFactory; false - createEntityManagerFactory
    protected boolean isInContainerMode = false;
    protected boolean isSessionLoadedFromSessionsXML=false;
    //project caching:
    protected ProjectCache projectCacheAccessor = null;
    protected boolean shouldBuildProject = true;
    // indicates whether weaving was used on the first run through predeploy (in STATE_INITIAL)
    protected Boolean enableWeaving = null;
    // indicates that classes have already been woven
    protected boolean isWeavingStatic = false;
    // used by static weaving
    protected StaticWeaveInfo staticWeaveInfo;
    protected SecurableObjectHolder securableObjectHolder = new SecurableObjectHolder();
    // used by deploy method
    protected ConcurrencyManager deployLock = new ConcurrencyManager();

    protected boolean requiresConnection;
    
    // 266912: Criteria API and Metamodel API (See Ch 5 of the JPA 2.0 Specification)
    /** Reference to the Metamodel for this deployment and session. 
     * Please use the accessor and not the instance variable directly*/
    private Metamodel metaModel;     
    
    protected List<StructConverter> structConverters = null;
    // factoryCount==0; session==null
    public static final String STATE_INITIAL        = "Initial";
    
    // session != null
    public static final String STATE_PREDEPLOYED    = "Predeployed";
    
    // factoryCount>0; session != null; session stored in SessionManager
    // for compositeMember factoryCount is always 0; session is never stored in SessionBroker
    // the session has not yet connected for the first time or failed to connect for the first time
    public static final String STATE_HALF_DEPLOYED  = "HalfDeployed";
    
    // factoryCount>0; session != null; session stored in SessionManager
    // for compositeMember factoryCount is always 0; session is never stored in SessionBroker
    // the session has connected for the first time
    public static final String STATE_DEPLOYED       = "Deployed";
    
    // factoryCount==0; session==null
    public static final String STATE_PREDEPLOY_FAILED="PredeployFailed";
    
    // factoryCount>0; session != null
    // for compositeMember factoryCount is always 0
    public static final String STATE_DEPLOY_FAILED  = "DeployFailed";
    
    // factoryCount==0; session==null
    public static final String STATE_UNDEPLOYED     = "Undeployed";

    // factoryCount==0; session==null
    // only composite member persistence unit can be in this state
    public static final String STATE_HALF_PREDEPLOYED_COMPOSITE_MEMBER = "HalfPredeployedCompositeMember";
    /**
     *     Initial -----> HalfPredeployedCompositeMember -----> PredeployFailed
     *                    |        ^                   |
     *                    V------->|                   V
     *                                                Predeployed
     */

    protected String state = STATE_INITIAL;

    /**
     *     Initial -----------> PredeployFailed ---
     *           |                                |
     *           V                                |
     *         Predeployed ---> DeployFailed --   |
     *           |                            |   |
     *           V                            V   V
     *         HalfDeployed --> Deployed -> Undeployed
     *           |                            ^
     *           V                            |
     *         DeployFailed -------------------
     *         
     */
    
    
    public static final String ERROR_LOADING_XML_FILE = "error_loading_xml_file";
    public static final String EXCEPTION_LOADING_ENTITY_CLASS = "exception_loading_entity_class";

    /*
     * Properties used to generate sessionName if none is provided.
     */
    public static String[] connectionPropertyNames = {
        PersistenceUnitProperties.TRANSACTION_TYPE,
        PersistenceUnitProperties.JTA_DATASOURCE,
        PersistenceUnitProperties.NON_JTA_DATASOURCE,
        PersistenceUnitProperties.JDBC_URL,
        PersistenceUnitProperties.JDBC_USER,
        PersistenceUnitProperties.NOSQL_CONNECTION_SPEC,
        PersistenceUnitProperties.NOSQL_CONNECTION_FACTORY,
        PersistenceUnitProperties.NOSQL_USER,
        PersistenceUnitProperties.JDBC_CONNECTOR
    };
    
    /*
     * Composite, not null only if it's a composite member.
     */
    protected EntityManagerSetupImpl compositeEmSetupImpl;

    /*
     * Composite members, not null only if it's a composite.
     */
    protected Set<EntityManagerSetupImpl> compositeMemberEmSetupImpls;
    
    /*
     * In HalfPredeployedCompositeMember predeploy method called several times,
     * each call uses mode, then updating it before returning.
     * So mode value could be viewed as a substate of HalfPredeployedCompositeMember state.
     * The mode is required for staging of processing OR metadata for composite members:
     * each processing stage should be completed for ALL composite members before
     * any one of then could proceed to the next processing stage. 
     */
    PersistenceUnitProcessor.Mode mode;
    
    boolean throwExceptionOnFail;
    boolean weaveChangeTracking;
    boolean weaveLazy;
    boolean weaveEager;
    boolean weaveFetchGroups;
    boolean weaveInternal;
    boolean weaveRest;
    
    /**
     * Used to indicate that an EntityManagerFactoryImpl based on this
     * EntityManagerSetupImpl has been refreshed.  This means this EntityManagerSetupImpl
     * will no longer be associated with new EntityManagerFactories
     */
    protected boolean isMetadataExpired = false;
    /*
     * Used to distinguish the various DDL options
     */
    protected enum TableCreationType {NONE, CREATE, DROP, DROP_AND_CREATE, EXTEND};
    
    /*
     * PersistenceException responsible for the invalid state.
     */
    protected PersistenceException persistenceException;

    public EntityManagerSetupImpl(String persistenceUnitUniqueName, String sessionName) {
        this.persistenceUnitUniqueName = persistenceUnitUniqueName;
        this.sessionName = sessionName;
        this.requiresConnection = true;
    }
    
    public EntityManagerSetupImpl() {
        this("", "");
    }
    
    protected static String addFileSeperator(String appLocation) {
        int strLength = appLocation.length();
        if (appLocation.substring(strLength -1, strLength).equals(File.separator)) {
            return appLocation;
        } else {
            return appLocation + File.separator;
        }
    }
    
    /*
     * Return session name if specified.
     * Otherwise build one from the connection properties names and values.
     * Note that specifying value "" in properties causes 
     * the property value specified in PersistenceUnitInfo to be ignored.
     * Never returns null.
     */
    public static String getOrBuildSessionName(Map properties, PersistenceUnitInfo puInfo, String persistenceUnitUniqueName) {
        // Weblogic server was found to prefix the file path on Windows platform with a slash, so mandating that for compatibility
        String persistenceUnitName = assertCompatiblePersistenceUnitName(persistenceUnitUniqueName);

        // if SESSION_NAME is specified in either properties or puInfo properties - use it as session name (unless it's an empty String).
        String sessionName = (String)properties.get(PersistenceUnitProperties.SESSION_NAME);
        if (sessionName == null) {
            sessionName = (String)puInfo.getProperties().get(PersistenceUnitProperties.SESSION_NAME);
        }
        // Specifying empty String in properties allows to remove SESSION_NAME specified in puInfo properties.
        if(sessionName != null && sessionName.length() > 0) {
            return sessionName;
        }

        // ELBug 355603 - Prepend the application id if present in properties.
        // This property will be set by the WebLogic Server if the persistence unit
        // is deployed as part of shared library to construct a unique session name
        String applicationId = (String)properties.get("weblogic.application-id");
        if (isComposite(puInfo)) {
            // Composite doesn't use connection properties.
            if (applicationId != null) {
                return applicationId + persistenceUnitName;
            }
            
            return persistenceUnitName;
        } else {
            // In case no SESSION_NAME specified (or empty String) - build one
            // by concatenating persistenceUnitUniqueName and suffix build of connection properties' names and values.
            if (applicationId != null) {
                return applicationId + persistenceUnitName + buildSessionNameSuffixFromConnectionProperties(properties);
            }
            
            return persistenceUnitName + buildSessionNameSuffixFromConnectionProperties(properties);
        }
    }

    private static String assertCompatiblePersistenceUnitName(String persistenceUnitUniqueName) {
        if(persistenceUnitUniqueName != null && !persistenceUnitUniqueName.startsWith("/")) {
            return '/' + persistenceUnitUniqueName;
        }
        return persistenceUnitUniqueName;
    }

    protected static String buildSessionNameSuffixFromConnectionProperties(Map properties) {
        StringBuilder suffix = new StringBuilder(32);
        for (int i=0; i < connectionPropertyNames.length; i++) {
            String name = connectionPropertyNames[i];
            Object value = properties.get(name);
            if (value != null) {
                String strValue = null;
                if (value instanceof String) {
                    strValue = (String)value;
                } else {
                    if (value instanceof javax.sql.DataSource) {
                        // value of JTA_DATASOURCE / NON_JTA_DATASOURCE may be a DataSource (we would prefer DataSource name)
                        strValue = Integer.toString(System.identityHashCode(value));
                    } else if (value instanceof PersistenceUnitTransactionType) {
                        strValue = value.toString();
                    } else {
                        strValue = Integer.toString(System.identityHashCode(value));                        
                    }
                }
                // don't set an empty String
                if (strValue.length() > 0) {
                    suffix.append("_").append(Helper.getShortClassName(name)).append("=").append(strValue);
                }
            }
        }
        return suffix.toString();
    }
    
    /*
     * Should only be called when emSetupImpl created during SE initialization is set into a new EMF.
     * emSetupImpl must be in PREDEPLOYED state.
     */
    public void changeSessionName(String newSessionName) {
        if(!session.getName().equals(newSessionName)) {
            session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "session_name_change", new Object[]{getPersistenceUnitInfo().getPersistenceUnitName(), session.getName(), newSessionName});
            sessionName = newSessionName;
            session.setName(newSessionName);
        }
    }
    
    /**
     * This method can be used to ensure the session represented by emSetupImpl
     * is removed from the SessionManager.
     */
    protected void removeSessionFromGlobalSessionManager() {
        if (this.session != null){
            try {
                if (this.session.isDatabaseSession() && this.session.isConnected()) {
                    getDatabaseSession().logout();
                }
            } finally {
                SessionManager manager = SessionManager.getManager();
                manager.getSessions().remove(this.session.getName(), this.session);
                manager.destroy();
            }
        }
    }
    
    /**
     * Deploy a persistence session and return an EntityManagerFactory.
     * 
     * Deployment takes a session that was partially created in the predeploy call and makes it whole.
     * 
     * This means doing any configuration that requires the real class definitions for the entities.  In
     * the predeploy phase we were in a stage where we were not let allowed to load the real classes.
     * 
     * Deploy could be called several times - but only the first call does the actual deploying -
     * additional calls allow to update session properties (in case the session is not connected).
     * 
     * Note that there is no need to synchronize deploy method - it doesn't alter factoryCount
     * and while deploy is executed no other method can alter the current state
     * (predeploy call would just increment factoryCount; undeploy call would not drop factoryCount to 0).
     * However precautions should be taken to handle concurrent calls to deploy, because those may
     * alter the current state or connect the session.
     * 
     * @param realClassLoader The class loader that was used to load the entity classes. This loader
     *               will be maintained for the lifespan of the loaded classes.
     * @param additionalProperties added to persistence unit properties for updateServerSession overriding existing properties.
     *              In JSE case it allows to alter properties in main (as opposed to preMain where preDeploy is called).
     * @return An EntityManagerFactory to be used by the Container to obtain EntityManagers
     */
    public AbstractSession deploy(ClassLoader realClassLoader, Map additionalProperties) {
        if (this.state != STATE_PREDEPLOYED && this.state != STATE_DEPLOYED && this.state != STATE_HALF_DEPLOYED) {
            if (mustBeCompositeMember()) {
                throw new PersistenceException(EntityManagerSetupException.compositeMemberCannotBeUsedStandalone(this.persistenceUnitInfo.getPersistenceUnitName()));
            }
            throw new PersistenceException(EntityManagerSetupException.cannotDeployWithoutPredeploy(this.persistenceUnitInfo.getPersistenceUnitName(), this.state, this.persistenceException));
        }
        // state is PREDEPLOYED or DEPLOYED
        this.session.log(SessionLog.FINEST, SessionLog.JPA, "deploy_begin", new Object[]{getPersistenceUnitInfo().getPersistenceUnitName(), this.session.getName(), this.state, this.factoryCount});
        
        ClassLoader classLoaderToUse = realClassLoader;
        
        if (additionalProperties.containsKey(PersistenceUnitProperties.CLASSLOADER)) {
            classLoaderToUse = (ClassLoader) additionalProperties.get(PersistenceUnitProperties.CLASSLOADER);
        } else if ((this.processor != null) && (this.processor.getProject() != null) && (this.processor.getProject().hasVirtualClasses()) && (this.state == STATE_PREDEPLOYED) && (!(classLoaderToUse instanceof DynamicClassLoader))) {
            classLoaderToUse = new DynamicClassLoader(classLoaderToUse);
        }
        
        // indicates whether session has failed to connect, determines whether HALF_DEPLOYED state should be kept in case of exception.
        boolean isLockAcquired = false;
        try {
            Map deployProperties = mergeMaps(additionalProperties, this.persistenceUnitInfo.getProperties());
            updateTunerPreDeploy(deployProperties, classLoaderToUse);
            translateOldProperties(deployProperties, this.session);
            if (isComposite()) {
                updateCompositeMembersProperties(deployProperties);
            }
            if (this.state == STATE_PREDEPLOYED) {
                this.deployLock.acquire();
                isLockAcquired = true;
                if (this.state == STATE_PREDEPLOYED) {
                    if (this.shouldBuildProject && !this.isSessionLoadedFromSessionsXML) {
                        if (isComposite()) {
                            deployCompositeMembers(deployProperties, classLoaderToUse);
                        } else {
                            if (this.processor.getMetadataSource() != null) {
                                Map metadataProperties = this.processor.getMetadataSource().getPropertyOverrides(deployProperties, classLoaderToUse, this.session.getSessionLog());
                                if (metadataProperties != null && !metadataProperties.isEmpty()) {
                                    translateOldProperties(metadataProperties, this.session);
                                    deployProperties = mergeMaps(metadataProperties, deployProperties);
                                }
                            }
                            // listeners and queries require the real classes and are therefore built during deploy using the realClassLoader
                            this.processor.setClassLoader(classLoaderToUse);
                            this.processor.createDynamicClasses();
                            if (classLoaderToUse instanceof DynamicClassLoader){
                                this.processor.createRestInterfaces();
                            }
                            
                            this.processor.addEntityListeners();

                            if (this.projectCacheAccessor != null) {
                                //cache the project:
                                this.projectCacheAccessor.storeProject(this.session.getProject(), deployProperties, this.session.getSessionLog());
                            }

                            // The project is initially created using class names rather than classes.  This call will make the conversion.
                            // If the session was loaded from sessions.xml this will also convert the descriptor classes to the correct class loader.
                            this.session.getProject().convertClassNamesToClasses(classLoaderToUse);

                            if (!isCompositeMember()) {
                                addBeanValidationListeners(deployProperties, classLoaderToUse);
                            }

                            // Process the customizers last.
                            this.processor.processCustomizers();
                        }

                        this.processor = null;
                    } else {
                        // The project is initially created using class names rather than classes.  This call will make the conversion.
                        // If the session was loaded from sessions.xml this will also convert the descriptor classes to the correct class loader.
                        this.session.getProject().convertClassNamesToClasses(classLoaderToUse);
                        if (!this.shouldBuildProject) {
                            //process anything that might not have been serialized/cached in the project correctly:
                            if (!isCompositeMember()) {
                                addBeanValidationListeners(deployProperties, classLoaderToUse);
                            }

                            //process Descriptor customizers:
                            processDescriptorsFromCachedProject(classLoaderToUse);     
                        }
                    }
                    finishProcessingDescriptorEvents(classLoaderToUse);
                    this.structConverters = getStructConverters(classLoaderToUse);
                    
                    updateRemote(deployProperties, classLoaderToUse);
                    initSession();

                    if (this.session.getIntegrityChecker().hasErrors()){
                        this.session.handleException(new IntegrityException(session.getIntegrityChecker()));
                    }

                    this.session.getDatasourcePlatform().getConversionManager().setLoader(classLoaderToUse);
                    this.state = STATE_HALF_DEPLOYED;
                    // keep deployLock
                } else {
                    // state is HALF_DEPLOYED or DEPLOY_FAILED
                    this.deployLock.release();
                    isLockAcquired = false;
                    if (this.state == STATE_DEPLOY_FAILED) {
                        // while this thread waited in STATE_PREDEPLOYED another thread attempted to deploy and failed. 
                        // Rethrow the cache PersistenceException, which caused STATE_DEPLOYED_FAILED.
                        throw persistenceException;
                    }
                }
            }
            // state is HALF_DEPLOYED or DEPLOYED
            if (!isCompositeMember()) {
                if (this.session.isDatabaseSession() && !((DatabaseSessionImpl)session).isLoggedIn()) {
                    // If it's HALF_DEPLOYED then deployLock has been already acquired.
                    if (!isLockAcquired) {
                        this.deployLock.acquire();
                        isLockAcquired = true;
                    }
                    if (!((DatabaseSessionImpl)this.session).isLoggedIn()) {
                        if (this.state == STATE_DEPLOY_FAILED) {
                            // while this thread waited in STATE_HALF_DEPLOYED another thread attempted to connect the session and failed. 
                            // Rethrow the cache PersistenceException, which caused STATE_DEPLOYED_FAILED.
                            throw persistenceException;
                        }
                        this.session.setProperties(deployProperties);
                        updateSession(deployProperties, classLoaderToUse);
                        if (isValidationOnly(deployProperties, false)) {
                            /**
                             * for 324213 we could add a session.loginAndDetectDatasource() call 
                             * before calling initializeDescriptors when validation-only is True
                             * to avoid a native sequence exception on a generic DatabasePlatform 
                             * by auto-detecting the correct DB platform.
                             * However, this would introduce a DB login when validation is on 
                             * - in opposition to the functionality of the property (to only validate)
                             */
                            if (this.state == STATE_HALF_DEPLOYED) {
                                getDatabaseSession().initializeDescriptors();
                                this.state = STATE_DEPLOYED;
                            }
                        } else {
                            try {
                                updateTunerDeploy(deployProperties, classLoaderToUse);
                                updateFreeMemory(deployProperties);
                                if (this.isSessionLoadedFromSessionsXML) {
                                    getDatabaseSession().login();
                                } else {
                                    login(getDatabaseSession(), deployProperties, requiresConnection);
                                }
                                final Platform platform = getDatabaseSession().getDatasourcePlatform();
                                PropertiesUtils.set(platform, PersistenceUnitProperties.TARGET_DATABASE_PROPERTIES, (String) deployProperties.get(PersistenceUnitProperties.TARGET_DATABASE_PROPERTIES));
                                
                                // Make JTA integration throw JPA exceptions.
                                if (this.session.hasExternalTransactionController()) {
                                    if (this.session.getExternalTransactionController().getExceptionHandler() == null) {
                                        this.session.getExternalTransactionController().setExceptionHandler(new ExceptionHandler() {
                                            
                                            public Object handleException(RuntimeException exception) {
                                                if (exception instanceof org.eclipse.persistence.exceptions.OptimisticLockException) {
                                                    throw new OptimisticLockException(exception);
                                                } else if (exception instanceof EclipseLinkException) {
                                                    throw new PersistenceException(exception);
                                                } else {
                                                    throw exception;
                                                }
                                            }
                                            
                                        });
                                    }
                                }
                                this.state = STATE_DEPLOYED;
                            } catch (Throwable loginException) {
                                if (this.state == STATE_HALF_DEPLOYED) {
                                    if (this.session.isConnected()) {
                                        // session is connected, but postConnect has failed.
                                        // Likely this is caused by failure in initializeDescriptors: 
                                        // either descriptor exception or by invalid named jpql query.
                                        // Cannot recover from that - the user has to fix the persistence unit and redeploy it.
                                        try {
                                            getDatabaseSession().logout();
                                        } catch (Throwable logoutException) {
                                            // Ignore
                                        }
                                        this.state = STATE_DEPLOY_FAILED;
                                    }
                                }
                                throw loginException;
                            }
                            if (!this.isSessionLoadedFromSessionsXML) {
                                addStructConverters();
                            }
                            
                            // Generate the DDL using the correct connection. 
                            writeDDL(deployProperties, getDatabaseSession(deployProperties), classLoaderToUse);    
                        }
                    }
                    updateTunerPostDeploy(deployProperties, classLoaderToUse);
                    this.deployLock.release();
                    isLockAcquired = false;
                }
                // 266912: Initialize the Metamodel, a login should have already occurred.
                try {
                    this.getMetamodel(classLoaderToUse);
                } catch (Exception e) {
                    this.session.log(SessionLog.FINEST, SessionLog.METAMODEL, "metamodel_init_failed", new Object[]{e.getMessage()});
                }
            }
            // Clear the weaver's reference to meta-data information, as it is held by the class loader and will never gc.
            if (this.weaver != null) {
                this.weaver.clear();
                this.weaver = null;
            }            
            
            return this.session;
        } catch (Throwable exception) {
            // before releasing deployLock switch to the correct state
            if (this.state == STATE_PREDEPLOYED) {
                this.state = STATE_DEPLOY_FAILED;
            }
            PersistenceException persistenceEx;
            if (this.state == STATE_DEPLOY_FAILED) {
                if (exception == persistenceException) {
                    persistenceEx = new PersistenceException(EntityManagerSetupException.cannotDeployWithoutPredeploy(this.persistenceUnitInfo.getPersistenceUnitName(), this.state, this.persistenceException));
                } else {
                    // before releasing deployLock cache the exception
                    persistenceEx = createDeployFailedPersistenceException(exception);
                }
            } else {
                if (exception instanceof PersistenceException) {
                    persistenceEx = (PersistenceException)exception;
                } else {
                    persistenceEx = new PersistenceException(exception);
                }
            }
            if (isLockAcquired) {
                this.deployLock.release();
            }
            this.session.logThrowable(SessionLog.SEVERE, SessionLog.EJB, exception);
            throw persistenceEx;
        } finally {
            this.session.log(SessionLog.FINEST, SessionLog.JPA, "deploy_end", new Object[]{getPersistenceUnitInfo().getPersistenceUnitName(), this.session.getName(), this.state, this.factoryCount});
        }
    }

    /**
     * INTERNAL:
     * This method is used to resolve Descriptor Customizers that might have been stored in the project
     * for JPA project caching.  
     * 
     * @param realClassLoader
     * @throws ClassNotFoundException
     * @throws PrivilegedActionException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private void processDescriptorsFromCachedProject(ClassLoader realClassLoader) throws ClassNotFoundException, PrivilegedActionException, IllegalAccessException, InstantiationException {
        for (ClassDescriptor descriptor: session.getProject().getDescriptors().values()) {
            //process customizers:
            if (descriptor.getDescriptorCustomizerClassName() != null) {
                Class listenerClass = this.findClass(descriptor.getDescriptorCustomizerClassName(), realClassLoader);
                DescriptorCustomizer customizer = (DescriptorCustomizer)this.buildObjectForClass(listenerClass, DescriptorCustomizer.class);     
                try {
                    customizer.customize(descriptor);
                } catch (Exception e) {
                    session.getSessionLog().logThrowable(SessionLog.FINER, SessionLog.METADATA, e);
                }
            }
        }
    }

    /**
     * INTERNAL:
     * This method is used to resolve Descriptor Events processed earlier into EventHolders that now need to be
     * used to create the DescriptorEventListeners and added to the DescriptorEventManager 
     *
     */
    private void finishProcessingDescriptorEvents(ClassLoader realClassLoader) {
        for (ClassDescriptor descriptor: session.getProject().getDescriptors().values()) {
            if (descriptor.hasEventManager()) {
                descriptor.getEventManager().processDescriptorEventHolders(session, realClassLoader);
            }
        }
    }

    protected PersistenceException createDeployFailedPersistenceException(Throwable ex) {
        PersistenceException perEx = new PersistenceException(EntityManagerSetupException.deployFailed(persistenceUnitInfo.getPersistenceUnitName(), ex));
        if (persistenceException == null) {
            persistenceException = perEx;
        }
        return perEx;
    }

    /**
     * Adds descriptors plus sequencing info found on the project to the session.
     */
    protected void addProjectToSession(ServerSession session, Project project) {
        DatasourcePlatform sessionPlatform = (DatasourcePlatform)session.getDatasourceLogin().getDatasourcePlatform();
        DatasourcePlatform projectPlatform = (DatasourcePlatform)project.getDatasourceLogin().getDatasourcePlatform();
        if (!sessionPlatform.hasDefaultSequence() && projectPlatform.hasDefaultSequence()) {
            sessionPlatform.setDefaultSequence(projectPlatform.getDefaultSequence());
        }
        if ((sessionPlatform.getSequences() == null) || sessionPlatform.getSequences().isEmpty()) {
            if ((projectPlatform.getSequences() != null) && !projectPlatform.getSequences().isEmpty()) {
                sessionPlatform.setSequences(projectPlatform.getSequences());
            }
        } else {
            if ((projectPlatform.getSequences() != null) && !projectPlatform.getSequences().isEmpty()) {
                Iterator itProjectSequences = projectPlatform.getSequences().values().iterator();
                while (itProjectSequences.hasNext()) {
                    Sequence sequence = (Sequence)itProjectSequences.next();
                    if (!sessionPlatform.getSequences().containsKey(sequence.getName())) {
                        sessionPlatform.addSequence(sequence);
                    }
                }
            }
        }
        session.addDescriptors(project);
    }
    
    /**
     * Put the given session into the session manager so it can be looked up later
     */
    protected void addSessionToGlobalSessionManager() {
        SessionManager sm = SessionManager.getManager();
        ConcurrentMap<String,Session> sessions = sm.getSessions();
        AbstractSession oldSession = (AbstractSession) sessions.get(session.getName());
        if(oldSession != null) {
            throw new PersistenceException(EntityManagerSetupException.attemptedRedeployWithoutClose(session.getName()));
        }
        sm.addSession(session);
    }

    /**
     * Add the StructConverters that were specified by annotation on the DatabasePlatform
     * This method must be called after the DatabasePlatform has been detected
     */
    public void addStructConverters(){
        if (this.compositeMemberEmSetupImpls == null) {
            for (StructConverter structConverter : structConverters){
                if (session.getPlatform().getTypeConverters().get(structConverter.getJavaType()) != null){
                    throw ValidationException.twoStructConvertersAddedForSameClass(structConverter.getJavaType().getName());
                }
                session.getPlatform().addStructConverter(structConverter);
            }
        } else {
            // composite
            for(EntityManagerSetupImpl compositeMemberEmSetupImpl : this.compositeMemberEmSetupImpls) {
                if (!compositeMemberEmSetupImpl.structConverters.isEmpty()) {
                    String compositeMemberPuName = compositeMemberEmSetupImpl.getPersistenceUnitInfo().getPersistenceUnitName();
                    // debug output added to make it easier to navigate the log because the method is called outside of composite member deploy
                    compositeMemberEmSetupImpl.session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "composite_member_begin_call", new Object[]{"addStructConverters", compositeMemberPuName, state});
                    compositeMemberEmSetupImpl.addStructConverters();
                    compositeMemberEmSetupImpl.session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "composite_member_end_call", new Object[]{"addStructConverters", compositeMemberPuName, state});
                }
            }
        }
    }

    /**
     * Assign a CMP3Policy to each descriptor, and sets the OptimisticLockingPolicy's LockOnChangeMode if applicable.
     */
    protected void assignCMP3Policy() {
        // all descriptors assigned CMP3Policy
        Project project = session.getProject();
        for (Iterator iterator = project.getDescriptors().values().iterator(); iterator.hasNext();){
            //bug:4406101  changed class cast to base class, which is used in projects generated from 904 xml
            ClassDescriptor descriptor = (ClassDescriptor)iterator.next();
            
            if(descriptor.getCMPPolicy() == null) {
                descriptor.setCMPPolicy(new CMP3Policy());
            }
            OptimisticLockingPolicy olp = descriptor.getOptimisticLockingPolicy();
            if (olp != null && olp.getLockOnChangeMode() == null){
                olp.setLockOnChangeMode(LockOnChange.OWNING);
            }
        }

        // TODO: Look into setting a CMPPolicy on the MappedSuperclass descriptors.
        // Will require some tweaking however to ensure the primary key fields are
        // set/initialized correctly. Currently rely on the descriptor initialized
        // object builder which is not available to mapped superclass descriptors.
    }

    /**
     * Updates the EclipseLink ServerPlatform class for use with this platform.
     * @return true if the ServerPlatform has changed.
     */  
    protected boolean updateServerPlatform(Map m, ClassLoader loader) {
        String serverPlatformClassName =
            PropertiesHandler.getPropertyValueLogDebug(PersistenceUnitProperties.TARGET_SERVER, m, session);
        if (serverPlatformClassName == null) {
            // property is not specified - try to detect.
            serverPlatformClassName = ServerPlatformUtils.detectServerPlatform(getSession());
            if (serverPlatformClassName == null) {
                // Unable to detect what platform we're running on. Use default/NoServer.
                return false;
            }
        }

        // originalServerPlatform is always non-null - Session's constructor sets serverPlatform to NoServerPlatform
        ServerPlatform originalServerPlatform = session.getServerPlatform();
        String originalServerPlatformClassName = originalServerPlatform.getClass().getName();
        if(originalServerPlatformClassName.equals(serverPlatformClassName)) {
            // nothing to do - use the same value as before
            return false;
        }

        // the new serverPlatform
        ServerPlatform serverPlatform = null;
        // New platform - create the new instance and set it.
        Class cls = findClassForProperty(serverPlatformClassName, PersistenceUnitProperties.TARGET_SERVER, loader);
        try {
            Constructor constructor = cls.getConstructor(new Class[]{org.eclipse.persistence.sessions.DatabaseSession.class});
            serverPlatform = (ServerPlatform)constructor.newInstance(new Object[]{session});
        } catch (Exception ex) {
            if(ExternalTransactionController.class.isAssignableFrom(cls)) {
                // the new serverPlatform is CustomServerPlatform, cls is its ExternalTransactionController class
                if(originalServerPlatform.getClass().equals(CustomServerPlatform.class)) {
                    // both originalServerPlatform and the new serverPlatform are Custom,
                    // just set externalTransactionController class (if necessary) into
                    // originalServerPlatform
                    CustomServerPlatform originalCustomServerPlatform = (CustomServerPlatform)originalServerPlatform;
                    if(cls.equals(originalCustomServerPlatform.getExternalTransactionControllerClass())) {
                        // externalTransactionController classes are the same - nothing to do
                    } else {
                        originalCustomServerPlatform.setExternalTransactionControllerClass(cls);
                    }
                } else {
                    // originalServerPlatform is not custom - need a new one.
                    CustomServerPlatform customServerPlatform = new CustomServerPlatform(getDatabaseSession());
                    customServerPlatform.setExternalTransactionControllerClass(cls);
                    serverPlatform = customServerPlatform;
                }
             } else {
                 throw EntityManagerSetupException.failedToInstantiateServerPlatform(serverPlatformClassName, PersistenceUnitProperties.TARGET_SERVER, ex);
             }
         }
 
        if (serverPlatform != null){
            getDatabaseSession().setServerPlatform(serverPlatform);
            return true;
        }    
        return false;
    }

    /**
     * Checks for partitioning properties.
     */  
    protected void updatePartitioning(Map m, ClassLoader loader) {
        // Partitioning
        String partitioning = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.PARTITIONING, m, this.session);
        if (partitioning != null) {
            PartitioningPolicy partitioningPolicy = this.session.getProject().getPartitioningPolicy(partitioning);
            if (partitioningPolicy == null) {
                throw DescriptorException.missingPartitioningPolicy(partitioning, null, null);
            }
            this.session.setPartitioningPolicy(partitioningPolicy);
        }
        
        String callbackClassName = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.PARTITIONING_CALLBACK, m, this.session);
        if (callbackClassName != null) {
            Class cls = findClassForProperty(callbackClassName, PersistenceUnitProperties.PARTITIONING_CALLBACK, loader);
            DataPartitioningCallback callback = null;
            try {
                Constructor constructor = cls.getConstructor();
                callback = (DataPartitioningCallback)constructor.newInstance();
            } catch (Exception exception) {
                throw EntityManagerSetupException.failedToInstantiateProperty(callbackClassName, PersistenceUnitProperties.PARTITIONING_CALLBACK, exception);
            }
            this.session.getLogin().setPartitioningCallback(callback);
        }
    }

    /**
     * Checks for partitioning properties.
     */  
    protected void updateRemote(Map m, ClassLoader loader) {        
        String protocol = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.REMOTE_PROTOCOL, m, this.session);
        String serverName = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.REMOTE_SERVER_NAME, m, this.session);
        if (serverName == null) {
            // Configure as client.
            if (protocol != null) {
                RemoteConnection connection = null;
                if (protocol.equalsIgnoreCase(RemoteProtocol.RMI)) {
                    String url = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.REMOTE_URL, m, this.session);
                    if (url == null) {
                        throw EntityManagerSetupException.missingProperty(PersistenceUnitProperties.REMOTE_URL);
                    }
                    try {
                        connection = new RMIConnection(((RMIServerSessionManager)Naming.lookup(url)).createRemoteSessionController());
                    } catch (Exception exception) {
                        throw ValidationException.invalidValueForProperty(url, PersistenceUnitProperties.REMOTE_URL, exception);                    
                    }
                } else {
                    Class cls = findClassForProperty(protocol, PersistenceUnitProperties.REMOTE_PROTOCOL, loader);                
                    try {
                        Constructor constructor = cls.getConstructor();
                        connection = (RemoteConnection)constructor.newInstance();
                    } catch (Exception exception) {
                        throw ValidationException.invalidValueForProperty(protocol, PersistenceUnitProperties.REMOTE_PROTOCOL, exception);
                    }
                }
                RemoteSession remoteSession = new RemoteSession();
                remoteSession.setIsMetadataRemote(false);
                remoteSession.setProject(this.session.getProject());
                remoteSession.setProfiler(this.session.getProfiler());
                remoteSession.setSessionLog(this.session.getSessionLog());
                remoteSession.setEventManager(this.session.getEventManager());
                remoteSession.setQueries(this.session.getQueries());
                remoteSession.setProperties(this.session.getProperties());
                remoteSession.setName(this.session.getName());
                remoteSession.setRemoteConnection(connection);
                this.session = remoteSession;
            }
        } else {
            // Configure as server.
            if (protocol.equalsIgnoreCase(RemoteProtocol.RMI)) {
                RMIServerSessionManager manager = null;
                // Make sure RMI registry is started.
                try {
                    java.rmi.registry.LocateRegistry.createRegistry(1099);
                } catch (Exception exception) {
                    System.out.println("Security violation " + exception.toString());
                }
                // Create local instance of the factory
                try {
                    manager = new RMIServerSessionManagerDispatcher(session);
                } catch (RemoteException exception) {
                    ValidationException.invalidValueForProperty(serverName, PersistenceUnitProperties.REMOTE_SERVER_NAME, exception);
                }
                // Put the local instance into the Registry
                try {
                    Naming.unbind(serverName);
                } catch (Exception exception) {
                    // Ignore.
                }

                // Put the local instance into the Registry
                try {
                    Naming.rebind(serverName, manager);
                } catch (Exception exception) {
                    ValidationException.invalidValueForProperty(serverName, PersistenceUnitProperties.REMOTE_SERVER_NAME, exception);
                }
            }
        }
    }

    /**
     * Checks for database events listener properties.
     */  
    protected void updateDatabaseEventListener(Map m, ClassLoader loader) {
        String listenerClassName = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.DATABASE_EVENT_LISTENER, m, this.session);
        if (listenerClassName != null) {
            if (listenerClassName.equalsIgnoreCase("DCN") || listenerClassName.equalsIgnoreCase("QCN")) {
                listenerClassName = "org.eclipse.persistence.platform.database.oracle.dcn.OracleChangeNotificationListener";
            }
            Class cls = findClassForProperty(listenerClassName, PersistenceUnitProperties.DATABASE_EVENT_LISTENER, loader);
            DatabaseEventListener listener = null;
            try {
                Constructor constructor = cls.getConstructor();
                listener = (DatabaseEventListener)constructor.newInstance();
            } catch (Exception exception) {
                throw EntityManagerSetupException.failedToInstantiateProperty(listenerClassName, PersistenceUnitProperties.DATABASE_EVENT_LISTENER, exception);
            }
            getDatabaseSession().setDatabaseEventListener(listener);
        }
    }
    
    /**
     * Update loggers and settings for the singleton logger and the session logger. 
     * @param persistenceProperties the properties map
     * @param serverPlatformChanged the boolean that denotes a serverPlatform change in the session.
     */
    protected void updateLoggers(Map persistenceProperties, boolean serverPlatformChanged, ClassLoader loader) {
        // Logger(SessionLog type) can be specified by the logger property or ServerPlatform.getServerLog().
        // The logger property has a higher priority to ServerPlatform.getServerLog().
        String loggerClassName = PropertiesHandler.getPropertyValueLogDebug(PersistenceUnitProperties.LOGGING_LOGGER, persistenceProperties, session);

        // The sessionLog instance should be different from the singletonLog because they have 
        // different state.
        SessionLog singletonLog = null, sessionLog = null;
        if (loggerClassName != null) {
            SessionLog currentLog = session.getSessionLog();
            if(loggerClassName.equals(LoggerType.ServerLogger)){
                ServerPlatform serverPlatform = session.getServerPlatform();
                singletonLog = serverPlatform.getServerLog();
                sessionLog = serverPlatform.getServerLog();
            } else if (!currentLog.getClass().getName().equals(loggerClassName)) {
                // Logger class was specified and it's not what's already there.
                Class sessionLogClass = findClassForProperty(loggerClassName, PersistenceUnitProperties.LOGGING_LOGGER, loader);
                try {
                    singletonLog = (SessionLog)sessionLogClass.newInstance();
                    sessionLog = (SessionLog)sessionLogClass.newInstance();
                } catch (Exception ex) {
                    throw EntityManagerSetupException.failedToInstantiateLogger(loggerClassName, PersistenceUnitProperties.LOGGING_LOGGER, ex);
                }
            }
        } else if (serverPlatformChanged) {
            ServerPlatform serverPlatform = session.getServerPlatform();
            singletonLog = serverPlatform.getServerLog();
            sessionLog = serverPlatform.getServerLog();
        }
        
        // Don't change default loggers if the new loggers have not been created.
        if (singletonLog != null && sessionLog != null) {
            AbstractSessionLog.setLog(singletonLog);
            session.setSessionLog(sessionLog);
        }

        // Bug5389828.  Update the logging settings for the singleton logger.
        initOrUpdateLogging(persistenceProperties, AbstractSessionLog.getLog());
        initOrUpdateLogging(persistenceProperties, session.getSessionLog());
        // Set logging file.
        String loggingFileString = (String)persistenceProperties.get(PersistenceUnitProperties.LOGGING_FILE);
        if (loggingFileString != null) {
            if (!loggingFileString.trim().equals("")) {
                try {
                    if (sessionLog!=null){
                        if (sessionLog instanceof AbstractSessionLog) {
                            FileOutputStream fos = new FileOutputStream(loggingFileString);
                           ((AbstractSessionLog)sessionLog).setWriter(fos);
                        } else {
                            FileWriter fw = new FileWriter(loggingFileString); 
                            sessionLog.setWriter(fw);
                        }
                    }
                } catch (IOException e) {
                    session.handleException(ValidationException.invalidLoggingFile(loggingFileString,e));
                }
            } else {
                session.handleException(ValidationException.invalidLoggingFile());
            }
        }
    }
    
    /**
     * Check for the PROFILER persistence or system property and set the Session's profiler.
     * This can also set the QueryMonitor.
     */
    protected void updateProfiler(Map persistenceProperties,ClassLoader loader) {
        // This must use config property as the profiler is not in the PropertiesHandler and requires
        // supporting generic profiler classes.
        String newProfilerClassName = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.PROFILER, persistenceProperties, session);

        if (newProfilerClassName == null) {
            ServerPlatformBase plaftorm = ((ServerPlatformBase)session.getServerPlatform());
            if (plaftorm != null) {
                plaftorm.configureProfiler(session);
            }
        } else {
            if (newProfilerClassName.equals(ProfilerType.NoProfiler)) {
                session.setProfiler(null);
                return;
            }
            if (newProfilerClassName.equals(ProfilerType.QueryMonitor)) {
                session.setProfiler(null);
                QueryMonitor.shouldMonitor=true;
                return;
            }
            if (newProfilerClassName.equals(ProfilerType.PerformanceProfiler)) {
                session.setProfiler(new PerformanceProfiler());
                return;
            }
            if (newProfilerClassName.equals(ProfilerType.PerformanceMonitor)) {
                session.setProfiler(new PerformanceMonitor());
                return;
            }
            
            if (newProfilerClassName.equals(ProfilerType.DMSProfiler)) {
                newProfilerClassName = ProfilerType.DMSProfilerClassName;
            }
            
            String originalProfilerClassNamer = null;
            if (session.getProfiler() != null) {
                originalProfilerClassNamer = session.getProfiler().getClass().getName();
                if (originalProfilerClassNamer.equals(newProfilerClassName)) {
                    return;
                }
            }
            
            // New profiler - create the new instance and set it.
            try {
                Class newProfilerClass = findClassForProperty(newProfilerClassName, PersistenceUnitProperties.PROFILER, loader);

                SessionProfiler sessionProfiler = (SessionProfiler)buildObjectForClass(newProfilerClass, SessionProfiler.class);

                if (sessionProfiler != null) {
                    session.setProfiler(sessionProfiler);
                } else {
                    session.handleException(ValidationException.invalidProfilerClass(newProfilerClassName));
                }
            } catch (IllegalAccessException e) {
                session.handleException(ValidationException.cannotInstantiateProfilerClass(newProfilerClassName,e));
            } catch (PrivilegedActionException e) {
                session.handleException(ValidationException.cannotInstantiateProfilerClass(newProfilerClassName,e));
            } catch (InstantiationException e) {
                session.handleException(ValidationException.cannotInstantiateProfilerClass(newProfilerClassName,e));
            }
        }
    }
    
    
    protected static Class findClass(String className, ClassLoader loader) throws ClassNotFoundException, PrivilegedActionException {
        if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
            return AccessController.doPrivileged(new PrivilegedClassForName(className, true, loader));
        } else {
            return org.eclipse.persistence.internal.security.PrivilegedAccessHelper.getClassForName(className, true, loader);
        }
    }
    
    protected static Class findClassForProperty(String className, String propertyName, ClassLoader loader) {
        ClassLoader eclipselinkLoader = EntityManagerSetupImpl.class.getClassLoader();
        boolean multipleLoaders = eclipselinkLoader != loader;
        if (multipleLoaders) {
            return findClassForPropertyInternal(className, propertyName, loader, eclipselinkLoader);
        } else {
            return findClassForPropertyInternal(className, propertyName, loader);
        }
    }

    private static Class findClassForPropertyInternal(String clsName, String propName, ClassLoader... loaders) {
        RuntimeException e = null;
        for (ClassLoader loader : loaders) {
            try {
                return findClass(clsName, loader);
            } catch (PrivilegedActionException exception1) {
                e = EntityManagerSetupException.classNotFoundForProperty(clsName, propName, exception1.getException());
            } catch (ClassNotFoundException exception2) {
                e = EntityManagerSetupException.classNotFoundForProperty(clsName, propName, exception2);
            }
        }

        throw e;
    }

    /**
     * Internal: 
     * Returns a list of StructConverter instances from a list of StructConverter names stored within the project.
     * 
     * @param realClassLoader
     * @return
     */
    protected List<StructConverter> getStructConverters(ClassLoader realClassLoader) {
        List<StructConverter> structConverters = new ArrayList<StructConverter>();
        if (session.getProject().getStructConverters() != null) {
            for (String converter: session.getProject().getStructConverters()) {
                Class clazz = null;
                try {
                    clazz = this.findClass(converter, realClassLoader);
                } catch (PrivilegedActionException exception) {
                    throw ValidationException.unableToLoadClass(converter, exception.getException());
                } catch (ClassNotFoundException exception) {
                    throw ValidationException.unableToLoadClass(converter, exception);
                }
                
                try {
                    structConverters.add((StructConverter)this.buildObjectForClass(clazz, clazz));
                } catch (PrivilegedActionException e) {
                    throw ValidationException.errorInstantiatingClass(clazz, e.getException());
                } catch (IllegalAccessException e) {
                    throw ValidationException.errorInstantiatingClass(clazz, e);
                }  catch (InstantiationException e) {
                    throw ValidationException.errorInstantiatingClass(clazz, e);
                }
            }
        }
        return structConverters;
    }

    protected boolean hasSchemaDatabaseGeneration(Map m) {
        if (hasConfigProperty(SCHEMA_GENERATION_DATABASE_ACTION, m)) {
            return getConfigPropertyAsString(SCHEMA_GENERATION_DATABASE_ACTION, m) != null && ! getConfigPropertyAsString(SCHEMA_GENERATION_DATABASE_ACTION, m).equals(SCHEMA_GENERATION_NONE_ACTION);
        }
        
        return false;
    }
    
    protected boolean hasSchemaScriptsGeneration(Map m) {
        if (hasConfigProperty(SCHEMA_GENERATION_SCRIPTS_ACTION, m)) {
            return getConfigPropertyAsString(SCHEMA_GENERATION_SCRIPTS_ACTION, m) != null && ! getConfigPropertyAsString(SCHEMA_GENERATION_SCRIPTS_ACTION, m).equals(SCHEMA_GENERATION_NONE_ACTION);
        }
        
        return false;
    }
    
    public AbstractSession getSession() {
        return session;
    }

    public DatabaseSessionImpl getDatabaseSession() {
        return (DatabaseSessionImpl)session;
    }
    
    /**
     * We may be provided a connection via the properties to use. Check for
     * one and build a database session around it. Otherwise return the pu 
     * database session.
     */
    public DatabaseSessionImpl getDatabaseSession(Map props) {
        DatabaseSessionImpl databaseSession = getDatabaseSession();
        Object connection = getConfigProperty(PersistenceUnitProperties.SCHEMA_GENERATION_CONNECTION, props);
        
        if (connection == null) {
            return databaseSession;
        } else {
            // A connection was provided. Build a database session using that 
            // connection and use the same log level set on the original 
            // database session.
            DatabaseSessionImpl newDatabaseSession = new DatabaseSessionImpl();
            newDatabaseSession.setAccessor(new DatabaseAccessor(connection));
            newDatabaseSession.setLogLevel(databaseSession.getLogLevel());
            newDatabaseSession.setProject(databaseSession.getProject().clone());
            return newDatabaseSession;
        }
    }
    
    /**
     * This method will be used to validate the specified class and return it's instance.
     */
    protected static Object buildObjectForClass(Class clazz, Class mustBeImplementedInterface) throws IllegalAccessException, PrivilegedActionException,InstantiationException {
        if(clazz!=null && Helper.classImplementsInterface(clazz,mustBeImplementedInterface)){
            if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                return AccessController.doPrivileged(new PrivilegedNewInstanceFromClass(clazz));
            } else {
                return PrivilegedAccessHelper.newInstanceFromClass(clazz);
            }
        } else {
            return null;
        }
    }
    
    protected void updateDescriptorCacheSettings(Map m, ClassLoader loader) {
        String queryCache = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.QUERY_CACHE, m, session);
        if ((queryCache != null) && queryCache.equalsIgnoreCase("true")) {
            session.getProject().setDefaultQueryResultsCachePolicy(new QueryResultsCachePolicy());
        }
        
        Map typeMap = PropertiesHandler.getPrefixValuesLogDebug(PersistenceUnitProperties.CACHE_TYPE_, m, session);
        Map sizeMap = PropertiesHandler.getPrefixValuesLogDebug(PersistenceUnitProperties.CACHE_SIZE_, m, session);
        Map sharedMap = PropertiesHandler.getPrefixValuesLogDebug(PersistenceUnitProperties.CACHE_SHARED_, m, session);
        if(typeMap.isEmpty() && sizeMap.isEmpty() && sharedMap.isEmpty()) {
            return;
        }

        String defaultTypeName = (String)typeMap.remove(PersistenceUnitProperties.DEFAULT);
        if (defaultTypeName != null) {
            // Always use the EclipseLink class loader, otherwise can have loader/redeployment issues.
            Class defaultType = findClassForProperty(defaultTypeName, PersistenceUnitProperties.CACHE_TYPE_DEFAULT, getClass().getClassLoader());
            session.getProject().setDefaultIdentityMapClass(defaultType);
        }
        
        String value = null;
        try {
            String defaultSizeString = (String)sizeMap.remove(PersistenceUnitProperties.DEFAULT);
            if (defaultSizeString != null) {
                value = defaultSizeString;
                int defaultSize = Integer.parseInt(defaultSizeString);
                session.getProject().setDefaultIdentityMapSize(defaultSize);
            }
            
            String defaultSharedString = (String)sharedMap.remove(PersistenceUnitProperties.DEFAULT);
            if (defaultSharedString != null) {
                boolean defaultShared = Boolean.parseBoolean(defaultSharedString);
                session.getProject().setDefaultIsIsolated(!defaultShared);
            }
            
            Iterator it = session.getDescriptors().values().iterator();
            while (it.hasNext() && (!typeMap.isEmpty() || !sizeMap.isEmpty() || !sharedMap.isEmpty())) {
                ClassDescriptor descriptor = (ClassDescriptor)it.next();
                
                if (descriptor.isDescriptorTypeAggregate()) {
                    continue;
                }
                
                String entityName = descriptor.getAlias();
                String className = descriptor.getJavaClass().getName();
                String name;
                
                name = entityName;
                String typeName = (String)typeMap.remove(name);
                if( typeName == null) {
                    name = className;
                    typeName = (String)typeMap.remove(name);
                }
                if (typeName != null) {
                    Class type = findClassForProperty(typeName, PersistenceUnitProperties.CACHE_TYPE_ + name, getClass().getClassLoader());
                    descriptor.setIdentityMapClass(type);
                }
    
                name = entityName;
                String sizeString = (String)sizeMap.remove(name);
                if (sizeString == null) {
                    name = className;
                    sizeString = (String)sizeMap.remove(name);
                }
                if (sizeString != null) {
                    value = sizeString;
                    int size = Integer.parseInt(sizeString);
                    descriptor.setIdentityMapSize(size);
                }
    
                name = entityName;
                String sharedString = (String)sharedMap.remove(name);
                if (sharedString == null) {
                    name = className;
                    sharedString = (String)sharedMap.remove(name);
                }
                if (sharedString != null) {
                    boolean shared = Boolean.parseBoolean(sharedString);
                    descriptor.setIsIsolated(!shared);
                }
            }
        } catch (NumberFormatException exception) {
            this.session.handleException(ValidationException.invalidValueForProperty(value, PersistenceUnitProperties.CACHE_SIZE_, exception));
        }
    }
    
    /**
     * Process all properties under "eclipselink.connection-pool.".
     * This allows for named connection pools.
     * It also processes "read", "write", "default"  and "sequence" connection pools.
     */
    protected void updateConnectionSettings(ServerSession serverSession, Map properties) {
        Map<String, Object> connectionsMap = PropertiesHandler.getPrefixValuesLogDebug(PersistenceUnitProperties.CONNECTION_POOL, properties, serverSession);
        if (connectionsMap.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : connectionsMap.entrySet()) {
            String poolName = "default";
            String attribute = null;
            try {
                if (entry.getKey().indexOf(".") == -1) {
                    attribute = entry.getKey();
                } else {
                    poolName = entry.getKey().substring(0, entry.getKey().indexOf("."));
                    attribute = entry.getKey().substring(entry.getKey().indexOf(".") + 1, entry.getKey().length());
                }
                ConnectionPool pool = null;
                if (poolName.equals("write")) {
                    poolName = "default";
                }
                if (poolName.equals("read")) {
                    pool = serverSession.getReadConnectionPool();
                    // By default there is no connection pool, so if the default, create a new one.
                    if ((pool == null) || (pool == serverSession.getDefaultConnectionPool())) {
                        if (this.session.getDatasourceLogin().shouldUseExternalConnectionPooling()) {
                            pool = new ExternalConnectionPool(poolName, serverSession.getDatasourceLogin(), serverSession);                            
                        } else {
                            pool = new ConnectionPool(poolName, serverSession.getDatasourceLogin(), serverSession);
                        }
                        serverSession.setReadConnectionPool(pool);
                    }
                } else if (poolName.equals("sequence")) {
                    pool = getDatabaseSession().getSequencingControl().getConnectionPool();
                    if (pool == null) {
                        if (this.session.getDatasourceLogin().shouldUseExternalConnectionPooling()) {
                            pool = new ExternalConnectionPool(poolName, serverSession.getDatasourceLogin(), serverSession);                            
                        } else {
                            pool = new ConnectionPool(poolName, serverSession.getDatasourceLogin(), serverSession);
                        }
                        getDatabaseSession().getSequencingControl().setConnectionPool(pool);
                    }
                } else {
                    pool = serverSession.getConnectionPool(poolName);
                    if (pool == null) {
                        if (this.session.getDatasourceLogin().shouldUseExternalConnectionPooling()) {
                            pool = new ExternalConnectionPool(poolName, serverSession.getDatasourceLogin(), serverSession);                            
                        } else {
                            pool = new ConnectionPool(poolName, serverSession.getDatasourceLogin(), serverSession);
                        }
                        serverSession.addConnectionPool(pool);
                    }
                }
                if (attribute.equals(PersistenceUnitProperties.CONNECTION_POOL_INITIAL)) {
                    pool.setInitialNumberOfConnections(Integer.parseInt((String)entry.getValue()));
                } else if (attribute.equals(PersistenceUnitProperties.CONNECTION_POOL_MIN)) {
                    pool.setMinNumberOfConnections(Integer.parseInt((String)entry.getValue()));
                } else if (attribute.equals(PersistenceUnitProperties.CONNECTION_POOL_MAX)) {
                    pool.setMaxNumberOfConnections(Integer.parseInt((String)entry.getValue()));
                } else if (attribute.equals(PersistenceUnitProperties.CONNECTION_POOL_URL)) {
                    pool.setLogin(pool.getLogin().clone());
                    ((DatabaseLogin)pool.getLogin()).setURL((String)entry.getValue());
                } else if (attribute.equals(PersistenceUnitProperties.CONNECTION_POOL_NON_JTA_DATA_SOURCE)) {
                    pool.setLogin(pool.getLogin().clone());
                    ((DatabaseLogin)pool.getLogin()).useDataSource((String)entry.getValue());
                } else if (attribute.equals(PersistenceUnitProperties.CONNECTION_POOL_JTA_DATA_SOURCE)) {
                    pool.setLogin(pool.getLogin().clone());
                    ((DatabaseLogin)pool.getLogin()).useDataSource((String)entry.getValue());
                } else if (attribute.equals(PersistenceUnitProperties.CONNECTION_POOL_USER)) {
                    pool.setLogin(pool.getLogin().clone());
                    ((DatabaseLogin)pool.getLogin()).setUserName((String)entry.getValue());
                } else if (attribute.equals(PersistenceUnitProperties.CONNECTION_POOL_PASSWORD)) {
                    pool.setLogin(pool.getLogin().clone());
                    ((DatabaseLogin)pool.getLogin()).setPassword((String)entry.getValue());
                } else if (attribute.equals(PersistenceUnitProperties.CONNECTION_POOL_WAIT)) {
                    pool.setWaitTimeout(Integer.parseInt((String)entry.getValue()));
                } else if (attribute.equals(PersistenceUnitProperties.CONNECTION_POOL_FAILOVER)) {
                    String failoverPools = (String)entry.getValue();
                    if ((failoverPools.indexOf(',') != -1) || (failoverPools.indexOf(' ') != -1)) {
                        StringTokenizer tokenizer = new StringTokenizer(failoverPools, " ,");
                        while (tokenizer.hasMoreTokens()) {
                            pool.addFailoverConnectionPool(tokenizer.nextToken());
                        }
                    } else {
                        pool.addFailoverConnectionPool((String)entry.getValue());
                    }
                } else if (poolName.equals("read") && attribute.equals(PersistenceUnitProperties.CONNECTION_POOL_SHARED)) {
                    boolean shared = Boolean.parseBoolean((String)entry.getValue());
                    if (shared) {
                        ReadConnectionPool readPool = new ReadConnectionPool(poolName, serverSession.getDatasourceLogin(), serverSession);
                        readPool.setInitialNumberOfConnections(pool.getInitialNumberOfConnections());
                        readPool.setMinNumberOfConnections(pool.getMinNumberOfConnections());
                        readPool.setMaxNumberOfConnections(pool.getMaxNumberOfConnections());
                        readPool.setWaitTimeout(pool.getWaitTimeout());
                        readPool.setLogin(pool.getLogin());
                        serverSession.setReadConnectionPool(readPool);
                    }
                }
            } catch (RuntimeException exception) {
                this.session.handleException(ValidationException.invalidValueForProperty(entry.getValue(), entry.getKey(), exception));
            }
        }
    }
    
    protected void updateConnectionPolicy(ServerSession serverSession, Map m) {
        String isLazyString = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.EXCLUSIVE_CONNECTION_IS_LAZY, m, session);
        if(isLazyString != null) {
            serverSession.getDefaultConnectionPolicy().setIsLazy(Boolean.parseBoolean(isLazyString));
        }
        ConnectionPolicy.ExclusiveMode exclusiveMode = getConnectionPolicyExclusiveModeFromProperties(m, session, true);
        if(exclusiveMode != null) {
            serverSession.getDefaultConnectionPolicy().setExclusiveMode(exclusiveMode);
        }
    }

    public static ConnectionPolicy.ExclusiveMode getConnectionPolicyExclusiveModeFromProperties(Map m, AbstractSession abstractSession, boolean useSystemAsDefault) {
        String exclusiveConnectionModeString = PropertiesHandler.getPropertyValueLogDebug(PersistenceUnitProperties.EXCLUSIVE_CONNECTION_MODE, m, abstractSession, useSystemAsDefault);
        if(exclusiveConnectionModeString != null) {
            if(exclusiveConnectionModeString == ExclusiveConnectionMode.Isolated) {
                return ConnectionPolicy.ExclusiveMode.Isolated;
            } else if(exclusiveConnectionModeString == ExclusiveConnectionMode.Always) {
                return ConnectionPolicy.ExclusiveMode.Always;
            } else {
                return ConnectionPolicy.ExclusiveMode.Transactional;
            }
        } else {
            return null;
        }
    }

    /**
     * Perform any steps necessary prior to actual deployment.  This includes any steps in the session
     * creation that do not require the real loaded domain classes.
     * 
     * The first call to this method caches persistenceUnitInfo which is reused in the following calls.
     * 
     * Note that in JSE case factoryCount is NOT incremented on the very first call 
     * (by JavaSECMPInitializer.callPredeploy, typically in preMain).
     * That provides 1 to 1 correspondence between factoryCount and the number of open factories.
     * 
     * In case factoryCount > 0 the method just increments factoryCount.
     * factory == 0 triggers creation of a new session.
     * 
     * This method and undeploy - the only methods altering factoryCount - should be synchronized.
     *
     * @return A transformer (which may be null) that should be plugged into the proper
     *         classloader to allow classes to be transformed as they get loaded.
     * @see #deploy(ClassLoader, Map)
     */
    public synchronized ClassTransformer predeploy(PersistenceUnitInfo info, Map extendedProperties) {
        ClassLoader classLoaderToUse = null;
        // session == null
        if (state == STATE_DEPLOY_FAILED || state == STATE_UNDEPLOYED) {
            throw new PersistenceException(EntityManagerSetupException.cannotPredeploy(persistenceUnitInfo.getPersistenceUnitName(), state, persistenceException));
        }
        // session != null
        if (state == STATE_PREDEPLOYED || state == STATE_DEPLOYED || state == STATE_HALF_DEPLOYED) {
            session.log(SessionLog.FINEST, SessionLog.JPA, "predeploy_begin", new Object[]{getPersistenceUnitInfo().getPersistenceUnitName(), session.getName(), state, factoryCount});
            factoryCount++;
            session.log(SessionLog.FINEST, SessionLog.JPA, "predeploy_end", new Object[]{getPersistenceUnitInfo().getPersistenceUnitName(), session.getName(), state, factoryCount});
            return null;
        // session == null
        } else if (state == STATE_INITIAL) {
            persistenceUnitInfo = info;
            if (!isCompositeMember()) {
                if (mustBeCompositeMember(persistenceUnitInfo)) {
                    if (this.staticWeaveInfo == null) {
                        return null;
                    } else {
                        // predeploy is used for static weaving
                        throw new PersistenceException(EntityManagerSetupException.compositeMemberCannotBeUsedStandalone(persistenceUnitInfo.getPersistenceUnitName()));
                    }
                }
            }
        }
        
        // state is INITIAL or PREDEPLOY_FAILED or STATE_HALF_PREDEPLOYED_COMPOSITE_MEMBER, session == null
        try {
            // properties not used in STATE_HALF_PREDEPLOYED_COMPOSITE_MEMBER
            Map predeployProperties = null;
            // composite can't be in STATE_HALF_PREDEPLOYED_COMPOSITE_MEMBER
            boolean isComposite = false;
            if(state != STATE_HALF_PREDEPLOYED_COMPOSITE_MEMBER) {
                //set the claasloader early on and change it if needed
                classLoaderToUse = persistenceUnitInfo.getClassLoader();

                predeployProperties = mergeMaps(extendedProperties, persistenceUnitInfo.getProperties());
                // Translate old properties.
                // This should be done before using properties (i.e. ServerPlatform).
                translateOldProperties(predeployProperties, null);
                
                String sessionsXMLStr = (String)predeployProperties.get(PersistenceUnitProperties.SESSIONS_XML);
                if (sessionsXMLStr != null) {
                    isSessionLoadedFromSessionsXML = true;
                }
                
                // Create session (it needs to be done before initializing ServerPlatform and logging).
                // If a sessions-xml is used this will get replaced later, but is required for logging.
                isComposite = isComposite(persistenceUnitInfo);
                if (isComposite) {
                    if (isSessionLoadedFromSessionsXML) {
                        throw EntityManagerSetupException.compositeIncompatibleWithSessionsXml(persistenceUnitInfo.getPersistenceUnitName());
                    }
                    session = new SessionBroker();
                    ((SessionBroker)session).setShouldUseDescriptorAliases(true);
                } else {
                    session = new ServerSession(new Project(new DatabaseLogin()));

                    //set the listener to process RCM metadata refresh commands 
                    session.setRefreshMetadataListener(this);
                }
                session.setName(this.sessionName);
                updateTunerPreDeploy(predeployProperties, classLoaderToUse);
                updateTolerateInvalidJPQL(predeployProperties);
                
                if (this.compositeEmSetupImpl == null) {
                    // session name and ServerPlatform must be set prior to setting the loggers.
                    if (this.staticWeaveInfo == null) {
                        updateServerPlatform(predeployProperties, classLoaderToUse);
                        // Update loggers and settings for the singleton logger and the session logger.
                        updateLoggers(predeployProperties, true, classLoaderToUse);
                        // log the server platform being used by the session
                        if (session.getSessionLog().shouldLog(SessionLog.FINE)) {
                            session.getSessionLog().log(SessionLog.FINE, SessionLog.SERVER,
                                    "configured_server_platform", session.getServerPlatform().getClass().getName()); // NOI18N
                        }
                        // Get the temporary classLoader based on the platform
                        
                        //Update performance profiler
                        updateProfiler(predeployProperties, classLoaderToUse);
                    } else {
                        // predeploy is used for static weaving
                        Writer writer = this.staticWeaveInfo.getLogWriter(); 
                        if (writer != null) {
                            ((DefaultSessionLog)session.getSessionLog()).setWriter(writer);
                        }
                        session.setLogLevel(this.staticWeaveInfo.getLogLevel());
                    }
                } else {
                    // composite member
                    session.setSessionLog(this.compositeEmSetupImpl.session.getSessionLog());
                    session.setProfiler(this.compositeEmSetupImpl.session.getProfiler());
                }
                
                // Cannot start logging until session and log and initialized, so log start of predeploy here.
                session.log(SessionLog.FINEST, SessionLog.JPA, "predeploy_begin", new Object[]{getPersistenceUnitInfo().getPersistenceUnitName(), session.getName(), state, factoryCount});
    
                //Project Cache accessor processing
                updateProjectCache(predeployProperties, classLoaderToUse);

                if (projectCacheAccessor!=null) {
                    //get the project from the cache
                    Project project = projectCacheAccessor.retrieveProject(predeployProperties, classLoaderToUse, session.getSessionLog());

                    if (project!=null) {
                        try {
                            DatabaseSessionImpl tempSession = (DatabaseSessionImpl)project.createServerSession();

                            tempSession.setName(this.sessionName);
                            tempSession.setSessionLog(session.getSessionLog());
                            tempSession.getSessionLog().setSession(tempSession);
                            if (this.staticWeaveInfo != null) {
                                tempSession.setLogLevel(this.staticWeaveInfo.getLogLevel());
                            }
                            tempSession.setProfiler(session.getProfiler());
                            tempSession.setRefreshMetadataListener(this);

                            session = tempSession;
                            // reusing the serverPlatform from the existing session would have been preferred,
                            //  but its session is only set through the ServerPlatform constructor.
                            updateServerPlatform(predeployProperties, classLoaderToUse);
                            shouldBuildProject = false;
                        } catch (Exception e) {
                            //need a better exception here
                            throw new PersistenceException(e);
                        }
                    }
                }

                if (isSessionLoadedFromSessionsXML) {
                    if (this.compositeEmSetupImpl == null && this.staticWeaveInfo == null) {
                        JPAClassLoaderHolder privateClassLoaderHolder = session.getServerPlatform().getNewTempClassLoader(persistenceUnitInfo);
                        classLoaderToUse = privateClassLoaderHolder.getClassLoader();
                    } else {
                        classLoaderToUse = persistenceUnitInfo.getNewTempClassLoader();
                    }
                    // Loading session from sessions-xml.
                    String tempSessionName = sessionName;
                    if (isCompositeMember()) {
                        // composite member session name is always the same as puName
                        // need the session name specified in properties to read correct session from sessions.xml
                        tempSessionName = (String)predeployProperties.get(PersistenceUnitProperties.SESSION_NAME);
                    }
                    session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "loading_session_xml", sessionsXMLStr, tempSessionName);
                    if (tempSessionName == null) {
                        throw EntityManagerSetupException.sessionNameNeedBeSpecified(persistenceUnitInfo.getPersistenceUnitName(), sessionsXMLStr);
                    }                
                    XMLSessionConfigLoader xmlLoader = new XMLSessionConfigLoader(sessionsXMLStr);
                    // Do not register the session with the SessionManager at this point, create temporary session using a local SessionManager and private class loader.
                    // This allows for the project to be accessed without loading any of the classes to allow weaving.
                    // Note that this method assigns sessionName to session.
                    Session tempSession = new SessionManager().getSession(xmlLoader, tempSessionName, classLoaderToUse, false, false);
                    // Load path of sessions-xml resource before throwing error so user knows which sessions-xml file was found (may be multiple).
                    session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "sessions_xml_path_where_session_load_from", xmlLoader.getSessionName(), xmlLoader.getResourcePath());
                    if (tempSession == null) {
                        throw ValidationException.noSessionFound(sessionName, sessionsXMLStr);
                    }
                    // Currently the session must be either a ServerSession or a SessionBroker, cannot be just a DatabaseSessionImpl. 
                    if (tempSession.isServerSession() || tempSession.isSessionBroker()) {
                       session = (DatabaseSessionImpl) tempSession;
                       if (tempSessionName != sessionName) {
                           // set back the original session name
                           session.setName(sessionName);
                       }
                    } else {
                        throw EntityManagerSetupException.sessionLoadedFromSessionsXMLMustBeServerSession(persistenceUnitInfo.getPersistenceUnitName(), (String)predeployProperties.get(PersistenceUnitProperties.SESSIONS_XML), tempSession);
                    }
                    if (this.staticWeaveInfo == null) {
                        // Must now reset logging and server-platform on the loaded session.
                        // ServerPlatform must be set prior to setting the loggers.
                        updateServerPlatform(predeployProperties, classLoaderToUse);
                        // Update loggers and settings for the singleton logger and the session logger.
                        updateLoggers(predeployProperties, true, classLoaderToUse);
                    }
                } else {
                    classLoaderToUse = persistenceUnitInfo.getClassLoader();
                }
                
                warnOldProperties(predeployProperties, session);
                session.getPlatform().setConversionManager(new JPAConversionManager());
                
                if (this.staticWeaveInfo == null) {
                    if (!isComposite) {
                        PersistenceUnitTransactionType transactionType=null;
                        //bug 5867753: find and override the transaction type
                        String transTypeString = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.TRANSACTION_TYPE, predeployProperties, session);
                        if (transTypeString != null && transTypeString.length() > 0) {
                            transactionType=PersistenceUnitTransactionType.valueOf(transTypeString);
                        } else if (persistenceUnitInfo!=null){
                            transactionType=persistenceUnitInfo.getTransactionType();
                        }
                        
                        if (!isValidationOnly(predeployProperties, false) && persistenceUnitInfo != null && transactionType == PersistenceUnitTransactionType.JTA) {
                            if (predeployProperties.get(PersistenceUnitProperties.JTA_DATASOURCE) == null && persistenceUnitInfo.getJtaDataSource() == null) {
                                if (predeployProperties.get(PersistenceUnitProperties.SCHEMA_DATABASE_PRODUCT_NAME) == null || 
                                        predeployProperties.get(PersistenceUnitProperties.SCHEMA_DATABASE_MAJOR_VERSION) == null || 
                                        predeployProperties.get(PersistenceUnitProperties.SCHEMA_DATABASE_MINOR_VERSION) == null) {
                                    throw EntityManagerSetupException.jtaPersistenceUnitInfoMissingJtaDataSource(persistenceUnitInfo.getPersistenceUnitName());
                                }
                            }
                        }
                    }
                    
                    // this flag is used to disable work done as a result of the LAZY hint on OneToOne and ManyToOne mappings
                    if(state == STATE_INITIAL) {
                        if (compositeEmSetupImpl == null) {
                            if(null == enableWeaving) {                    
                                enableWeaving = Boolean.TRUE;
                            }
                            isWeavingStatic = false;
                            String weaving = getConfigPropertyAsString(PersistenceUnitProperties.WEAVING, predeployProperties);

                            if (weaving != null && weaving.equalsIgnoreCase("false")) {
                                enableWeaving = Boolean.FALSE;
                            }else if (weaving != null && weaving.equalsIgnoreCase("static")) {
                                isWeavingStatic = true;
                            }
                        } else {
                            // composite member
                            // no weaving for composite forces no weaving for members
                            if (!compositeEmSetupImpl.enableWeaving) {
                                enableWeaving = Boolean.FALSE;
                            } else {
                                if(null == enableWeaving) {                    
                                    enableWeaving = Boolean.TRUE;
                                }
                                String weaving = getConfigPropertyAsString(PersistenceUnitProperties.WEAVING, predeployProperties);
                                if (weaving != null && weaving.equalsIgnoreCase("false")) {
                                    enableWeaving = Boolean.FALSE;
                                }
                            }
                            // static weaving is dictated by composite
                            isWeavingStatic = compositeEmSetupImpl.isWeavingStatic;
                        }
                    }
                
                    if (compositeEmSetupImpl == null) {
                        throwExceptionOnFail = "true".equalsIgnoreCase(
                                EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.THROW_EXCEPTIONS, predeployProperties, "true", session));
                    } else {
                        // composite member
                        throwExceptionOnFail = compositeEmSetupImpl.throwExceptionOnFail; 
                    }
                } else {
                    // predeploy is used for static weaving
                    enableWeaving = Boolean.TRUE;
                }
                
                weaveChangeTracking = false;
                weaveLazy = false;
                weaveEager = false;
                weaveFetchGroups = false;
                weaveInternal = false;
                weaveRest = false;
                if (enableWeaving) {
                    weaveChangeTracking = "true".equalsIgnoreCase(EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.WEAVING_CHANGE_TRACKING, predeployProperties, "true", session));
                    weaveLazy = "true".equalsIgnoreCase(EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.WEAVING_LAZY, predeployProperties, "true", session));
                    weaveEager = "true".equalsIgnoreCase(EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.WEAVING_EAGER, predeployProperties, "false", session));
                    weaveFetchGroups = "true".equalsIgnoreCase(EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.WEAVING_FETCHGROUPS, predeployProperties, "true", session));
                    weaveInternal = "true".equalsIgnoreCase(EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.WEAVING_INTERNAL, predeployProperties, "true", session));
                    weaveRest = "true".equalsIgnoreCase(EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.WEAVING_REST, predeployProperties, "true", session));
                }

            }
            if (shouldBuildProject && !isSessionLoadedFromSessionsXML ) {
                if (isComposite) {
                    predeployCompositeMembers(predeployProperties, classLoaderToUse);
                } else {
                    MetadataProcessor compositeProcessor = null;
                    if (compositeEmSetupImpl == null) {
                        mode = PersistenceUnitProcessor.Mode.ALL;
                    } else {
                        // composite member
                        if (state != STATE_HALF_PREDEPLOYED_COMPOSITE_MEMBER) {
                            state = STATE_HALF_PREDEPLOYED_COMPOSITE_MEMBER;
                            mode = PersistenceUnitProcessor.Mode.COMPOSITE_MEMBER_INITIAL;
                        }
                        compositeProcessor = compositeEmSetupImpl.processor;
                    }
                    
                    if (mode == PersistenceUnitProcessor.Mode.ALL || mode == PersistenceUnitProcessor.Mode.COMPOSITE_MEMBER_INITIAL) {
                        boolean usesMultitenantSharedEmf = "true".equalsIgnoreCase(EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.MULTITENANT_SHARED_EMF, predeployProperties, "true", session));
                        boolean usesMultitenantSharedCache = "true".equalsIgnoreCase(EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.MULTITENANT_SHARED_CACHE, predeployProperties, "false", session));
    
                        // Create an instance of MetadataProcessor for specified persistence unit info
                        processor = new MetadataProcessor(persistenceUnitInfo, session, classLoaderToUse, weaveLazy, weaveEager, weaveFetchGroups, usesMultitenantSharedEmf, usesMultitenantSharedCache, predeployProperties, compositeProcessor);
                        
                        //need to use the real classloader to create the repository class
                        updateMetadataRepository(predeployProperties, classLoaderToUse); 
        
                        //bug:299926 - Case insensitive table / column matching with native SQL queries
                        EntityManagerSetupImpl.updateCaseSensitivitySettings(predeployProperties, processor.getProject(), session);
                    }
                    
                    // Process the Object/relational metadata from XML and annotations.
                    PersistenceUnitProcessor.processORMetadata(processor, throwExceptionOnFail, mode);
                    
                    if (mode == PersistenceUnitProcessor.Mode.COMPOSITE_MEMBER_INITIAL) {
                        mode = PersistenceUnitProcessor.Mode.COMPOSITE_MEMBER_MIDDLE;
                        session.log(SessionLog.FINEST, SessionLog.JPA, "predeploy_end", new Object[]{getPersistenceUnitInfo().getPersistenceUnitName(), session.getName(), state + " " + mode , factoryCount});
                        return null;
                    } else if (mode == PersistenceUnitProcessor.Mode.COMPOSITE_MEMBER_MIDDLE) {
                        mode = PersistenceUnitProcessor.Mode.COMPOSITE_MEMBER_FINAL;
                        session.log(SessionLog.FINEST, SessionLog.JPA, "predeploy_end", new Object[]{getPersistenceUnitInfo().getPersistenceUnitName(), session.getName(), state + " " + mode , factoryCount});
                        return null;
                    }
                    // mode == PersistenceUnitProcessor.Mode.ALL || mode == PersistenceUnitProcessor.Mode.COMPOSITE_MEMBER_FINAL
                    // clear mode and proceed
                    mode = null;
    
                    if (session.getIntegrityChecker().hasErrors()){
                        session.handleException(new IntegrityException(session.getIntegrityChecker()));
                    }
    
                    // The transformer is capable of altering domain classes to handle a LAZY hint for OneToOne mappings.  It will only
                    // be returned if we we are mean to process these mappings
                    if (enableWeaving) {                
                        // build a list of entities the persistence unit represented by this EntityManagerSetupImpl will use
                        Collection entities = PersistenceUnitProcessor.buildEntityList(processor, classLoaderToUse);
                        this.weaver = TransformerFactory.createTransformerAndModifyProject(session, entities, classLoaderToUse, weaveLazy, weaveChangeTracking, weaveFetchGroups, weaveInternal, weaveRest);
                        session.getProject().setClassNamesForWeaving(new ArrayList(processor.getProject().getWeavableClassNames()));
                    }

                    //moved from deployment:
                    processor.addNamedQueries();
                    processor.addStructConverterNames();
                }
            } else {
                //This means this session is from sessions.xml or a cached project

                // The transformer is capable of altering domain classes to handle a LAZY hint for OneToOne mappings.  It will only
                // be returned if we we are meant to process these mappings.
                if (enableWeaving) {
                    Collection persistenceClasses = new ArrayList();
                    MetadataAsmFactory factory = new MetadataAsmFactory(new MetadataLogger(session), classLoaderToUse);
                    if (shouldBuildProject) {
                        // If deploying from a sessions-xml it is still desirable to allow the classes to be weaved.
                        // build a list of entities the persistence unit represented by this EntityManagerSetupImpl will use
                        for (Iterator iterator = session.getProject().getDescriptors().keySet().iterator(); iterator.hasNext(); ) {
                            persistenceClasses.add(factory.getMetadataClass(((Class)iterator.next()).getName()));
                        }
                    } else {
                        // build a list of entities the persistence unit represented by this EntityManagerSetupImpl will use
                        for (String className : session.getProject().getClassNamesForWeaving()) {
                            persistenceClasses.add(factory.getMetadataClass(className));
                        }
                    }
                    this.weaver = TransformerFactory.createTransformerAndModifyProject(session, persistenceClasses, classLoaderToUse, weaveLazy, weaveChangeTracking, weaveFetchGroups, weaveInternal, weaveRest);
                }
            }

            // composite member never has a factory - it is predeployed by the composite.
            if (!isCompositeMember()) {
                // factoryCount is not incremented only in case of a first call to preDeploy
                // in non-container mode: this call is not associated with a factory
                // but rather done by JavaSECMPInitializer.callPredeploy (typically in preMain).
                if(state != STATE_INITIAL || this.isInContainerMode()) {
                    factoryCount++;
                }
                preInitializeMetamodel();
            }

            state = STATE_PREDEPLOYED;
            session.log(SessionLog.FINEST, SessionLog.JPA, "predeploy_end", new Object[]{getPersistenceUnitInfo().getPersistenceUnitName(), session.getName(), state, factoryCount});
            //gf3146: if static weaving is used, we should not return a transformer.  Transformer should still be created though as it modifies descriptors 
            if (isWeavingStatic) {
                return null;
            } else {
                return this.weaver;
            }
        } catch (Throwable ex) {
            state = STATE_PREDEPLOY_FAILED;
            // cache this.persistenceException before slow logging
            PersistenceException persistenceEx = createPredeployFailedPersistenceException(ex);
            // If session exists, use it for logging
            if (session != null) {
                session.log(SessionLog.FINEST, SessionLog.JPA, "predeploy_end", new Object[]{getPersistenceUnitInfo().getPersistenceUnitName(), session.getName(), state, factoryCount});
            // If at least staticWeaveInfo exists, use it for logging
            } else if (staticWeaveInfo != null && staticWeaveInfo.getLogLevel() <= SessionLog.FINEST) {
                Writer logWriter = staticWeaveInfo.getLogWriter();
                if (logWriter != null) {
                    String message = LoggingLocalization.buildMessage("predeploy_end", new Object[]{getPersistenceUnitInfo().getPersistenceUnitName(), "N/A", state, factoryCount});
                    try {
                        logWriter.write(message);
                        logWriter.write(Helper.cr());
                    } catch (IOException ioex) {
                        // Ignore IOException
                    }
                }
            }
            session = null;
            mode = null;
            throw persistenceEx;
        }
    }
    
    protected PersistenceException createPredeployFailedPersistenceException(Throwable ex) {
        PersistenceException perEx = new PersistenceException(EntityManagerSetupException.predeployFailed(persistenceUnitInfo.getPersistenceUnitName(), ex));
        if (persistenceException == null) {
            persistenceException = perEx;
        }
        return perEx;
    }

    /**
     * Return the name of the session this SetupImpl is building. The session name is only known at deploy
     * time and if this method is called prior to that, this method will return null.
     */
    public String getDeployedSessionName(){
        return session != null ? session.getName() : null;
    }
    
    public PersistenceUnitInfo getPersistenceUnitInfo(){
        return persistenceUnitInfo;
    }
    
    public boolean isValidationOnly(Map m) {
        return isValidationOnly(m, true);
    }
    
    protected boolean isValidationOnly(Map m, boolean shouldMergeMap) {
        if (shouldMergeMap) {
            m = mergeWithExistingMap(m);
        }
        String validationOnlyString = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.VALIDATION_ONLY_PROPERTY, m, session);
        if (validationOnlyString != null) {
            return Boolean.parseBoolean(validationOnlyString);
        } else {
            return false;
        }
    }
    
    /**
     * Return if the session should be deployed and connected during the creation of the EntityManagerFactory,
     * or if it should be deferred until createEntityManager().
     * The default is to defer, but is only validating, or can be configured to deploy upfront to avoid hanging the
     * application at runtime.
     */
    public boolean shouldGetSessionOnCreateFactory(Map m) {
        m = mergeWithExistingMap(m);
        if (isValidationOnly(m, false)) {
            return true;
        }

        String deployString = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.DEPLOY_ON_STARTUP, m, this.session);
        if (deployString != null) {
            return Boolean.parseBoolean(deployString);
        } else {
            // If DDL schame generation is turned on, we need to deploy.
            return hasSchemaDatabaseGeneration(m) || hasSchemaScriptsGeneration(m);
        }
    }
    
    protected Map mergeWithExistingMap(Map m) {
        if(persistenceUnitInfo != null) {
            return mergeMaps(m, persistenceUnitInfo.getProperties());
        } else {
            return m;
        }
    }

    public boolean isInContainerMode(){
        return isInContainerMode;
    }

    /**
     * Configure cache coordination using properties.
     */
    protected void updateCacheCoordination(Map m, ClassLoader loader) {
        String protocol = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_PROTOCOL, m, this.session);
        String value = "";
        String property = "";
        try {
            if (protocol != null) {
                RemoteCommandManager rcm = new RemoteCommandManager(this.session);        
                if (protocol.equalsIgnoreCase(CacheCoordinationProtocol.JGROUPS)) {
                    property = PersistenceUnitProperties.COORDINATION_PROTOCOL;                    
                    value = "org.eclipse.persistence.sessions.coordination.jgroups.JGroupsTransportManager";
                    // Avoid compile and runtime dependency.
                    Class transportClass = findClassForProperty(value, PersistenceUnitProperties.COORDINATION_PROTOCOL, loader);
                    TransportManager transport = (TransportManager)transportClass.newInstance();
                    rcm.setTransportManager(transport);                    
                    String config = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_JGROUPS_CONFIG, m, this.session);
                    if (config != null) {
                        transport.setConfig(config);
                    }                    
                } else if (protocol.equalsIgnoreCase(CacheCoordinationProtocol.JMS) || protocol.equalsIgnoreCase(CacheCoordinationProtocol.JMSPublishing)) {
                    JMSPublishingTransportManager transport = null;
                    if (protocol.equalsIgnoreCase(CacheCoordinationProtocol.JMS)) {
                         transport = new JMSTopicTransportManager(rcm);
                    } else {
                        transport = new JMSPublishingTransportManager(rcm);
                    }
                    rcm.setTransportManager(transport);
                    
                    String host = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_JMS_HOST, m, this.session);
                    if (host != null) {
                        transport.setTopicHostUrl(host);
                    }
                    String topic = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_JMS_TOPIC, m, this.session);
                    if (topic != null) {
                        transport.setTopicName(topic);
                    }
                    String factory = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_JMS_FACTORY, m, this.session);
                    if (factory != null) {
                        transport.setTopicConnectionFactoryName(factory);
                    }
                    
                    String reuse_publisher = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_JMS_REUSE_PUBLISHER, m, this.session);
                    if (reuse_publisher != null) {
                        transport.setShouldReuseJMSTopicPublisher(reuse_publisher.equalsIgnoreCase("true"));
                    }
                    
                } else if (protocol.equalsIgnoreCase(CacheCoordinationProtocol.RMI) || protocol.equalsIgnoreCase(CacheCoordinationProtocol.RMIIIOP)) {
                    if (protocol.equalsIgnoreCase(CacheCoordinationProtocol.RMIIIOP)) {
                        ((RMITransportManager)rcm.getTransportManager()).setIsRMIOverIIOP(true);
                    }
                    // Default protocol.
                    String delay = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_RMI_ANNOUNCEMENT_DELAY, m, this.session);
                    property = PersistenceUnitProperties.COORDINATION_RMI_ANNOUNCEMENT_DELAY;
                    value = delay;
                    if (delay != null) {
                        rcm.getDiscoveryManager().setAnnouncementDelay(Integer.parseInt(delay));
                    }
                    String multicast = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_RMI_MULTICAST_GROUP, m, this.session);
                    if (multicast != null) {
                        rcm.getDiscoveryManager().setMulticastGroupAddress(multicast);
                    }
                    String port = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_RMI_MULTICAST_GROUP_PORT, m, this.session);
                    property = PersistenceUnitProperties.COORDINATION_RMI_MULTICAST_GROUP_PORT;
                    value = port;
                    if (port != null) {
                        rcm.getDiscoveryManager().setMulticastPort(Integer.parseInt(port));
                    }
                    String timeToLive = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_RMI_PACKET_TIME_TO_LIVE, m, this.session);
                    property = PersistenceUnitProperties.COORDINATION_RMI_PACKET_TIME_TO_LIVE;
                    value = timeToLive;
                    if (timeToLive != null) {
                        rcm.getDiscoveryManager().setPacketTimeToLive(Integer.parseInt(timeToLive));
                    }
                    String url = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_RMI_URL, m, this.session);
                    if (url != null) {
                        rcm.setUrl(url);
                    }
                } else {
                    property = PersistenceUnitProperties.COORDINATION_PROTOCOL;
                    value = protocol;
                    Class transportClass = findClassForProperty(protocol, PersistenceUnitProperties.COORDINATION_PROTOCOL, loader);
                    rcm.setTransportManager((TransportManager)transportClass.newInstance());
                }
                String serializer = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_SERIALIZER, m, this.session);
                if (serializer != null) {
                    property = PersistenceUnitProperties.COORDINATION_SERIALIZER;
                    value = serializer;
                    Class transportClass = findClassForProperty(serializer, PersistenceUnitProperties.COORDINATION_SERIALIZER, loader);
                    rcm.setSerializer((Serializer)transportClass.newInstance());
                }
                
                String naming = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_NAMING_SERVICE, m, this.session);
                if (naming != null) {
                    if (naming.equalsIgnoreCase("jndi")) {
                        rcm.getTransportManager().setNamingServiceType(TransportManager.JNDI_NAMING_SERVICE);
                    } else if (naming.equalsIgnoreCase("rmi")) {
                        rcm.getTransportManager().setNamingServiceType(TransportManager.REGISTRY_NAMING_SERVICE);
                    }
                }
                String user = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_JNDI_USER, m, this.session);
                if (user != null) {
                    rcm.getTransportManager().setUserName(user);
                }
                String password = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_JNDI_PASSWORD, m, this.session);
                if (password != null) {
                    rcm.getTransportManager().setPassword(password);
                }
                String context = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_JNDI_CONTEXT, m, this.session);
                if (context != null) {
                    rcm.getTransportManager().setInitialContextFactoryName(context);
                }
                String removeOnError = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_REMOVE_CONNECTION, m, this.session);
                if (removeOnError != null) {
                    rcm.getTransportManager().setShouldRemoveConnectionOnError(removeOnError.equalsIgnoreCase("true"));
                }
                String asynch = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_ASYNCH, m, this.session);
                if (asynch != null) {
                    rcm.setShouldPropagateAsynchronously(asynch.equalsIgnoreCase("true"));
                }
                String threadPoolSize = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_THREAD_POOL_SIZE, m, this.session);
                property = PersistenceUnitProperties.COORDINATION_THREAD_POOL_SIZE;
                value = threadPoolSize;
                if (threadPoolSize != null) {
                    this.session.getServerPlatform().setThreadPoolSize(Integer.parseInt(threadPoolSize));
                }
                String channel = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.COORDINATION_CHANNEL, m, this.session);
                if (channel != null) {
                    rcm.setChannel(channel);
                }
                this.session.setCommandManager(rcm);
                this.session.setShouldPropagateChanges(true);
            }
        } catch (Exception exception) {
            this.session.handleException(ValidationException.invalidValueForProperty(value, property, exception));
        }
    }
        
    /**
     * Update session serializer.
     */
    protected void updateSerializer(Map m, ClassLoader loader) {
	    String serializer = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.SERIALIZER, m, this.session);
	    if (serializer != null) {
	    	if (serializer.length() > 0) {
		        try {
		        	Class transportClass = findClassForProperty(serializer, PersistenceUnitProperties.SERIALIZER, loader);
		        	this.session.setSerializer((Serializer)transportClass.newInstance());
		        } catch (Exception exception) {
		            this.session.handleException(ValidationException.invalidValueForProperty(serializer, PersistenceUnitProperties.SERIALIZER, exception));
		        }
	    	} else {
	    		this.session.setSerializer(JavaSerializer.instance);
	    	}
	    }
    }

    /**
     * Update whether session ShouldOptimizeResultSetAccess.
     */
    protected void updateShouldOptimizeResultSetAccess(Map m) {
       String resultSetAccessOptimization = PropertiesHandler.getPropertyValueLogDebug(PersistenceUnitProperties.JDBC_RESULT_SET_ACCESS_OPTIMIZATION, m, this.session);
       if (resultSetAccessOptimization != null) {
          this.session.setShouldOptimizeResultSetAccess(resultSetAccessOptimization.equals("true"));
       }
    }

    /**
     * Update whether session should use externally defined multi tenancy.
     */
    protected void updateTenancy(Map m, ClassLoader loader) {
        String tenantStrategy = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.MULTITENANT_STRATEGY, m, this.session);
        if(tenantStrategy != null) {
            if ("external".equalsIgnoreCase(tenantStrategy)) {
                SchemaPerMultitenantPolicy policy = new SchemaPerMultitenantPolicy();
                String prop = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.MULTITENANT_SHARED_EMF, m, session);
                if (prop != null) {
                    policy.setShouldUseSharedEMF(Boolean.valueOf(prop));
                }
                prop = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.MULTITENANT_SHARED_CACHE, m, session);
                if (prop != null) {
                    policy.setShouldUseSharedCache(Boolean.valueOf(prop));
                }
                session.getProject().setMultitenantPolicy(policy);
            } else {
                //assume it is a class with default constructor implementing existing interface
                Class cls = findClassForProperty(tenantStrategy, PersistenceUnitProperties.MULTITENANT_STRATEGY, loader);
                MultitenantPolicy policy = null;
                try {
                    Constructor constructor = cls.getConstructor();
                    policy = (MultitenantPolicy) constructor.newInstance();
                } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                    throw EntityManagerSetupException.failedToInstantiateProperty(tenantStrategy, PersistenceUnitProperties.MULTITENANT_STRATEGY, ex);
                }
                session.getProject().setMultitenantPolicy(policy);
            }
        }
    }

    /**
     * Update whether session should tolerate invalid JPQL at creation time.
     */
    protected void updateTolerateInvalidJPQL(Map m) {
        String config =
            PropertiesHandler.getPropertyValueLogDebug(PersistenceUnitProperties.JPQL_TOLERATE, m, this.session);
        // Tolerate invalid JPQL is ignored if running in validation only mode
        if (config != null && isValidationOnly(m) == false) {
            this.session.setTolerateInvalidJPQL(config.equals("true"));
        }
    }
    
    /**
     * Override the default login creation method.
     * If persistenceInfo is available, use the information from it to setup the login
     * and possibly to set readConnectionPool.
     */
    protected void updateLogins(Map m){
        DatasourceLogin login = (DatasourceLogin)this.session.getDatasourceLogin();

        String eclipselinkPlatform = PropertiesHandler.getPropertyValueLogDebug(PersistenceUnitProperties.TARGET_DATABASE, m, this.session);
        if (eclipselinkPlatform != null) {
            login.setPlatformClassName(eclipselinkPlatform, this.persistenceUnitInfo.getClassLoader());
        }        
        // Check for EIS platform, need to use an EIS login.
        boolean isEIS = false;
        if (login.getDatasourcePlatform() instanceof EISPlatform) {
            isEIS = true;
            EISLogin newLogin = new EISLogin();
            newLogin.setDatasourcePlatform(login.getDatasourcePlatform());
            this.session.setDatasourceLogin(newLogin);
            if (this.session.isServerSession()) {
               for (ConnectionPool pool : ((ServerSession)this.session).getConnectionPools().values()) {
                   pool.setLogin(newLogin);
               }
            }
            login = newLogin;
        }
        
        // Check for EIS or custom (JDBC) Connector class.
        Object connectorValue = getConfigPropertyLogDebug(PersistenceUnitProperties.NOSQL_CONNECTION_SPEC, m, this.session);
        String connectorProperty = PersistenceUnitProperties.NOSQL_CONNECTION_SPEC;
        if (connectorValue == null) {
            connectorValue = getConfigPropertyLogDebug(PersistenceUnitProperties.JDBC_CONNECTOR, m, this.session);
            connectorProperty = PersistenceUnitProperties.JDBC_CONNECTOR;
        }
        if (connectorValue instanceof Connector) {
            login.setConnector((Connector)connectorValue);
        } else if (connectorValue instanceof String) {
            Class cls = null;
            // Try both class loaders.
            try {
                cls = findClassForProperty((String)connectorValue, connectorProperty, this.persistenceUnitInfo.getClassLoader());
            } catch (Throwable failed) {
                cls = findClassForProperty((String)connectorValue, connectorProperty, getClass().getClassLoader());                
            }
            Connector connector = null;
            try {
                Constructor constructor = cls.getConstructor();
                connector = (Connector)constructor.newInstance();
            } catch (Exception exception) {
                throw EntityManagerSetupException.failedToInstantiateProperty((String)connectorValue, connectorProperty, exception);
            }
            if (connector != null) {
                login.setConnector(connector);
            }
        } else if (connectorValue != null) {
            // Assume JCA connection spec.
            ((EISConnectionSpec)login.getConnector()).setConnectionSpecObject(connectorValue);
        }
        
        // Check for EIS ConnectionFactory.
        Object factoryValue = getConfigPropertyLogDebug(PersistenceUnitProperties.NOSQL_CONNECTION_FACTORY, m, this.session);
        if (factoryValue instanceof String) {
            // JNDI name.
            ((EISConnectionSpec)login.getConnector()).setName((String)factoryValue);
        } else if (factoryValue != null) {
            ((EISConnectionSpec)login.getConnector()).setConnectionFactoryObject(factoryValue);
        }
        
        // Process EIS or JDBC connection properties.
        Map propertiesMap = PropertiesHandler.getPrefixValuesLogDebug(PersistenceUnitProperties.NOSQL_PROPERTY, m, session);
        if (propertiesMap.isEmpty()) {
            propertiesMap = PropertiesHandler.getPrefixValuesLogDebug(PersistenceUnitProperties.JDBC_PROPERTY, m, session);
        }
        for (Iterator iterator = propertiesMap.entrySet().iterator(); iterator.hasNext(); ) {
            Map.Entry entry = (Map.Entry)iterator.next();
            String property = (String)entry.getKey();
            Object value = entry.getValue();
            login.setProperty(property, value);
        }        
        
        // Note: This call does not checked the stored persistenceUnitInfo or extended properties because
        // the map passed into this method should represent the full set of properties we expect to process

        String user = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_USER, m, this.session);
        String password = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_PASSWORD, m, this.session);
        if(user != null) {
            login.setUserName(user);
        }
        if (password != null) {
            login.setPassword(this.securableObjectHolder.getSecurableObject().decryptPassword(password));
        }
        
        PersistenceUnitTransactionType transactionType = this.persistenceUnitInfo.getTransactionType();
        //bug 5867753: find and override the transaction type using properties
        String transTypeString = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.TRANSACTION_TYPE, m, this.session);
        if (transTypeString != null && transTypeString.length() > 0) {
            transactionType = PersistenceUnitTransactionType.valueOf(transTypeString);
        }
        //find the jta datasource
        javax.sql.DataSource jtaDatasource = getDatasourceFromProperties(m, PersistenceUnitProperties.JTA_DATASOURCE, this.persistenceUnitInfo.getJtaDataSource());

        //find the non jta datasource  
        javax.sql.DataSource nonjtaDatasource = getDatasourceFromProperties(m, PersistenceUnitProperties.NON_JTA_DATASOURCE, this.persistenceUnitInfo.getNonJtaDataSource());

        if (isValidationOnly(m, false) && transactionType == PersistenceUnitTransactionType.JTA && jtaDatasource == null) {
            updateLoginDefaultConnector(login, m);
            return;
        }
        
        login.setUsesExternalTransactionController(transactionType == PersistenceUnitTransactionType.JTA);

        // Avoid processing data-source if EIS, as container may pass in a default one.
        if (isEIS) {
            return;
        }
        
        javax.sql.DataSource mainDatasource = null;
        javax.sql.DataSource readDatasource = null;
        if (login.shouldUseExternalTransactionController()) {
            // JtaDataSource is guaranteed to be non null - otherwise exception would've been thrown earlier
            mainDatasource = jtaDatasource;
            // only define readDatasource if there is jta mainDatasource
            readDatasource = nonjtaDatasource;
        } else {
            // JtaDataSource will be ignored because transactionType is RESOURCE_LOCAL
            if (jtaDatasource != null) {
                session.log(SessionLog.WARNING, SessionLog.TRANSACTION, "resource_local_persistence_init_info_ignores_jta_data_source", this.persistenceUnitInfo.getPersistenceUnitName());
            }
            if (nonjtaDatasource != null) {
                mainDatasource = nonjtaDatasource;
            } else {
                updateLoginDefaultConnector(login, m);
                return;
            }
        }

        // mainDatasource is guaranteed to be non null - TODO: No it is not, if they did not set one it is null, should raise error, not null-pointer.
        if (!(login.getConnector() instanceof JNDIConnector)) {
            JNDIConnector jndiConnector;
            if (mainDatasource instanceof DataSourceImpl) {
                //Bug5209363  Pass in the datasource name instead of the dummy datasource
                jndiConnector = new JNDIConnector(((DataSourceImpl)mainDatasource).getName());                
            } else {
                jndiConnector = new JNDIConnector(mainDatasource);                                
            }
            login.setConnector(jndiConnector);
            String useInternalConnectionPool = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.CONNECTION_POOL_INTERNALLY_POOL_DATASOURCE, m, this.session);
            if (!"true".equalsIgnoreCase(useInternalConnectionPool)){
                login.setUsesExternalConnectionPooling(true);
            }
        }

        if (this.session.isServerSession()) {
            // set readLogin
            if (readDatasource != null) {
                DatasourceLogin readLogin = login.clone();
                readLogin.dontUseExternalTransactionController();
                JNDIConnector jndiConnector;
                if (readDatasource instanceof DataSourceImpl) {
                    //Bug5209363  Pass in the datasource name instead of the dummy datasource
                    jndiConnector = new JNDIConnector(((DataSourceImpl)readDatasource).getName());
                } else {
                    jndiConnector = new JNDIConnector(readDatasource);                    
                }
                readLogin.setConnector(jndiConnector);
                ((ServerSession)this.session).setReadConnectionPool(readLogin);
            }
        }
        
    }
    
    /**
     * This is used to return either the defaultDatasource or, if one exists, a datasource 
     * defined under the property from the Map m.  This method will build a DataSourceImpl
     * object to hold the url if the property in Map m defines a string instead of a datasource.
     */
    protected javax.sql.DataSource getDatasourceFromProperties(Map m, String property, javax.sql.DataSource defaultDataSource){
        Object datasource = getConfigPropertyLogDebug(property, m, session);
        if ( datasource == null ){
            return defaultDataSource;
        } 
        if ( datasource instanceof String){
            if(((String)datasource).length() > 0) {
                // Create a dummy DataSource that will throw an exception on access
                return new DataSourceImpl((String)datasource, null, null, null);
            } else {
                // allow an empty string data source property passed to createEMF to cancel data source specified in persistence.xml
                return null;
            }
        }
        if ( !(datasource instanceof javax.sql.DataSource) ){
            //A warning should be enough.  Though an error might be better, the properties passed in could contain anything
            session.log(SessionLog.WARNING, SessionLog.PROPERTIES, "invalid_datasource_property_value", property, datasource);
            return defaultDataSource;
        }
        return (javax.sql.DataSource)datasource;
    }
    
    /**
     * In cases where there is no data source, we will use properties to configure the login for
     * our session.  This method gets those properties and sets them on the login.
     */
    protected void updateLoginDefaultConnector(DatasourceLogin login, Map m){
        //Login info might be already set with sessions.xml and could be overridden by session customizer after this
        //If login has default connector then JDBC properties update(override) the login info
        if ((login.getConnector() instanceof DefaultConnector)) {
            DatabaseLogin dbLogin = (DatabaseLogin)login;
            // Note: This call does not checked the stored persistenceUnitInfo or extended properties because
            // the map passed into this method should represent the full set of properties we expect to process
            String jdbcDriver = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_DRIVER, m, session);
            String connectionString = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_URL, m, session);
            if(connectionString != null) {
                dbLogin.setConnectionString(connectionString);
            }
            if(jdbcDriver != null) {
                dbLogin.setDriverClassName(jdbcDriver);
            }
        }
    }

    /**
     * Configure the internal connection pooling parameters.
     * By default if nothing is configured a default shared (exclusive) read/write pool is used with 32 min/max connections and 1 initial.
     */
    @SuppressWarnings("deprecation")
    protected void updatePools(ServerSession serverSession, Map m) {
        String value = null;
        String property = null;
        try {
            // Configure default/write connection pool.
            // Sizes are irrelevant for external connection pool
            if (!serverSession.getDefaultConnectionPool().getLogin().shouldUseExternalConnectionPooling()) {
                // CONNECTION and WRITE_CONNECTION properties both configure the default pool (mean the same thing, but WRITE normally used with READ).
                property = PersistenceUnitProperties.JDBC_CONNECTIONS_MIN;
                value = getConfigPropertyAsStringLogDebug(property, m, serverSession);
                if (value != null) {
                    serverSession.getDefaultConnectionPool().setMinNumberOfConnections(Integer.parseInt(value));
                }
                property = PersistenceUnitProperties.JDBC_CONNECTIONS_MAX;
                value = getConfigPropertyAsStringLogDebug(property, m, serverSession);
                if (value != null) {
                    serverSession.getDefaultConnectionPool().setMaxNumberOfConnections(Integer.parseInt(value));
                }
                property = PersistenceUnitProperties.JDBC_CONNECTIONS_INITIAL;
                value = getConfigPropertyAsStringLogDebug(property, m, serverSession);
                if (value != null) {
                    serverSession.getDefaultConnectionPool().setInitialNumberOfConnections(Integer.parseInt(value));
                }
                property = PersistenceUnitProperties.JDBC_WRITE_CONNECTIONS_MIN;
                value = getConfigPropertyAsStringLogDebug(property, m, serverSession);
                if (value != null) {
                    serverSession.getDefaultConnectionPool().setMinNumberOfConnections(Integer.parseInt(value));
                }
                property = PersistenceUnitProperties.JDBC_WRITE_CONNECTIONS_MAX;
                value = getConfigPropertyAsStringLogDebug(property, m, serverSession);
                if (value != null) {
                    serverSession.getDefaultConnectionPool().setMaxNumberOfConnections(Integer.parseInt(value));
                }
                property = PersistenceUnitProperties.JDBC_WRITE_CONNECTIONS_INITIAL;
                value = getConfigPropertyAsStringLogDebug(property, m, serverSession);
                if (value != null) {
                    serverSession.getDefaultConnectionPool().setInitialNumberOfConnections(Integer.parseInt(value));
                }
            }
            
            // Configure read connection pool if set.
            // Sizes and shared option are irrelevant for external connection pool
            if (!serverSession.getReadConnectionPool().getLogin().shouldUseExternalConnectionPooling()) {
                String shared = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_READ_CONNECTIONS_SHARED, m, serverSession);
                boolean isShared = false;
                if (shared != null) {
                    isShared = Boolean.parseBoolean(shared);
                }            
                ConnectionPool pool = null;
                if (isShared) {
                    pool = new ReadConnectionPool("read", serverSession.getReadConnectionPool().getLogin(), serverSession);
                } else {
                    pool = new ConnectionPool("read", serverSession.getReadConnectionPool().getLogin(), serverSession);
                }
                String min = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_READ_CONNECTIONS_MIN, m, serverSession);
                if (min != null) {
                    value = min;
                    property = PersistenceUnitProperties.JDBC_READ_CONNECTIONS_MIN;
                    pool.setMinNumberOfConnections(Integer.parseInt(min));
                }
                String max = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_READ_CONNECTIONS_MAX, m, serverSession);
                if (max != null) {
                    value = max;
                    property = PersistenceUnitProperties.JDBC_READ_CONNECTIONS_MAX;
                    pool.setMaxNumberOfConnections(Integer.parseInt(max));
                }
                String initial = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_READ_CONNECTIONS_INITIAL, m, serverSession);
                if (initial != null) {
                    value = initial;
                    property = PersistenceUnitProperties.JDBC_READ_CONNECTIONS_INITIAL;
                    pool.setInitialNumberOfConnections(Integer.parseInt(initial));
                }
                // Only set the read pool if they configured it, otherwise use default shared read/write.
                if (isShared || (min != null) || (max != null) || (initial != null)) {
                    serverSession.setReadConnectionPool(pool);
                }
                String wait = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_CONNECTIONS_WAIT, m, serverSession);
                if (wait != null) {
                    value = wait;
                    property = PersistenceUnitProperties.JDBC_CONNECTIONS_WAIT;
                    serverSession.getDefaultConnectionPool().setWaitTimeout(Integer.parseInt(wait));
                    pool.setWaitTimeout(Integer.parseInt(wait));
                }
            }
            
            // Configure sequence connection pool if set.
            String sequence = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_SEQUENCE_CONNECTION_POOL, m, serverSession);
            if (sequence != null) {
                serverSession.getSequencingControl().setShouldUseSeparateConnection(Boolean.parseBoolean(sequence));
            }
            String sequenceDataSource = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_SEQUENCE_CONNECTION_POOL_DATASOURCE, m, serverSession);
            if (sequenceDataSource != null) {
                DatasourceLogin login = this.session.getLogin().clone();
                login.dontUseExternalTransactionController();
                JNDIConnector jndiConnector = new JNDIConnector(sequenceDataSource);
                login.setConnector(jndiConnector);
                serverSession.getSequencingControl().setLogin(login);
            }        
            // Sizes and shared option are irrelevant for external connection pool
            if (!serverSession.getReadConnectionPool().getLogin().shouldUseExternalConnectionPooling()) {
                value = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_SEQUENCE_CONNECTION_POOL_MIN, m, serverSession);
                if (value != null) {
                    property = PersistenceUnitProperties.JDBC_SEQUENCE_CONNECTION_POOL_MIN;
                    serverSession.getSequencingControl().setMinPoolSize(Integer.parseInt(value));
                }
                value = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_SEQUENCE_CONNECTION_POOL_MAX, m, serverSession);
                if (value != null) {
                    property = PersistenceUnitProperties.JDBC_SEQUENCE_CONNECTION_POOL_MAX;
                    serverSession.getSequencingControl().setMaxPoolSize(Integer.parseInt(value));
                }
                value = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_SEQUENCE_CONNECTION_POOL_INITIAL, m, serverSession);
                if (value != null) {
                    property = PersistenceUnitProperties.JDBC_SEQUENCE_CONNECTION_POOL_INITIAL;
                    serverSession.getSequencingControl().setInitialPoolSize(Integer.parseInt(value));
                }
            }
        } catch (NumberFormatException exception) {
            serverSession.handleException(ValidationException.invalidValueForProperty(value, property, exception));
        }
    }
    
    /**
     * Normally when a property is missing nothing should be applied to the session.
     * However there are several session attributes that defaulted in EJB3 to the values
     * different from EclipseLink defaults.
     * This function applies defaults for such properties and registers the session.
     * All other session-related properties are applied in updateServerSession.
     * Note that updateSession may be called several times on the same session
     * (before login), but initSession is called just once - before the first call
     * to updateSession.
     */
    protected void initSession() {
        assignCMP3Policy();

        if(!isCompositeMember()) {
            // Register session that has been created earlier.
            addSessionToGlobalSessionManager();
        }
    }

    /**
     * Make any changes to our ServerSession that can be made after it is created.
     */
    protected void updateSession(Map m, ClassLoader loader) {
        if (session == null || (session.isDatabaseSession() && ((DatabaseSessionImpl)session).isLoggedIn())) {
            return;
        }

        // In deploy ServerPlatform could've changed which will affect the loggers.
        boolean serverPlatformChanged = updateServerPlatform(m, loader);
        updateJPQLParser(m);

        if (!session.hasBroker()) {
            updateLoggers(m, serverPlatformChanged, loader);        
            updateProfiler(m,loader);
        }

        // log the server platform being used by the session if it has been changed
        if (serverPlatformChanged && session.getSessionLog().shouldLog(SessionLog.FINE)) {
            session.getSessionLog().log(SessionLog.FINE, SessionLog.SERVER,
                    "configured_server_platform", session.getServerPlatform().getClass().getName()); // NOI18N
        }

        if(session.isBroker()) {
            PersistenceUnitTransactionType transactionType = persistenceUnitInfo.getTransactionType();
            //bug 5867753: find and override the transaction type using properties
            String transTypeString = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.TRANSACTION_TYPE, m, session);
            if (transTypeString != null) {
                transactionType = PersistenceUnitTransactionType.valueOf(transTypeString);
            }
            ((DatasourceLogin)session.getDatasourceLogin()).setUsesExternalTransactionController(transactionType == PersistenceUnitTransactionType.JTA);
        } else {
            String shouldBindString = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JDBC_BIND_PARAMETERS, m, session);
            if (shouldBindString != null) {
                session.getPlatform().setShouldBindAllParameters(Boolean.parseBoolean(shouldBindString));
            }
            updateLogins(m);
        }
        if (!session.getDatasourceLogin().shouldUseExternalTransactionController()) {
            session.getServerPlatform().disableJTA();
        }
        
        if(session.isServerSession()) {
            updatePools((ServerSession)session, m);
            updateConnectionSettings((ServerSession)session, m);
            if (!isSessionLoadedFromSessionsXML) {
                updateDescriptorCacheSettings(m, loader);
            }
            updateConnectionPolicy((ServerSession)session, m);
        }
        
        if(session.isBroker()) {
            if (this.compositeMemberEmSetupImpls != null) {
                // composite
                Map compositeMemberMapOfProperties = (Map)getConfigProperty(PersistenceUnitProperties.COMPOSITE_UNIT_PROPERTIES, m);
                for(EntityManagerSetupImpl compositeMemberEmSetupImpl : this.compositeMemberEmSetupImpls) {
                    // the properties guaranteed to be non-null after updateCompositeMemberProperties call
                    String compositeMemberPuName = compositeMemberEmSetupImpl.getPersistenceUnitInfo().getPersistenceUnitName();
                    Map compositeMemberProperties = (Map)compositeMemberMapOfProperties.get(compositeMemberPuName);
                    // debug output added to make it easier to navigate the log because the method is called outside of composite member deploy
                    compositeMemberEmSetupImpl.session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "composite_member_begin_call", new Object[]{"updateSession", compositeMemberPuName, state});
                    compositeMemberEmSetupImpl.updateSession(compositeMemberProperties, loader);
                    compositeMemberEmSetupImpl.session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "composite_member_end_call", new Object[]{"updateSession", compositeMemberPuName, state});
                }
            }
            setSessionEventListener(m, loader);
            setExceptionHandler(m, loader);

            updateAllowZeroIdSetting(m);
            updateCacheCoordination(m, loader);
            processSessionCustomizer(m, loader);
        } else {
            setSessionEventListener(m, loader);
            setExceptionHandler(m, loader);

            updateBatchWritingSetting(m, loader);
    
            updateNativeSQLSetting(m);
            updateSequencing(m);
            updateAllowNativeSQLQueriesSetting(m);
            updateSQLCastSetting(m);
            updateUppercaseSetting(m);
            updateCacheStatementSettings(m);
            updateTemporalMutableSetting(m);
            updateTableCreationSettings(m);
            updateIndexForeignKeys(m);
            if (!session.hasBroker()) {
                updateAllowZeroIdSetting(m);
            }
            updateIdValidation(m);
            updatePessimisticLockTimeout(m);
            updateQueryTimeout(m);
            updateQueryTimeoutUnit(m);
            if (!session.hasBroker()) {
                updateCacheCoordination(m, loader);
            }
            updatePartitioning(m, loader);
            updateDatabaseEventListener(m, loader);
            updateSerializer(m, loader);
            updateShouldOptimizeResultSetAccess(m);
            updateTolerateInvalidJPQL(m);
            updateTenancy(m, loader);
            
            // Customizers should be processed last
            processDescriptorCustomizers(m, loader);
            processSessionCustomizer(m, loader);
            
            setDescriptorNamedQueries(m);
        }
    }

    /** 
     * This sets the isInContainerMode flag.
     * "true" indicates container case, "false" - SE.
     */ 
    public void setIsInContainerMode(boolean isInContainerMode) {
        this.isInContainerMode = isInContainerMode;
    }
    
    /**
     * Used to indicate that an EntityManagerFactoryImpl based on this
     * EntityManagerSetupImpl has been refreshed.  This means this EntityManagerSetupImpl
     * will no longer be associated with new EntityManagerFactories
     */
    public void setIsMetadataExpired(boolean hasExpiredMetadata) {
        this.isMetadataExpired = hasExpiredMetadata;
    }
    
    protected void processSessionCustomizer(Map m, ClassLoader loader) {
        String sessionCustomizerClassName = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.SESSION_CUSTOMIZER, m, session);
        if (sessionCustomizerClassName == null) {
            return;
        }        
        Class sessionCustomizerClass = findClassForProperty(sessionCustomizerClassName, PersistenceUnitProperties.SESSION_CUSTOMIZER, loader);
        SessionCustomizer sessionCustomizer;
        try {
            sessionCustomizer = (SessionCustomizer)sessionCustomizerClass.newInstance();
            sessionCustomizer.customize(session);
        } catch (Exception ex) {
            throw EntityManagerSetupException.failedWhileProcessingProperty(PersistenceUnitProperties.SESSION_CUSTOMIZER, sessionCustomizerClassName, ex);
        }
    }

    protected void initOrUpdateLogging(Map m, SessionLog log) {
        String logLevelString = PropertiesHandler.getPropertyValueLogDebug(PersistenceUnitProperties.LOGGING_LEVEL, m, session);
        if (logLevelString != null) {
            log.setLevel(AbstractSessionLog.translateStringToLoggingLevel(logLevelString));
        }
        // category-specific logging level
        Map categoryLogLevelMap = PropertiesHandler.getPrefixValuesLogDebug(PersistenceUnitProperties.CATEGORY_LOGGING_LEVEL_, m, session);
        if(!categoryLogLevelMap.isEmpty()) {
            Iterator it = categoryLogLevelMap.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry entry = (Map.Entry)it.next();
                String category = (String)entry.getKey();
                String value = (String)entry.getValue();
                log.setLevel(AbstractSessionLog.translateStringToLoggingLevel(value), category);
            }
        }
        
        String tsString = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.LOGGING_TIMESTAMP, m, session);
        if (tsString != null) {
            log.setShouldPrintDate(Boolean.parseBoolean(tsString));
        }
        String threadString = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.LOGGING_THREAD, m, session);
        if (threadString != null) {
            log.setShouldPrintThread(Boolean.parseBoolean(threadString));
        }
        String sessionString = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.LOGGING_SESSION, m, session);
        if (sessionString != null) {
            log.setShouldPrintSession(Boolean.parseBoolean(sessionString));
        }
        String connectionString = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.LOGGING_CONNECTION, m, session);
        if (connectionString != null) {
            log.setShouldPrintConnection(Boolean.parseBoolean(connectionString));
        }
        String exString = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.LOGGING_EXCEPTIONS, m, session);
        if (exString != null) {
            log.setShouldLogExceptionStackTrace(Boolean.parseBoolean(exString));
        }
        String shouldDisplayData = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.LOGGING_PARAMETERS, m, session);
        if (shouldDisplayData != null) {
            log.setShouldDisplayData(Boolean.parseBoolean(shouldDisplayData));
        }
    }

    protected void processDescriptorCustomizers(Map m, ClassLoader loader) {
        Map customizerMap = PropertiesHandler.getPrefixValuesLogDebug(PersistenceUnitProperties.DESCRIPTOR_CUSTOMIZER_, m, session);
        if (customizerMap.isEmpty()) {
            return;
        }

        Iterator it = customizerMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            String name = (String)entry.getKey();
            String customizerClassName = (String)entry.getValue();
            
            ClassDescriptor descriptor = session.getDescriptorForAlias(name);
            if (descriptor == null) {
                try {
                    Class javaClass = findClass(name, loader);
                    descriptor = session.getDescriptor(javaClass);
                } catch (Exception ex) {
                    throw EntityManagerSetupException.failedWhileProcessingProperty(PersistenceUnitProperties.DESCRIPTOR_CUSTOMIZER_ + name, customizerClassName, ex);
                }
            }
            if (descriptor != null) {
                Class customizerClass = findClassForProperty(customizerClassName, PersistenceUnitProperties.DESCRIPTOR_CUSTOMIZER_ + name, loader);
                try {
                    DescriptorCustomizer customizer = (DescriptorCustomizer)customizerClass.newInstance();
                    customizer.customize(descriptor);
                } catch (Exception ex) {
                    throw EntityManagerSetupException.failedWhileProcessingProperty(PersistenceUnitProperties.DESCRIPTOR_CUSTOMIZER_ + name, customizerClassName, ex);
                }
            } else {
                // TODO throw a better error, missing descriptor for property.
                throw EntityManagerSetupException.failedWhileProcessingProperty(PersistenceUnitProperties.DESCRIPTOR_CUSTOMIZER_ + name, customizerClassName, null);                
            }
        }
    }
    
    public boolean isInitial() {
        return state == STATE_INITIAL;
    }
    
    /**
     * Used to indicate that an EntityManagerFactoryImpl based on this
     * EntityManagerSetupImpl has been refreshed.  This means this EntityManagerSetupImpl
     * will no longer be associated with new EntityManagerFactories
     */
    public boolean isMetadataExpired() {
        return isMetadataExpired;
    }
    
    public boolean isPredeployed() {
        return state == STATE_PREDEPLOYED;
    }

    public boolean isDeployed() {
        return state == STATE_DEPLOYED;
    }

    public boolean isHalfDeployed() {
        return state == STATE_HALF_DEPLOYED;
    }

    public boolean isUndeployed() {
        return state == STATE_UNDEPLOYED;
    }

    public boolean isPredeployFailed() {
        return state == STATE_PREDEPLOY_FAILED;
    }

    public boolean isDeployFailed() {
        return state == STATE_DEPLOY_FAILED;
    }
    
    public boolean isHalfPredeployedCompositeMember() {
        return state == STATE_HALF_PREDEPLOYED_COMPOSITE_MEMBER;
    }
    
    public String getPersistenceUnitUniqueName() {
        return this.persistenceUnitUniqueName;
    }
    
    public int getFactoryCount() {
        return factoryCount;
    }

    public String getSessionName() {
        return this.sessionName;
    }
    
    public boolean shouldRedeploy() {
        return state == STATE_UNDEPLOYED || state == STATE_PREDEPLOY_FAILED;
    }
    
    /**
     * Return if MetadataSource refresh commands should be sent when refresh is called
     * Checks the PersistenceUnitProperties.METADATA_SOURCE_RCM_COMMAND property and defaults to true.
     */
    public boolean shouldSendMetadataRefreshCommand(Map m) {
        String sendCommand = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.METADATA_SOURCE_RCM_COMMAND, m, this.session);
        if (sendCommand != null) {
            return Boolean.parseBoolean(sendCommand);
        } else {
            return true;
        }
    }

    /**
     * Undeploy may be called several times, but only the call that decreases
     * factoryCount to 0 disconnects the session and removes it from the session manager.
     * This method and predeploy - the only methods altering factoryCount - should be synchronized.
     * After undeploy call that turns factoryCount to 0:
     *   session==null;
     *   PREDEPLOYED, DEPLOYED and DEPLOYED_FAILED states change to UNDEPLOYED state.
     */
    public synchronized void undeploy() {
        if (state == STATE_INITIAL || state == STATE_PREDEPLOY_FAILED || state == STATE_UNDEPLOYED) {
            // must already have factoryCount==0 and session==null
            return;
        }
        // state is PREDEPLOYED, DEPLOYED or DEPLOY_FAILED
        session.log(SessionLog.FINEST, SessionLog.JPA, "undeploy_begin", new Object[]{getPersistenceUnitInfo().getPersistenceUnitName(), session.getName(), state, factoryCount});
        try {
            factoryCount--;
            if(factoryCount > 0) {
                return;
            }
            synchronized (EntityManagerFactoryProvider.emSetupImpls) {
                state = STATE_UNDEPLOYED;
                removeSessionFromGlobalSessionManager();
                // remove undeployed emSetupImpl from the map
                EntityManagerSetupImpl emSetupImpl = EntityManagerFactoryProvider.emSetupImpls.get(sessionName);
                if ((emSetupImpl != null) && (emSetupImpl.equals(this))) {
                    EntityManagerFactoryProvider.emSetupImpls.remove(sessionName);
                }
            }
        } finally {
            session.log(SessionLog.FINEST, SessionLog.JPA, "undeploy_end", new Object[]{getPersistenceUnitInfo().getPersistenceUnitName(), session.getName(), state, factoryCount});
            if(state == STATE_UNDEPLOYED) {
                session = null;
            }
        }
    }    
    
    /**
     * INTERNAL:
     * By default we require a connection to the database. However, when 
     * generating schema to scripts only, this is not required.
     */
    public void setRequiresConnection(boolean requiresConnection) {
        this.requiresConnection = requiresConnection;
    }
    
    /**
     * Allow customized session event listener to be added into session.
     * The method needs to be called in deploy stage.
     */
    protected void setSessionEventListener(Map m, ClassLoader loader){
        //Set event listener if it has been specified.
        String sessionEventListenerClassName = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.SESSION_EVENT_LISTENER_CLASS, m, session);
        if(sessionEventListenerClassName!=null){
            Class sessionEventListenerClass = findClassForProperty(sessionEventListenerClassName,PersistenceUnitProperties.SESSION_EVENT_LISTENER_CLASS, loader);
            try {
                SessionEventListener sessionEventListener = (SessionEventListener)buildObjectForClass(sessionEventListenerClass, SessionEventListener.class);
                if(sessionEventListener!=null){
                    session.getEventManager().addListener(sessionEventListener);
                } else {
                    session.handleException(ValidationException.invalidSessionEventListenerClass(sessionEventListenerClassName));
                }
            } catch (IllegalAccessException e) {
                session.handleException(ValidationException.cannotInstantiateSessionEventListenerClass(sessionEventListenerClassName,e));
            } catch (PrivilegedActionException e) {
                session.handleException(ValidationException.cannotInstantiateSessionEventListenerClass(sessionEventListenerClassName,e));
            } catch (InstantiationException e) {
                session.handleException(ValidationException.cannotInstantiateSessionEventListenerClass(sessionEventListenerClassName,e));
            }
        }
    }
    
    /**
     * Allow customized exception handler to be added into session.
     * The method needs to be called in deploy and pre-deploy stage.
     */
    protected void setExceptionHandler(Map m, ClassLoader loader){
        //Set exception handler if it was specified.
        String exceptionHandlerClassName = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.EXCEPTION_HANDLER_CLASS, m, session);
        if(exceptionHandlerClassName!=null){
            Class exceptionHandlerClass = findClassForProperty(exceptionHandlerClassName,PersistenceUnitProperties.EXCEPTION_HANDLER_CLASS, loader);
            try {
                ExceptionHandler exceptionHandler = (ExceptionHandler)buildObjectForClass(exceptionHandlerClass, ExceptionHandler.class);
                if (exceptionHandler!=null){
                    session.setExceptionHandler(exceptionHandler);
                } else {
                    session.handleException(ValidationException.invalidExceptionHandlerClass(exceptionHandlerClassName));
                }
            } catch (IllegalAccessException e) {
                session.handleException(ValidationException.cannotInstantiateExceptionHandlerClass(exceptionHandlerClassName,e));
            } catch (PrivilegedActionException e) {
                session.handleException(ValidationException.cannotInstantiateExceptionHandlerClass(exceptionHandlerClassName,e));
            } catch (InstantiationException e) {
                session.handleException(ValidationException.cannotInstantiateExceptionHandlerClass(exceptionHandlerClassName,e));
            }
        }
    }
    
    /**
     * Update batch writing setting.
     * The method needs to be called in deploy stage.
     */
    protected void updateBatchWritingSetting(Map persistenceProperties, ClassLoader loader) {
        String batchWritingSettingString = PropertiesHandler.getPropertyValueLogDebug(PersistenceUnitProperties.BATCH_WRITING, persistenceProperties, this.session);
        if (batchWritingSettingString != null) {
             this.session.getPlatform().setUsesBatchWriting(batchWritingSettingString != BatchWriting.None);
             if (batchWritingSettingString == BatchWriting.JDBC) {
                 this.session.getPlatform().setUsesJDBCBatchWriting(true);
                 this.session.getPlatform().setUsesNativeBatchWriting(false);
             } else if (batchWritingSettingString == BatchWriting.Buffered) {
                 this.session.getPlatform().setUsesJDBCBatchWriting(false);
                 this.session.getPlatform().setUsesNativeBatchWriting(false);
             } else if (batchWritingSettingString == BatchWriting.OracleJDBC) {
                 this.session.getPlatform().setUsesNativeBatchWriting(true);
                 this.session.getPlatform().setUsesJDBCBatchWriting(true);
             } else if (batchWritingSettingString == BatchWriting.None) {
                 // Nothing required.
             } else {
                 if (batchWritingSettingString.equalsIgnoreCase("ExaLogic")) {
                     batchWritingSettingString = "oracle.toplink.exalogic.batch.DynamicParameterizedBatchWritingMechanism";
                 }
                 Class cls = findClassForProperty(batchWritingSettingString, PersistenceUnitProperties.BATCH_WRITING, loader);
                 BatchWritingMechanism mechanism = null;
                 try {
                     Constructor constructor = cls.getConstructor();
                     mechanism = (BatchWritingMechanism)constructor.newInstance();
                 } catch (Exception exception) {
                     if (batchWritingSettingString.indexOf('.') == -1) {
                         throw new IllegalArgumentException(ExceptionLocalization.buildMessage("ejb30-illegal-property-value", new Object[]{PersistenceUnitProperties.BATCH_WRITING, batchWritingSettingString}));
                     } else {
                         throw EntityManagerSetupException.failedToInstantiateProperty(batchWritingSettingString, PersistenceUnitProperties.BATCH_WRITING, exception);
                     }
                 }
                 this.session.getPlatform().setBatchWritingMechanism(mechanism);
             }
        }
        // Set batch size.
        String sizeString = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.BATCH_WRITING_SIZE, persistenceProperties, this.session);
        if (sizeString != null) {
            try {
                this.session.getPlatform().setMaxBatchWritingSize(Integer.parseInt(sizeString));
            } catch (NumberFormatException invalid) {
                session.handleException(ValidationException.invalidValueForProperty(sizeString, PersistenceUnitProperties.BATCH_WRITING_SIZE, invalid));
            }
        }
    }

    /**
     * Load the Metadata Repository for Extensibility
     */
    protected void updateMetadataRepository(Map m, ClassLoader loader){
        Object metadataSource = EntityManagerFactoryProvider.getConfigPropertyLogDebug(PersistenceUnitProperties.METADATA_SOURCE, m, session);
        if (metadataSource != null && metadataSource instanceof MetadataSource){
            processor.setMetadataSource((MetadataSource)metadataSource);
        } else {
            if (metadataSource!=null) {
                String repository = (String)metadataSource;
                if (repository.equalsIgnoreCase("XML")) {
                    processor.setMetadataSource(new XMLMetadataSource());
                } else {
                    Class transportClass = findClassForProperty(repository, PersistenceUnitProperties.METADATA_SOURCE, loader);
                    try {
                        processor.setMetadataSource((MetadataSource)transportClass.newInstance());
                    } catch (Exception invalid) {
                        session.handleException(EntityManagerSetupException.failedToInstantiateProperty(repository, PersistenceUnitProperties.METADATA_SOURCE,invalid));
                    }
                }
            }
        }
    }

    /**
     * Check for a tuning property and run the tuner preDeploy.
     */
    protected void updateTunerPreDeploy(Map m, ClassLoader loader) {
        String tuning = (String)EntityManagerFactoryProvider.getConfigPropertyLogDebug(PersistenceUnitProperties.TUNING, m, this.session);
        if (tuning != null) {
            SessionTuner tuner = null;
            if (tuning.equalsIgnoreCase("Safe")) {
                tuner = new SafeModeTuner();
            } else if (tuning.equalsIgnoreCase("Standard")) {
                tuner = new StandardTuner();
            } else {
                if (tuning.equalsIgnoreCase("ExaLogic")) {
                    tuning = "oracle.toplink.exalogic.tuning.ExaLogicTuner";
                }
                Class tunerClass = findClassForProperty(tuning, PersistenceUnitProperties.TUNING, loader);
                try {
                    tuner = (SessionTuner)tunerClass.newInstance();
                } catch (Exception invalid) {
                    this.session.handleException(EntityManagerSetupException.failedToInstantiateProperty(tuning, PersistenceUnitProperties.TUNING, invalid));
                }
            }
            getDatabaseSession().setTuner(tuner);
            tuner.tunePreDeploy(m);
        }
    }

    /**
     * Check for a tuning property and run the tuner deploy.
     */
    protected void updateTunerDeploy(Map m, ClassLoader loader) {
        if (getDatabaseSession().getTuner() != null) {
            getDatabaseSession().getTuner().tuneDeploy(getDatabaseSession());
        }
    }

    /**
     * Check for a tuning property and run the tuner deploy.
     */
    protected void updateTunerPostDeploy(Map m, ClassLoader loader) {
        if (getDatabaseSession().getTuner() != null) {
            getDatabaseSession().getTuner().tunePostDeploy(getDatabaseSession());
        }
    }

    /**
     * Allow the deployment metadata to be freed post-deploy to conserve memory.
     */
    protected void updateFreeMemory(Map m) {
        String freeMemory = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.FREE_METADATA, m, session);
        if (freeMemory != null) {
           if (freeMemory.equalsIgnoreCase("true")) {
               XMLEntityMappingsReader.clear();
           } else if (freeMemory.equalsIgnoreCase("false")) {
               // default.
           } else {
               session.handleException(ValidationException.invalidBooleanValueForProperty(freeMemory, PersistenceUnitProperties.FREE_METADATA));
           }
        }
    }

    /**
     * Enable or disable the capability of Native SQL function.  
     * The method needs to be called in deploy stage.
     */
    protected void updateNativeSQLSetting(Map m){
        //Set Native SQL flag if it was specified.
        String nativeSQLString = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.NATIVE_SQL, m, session);
        if(nativeSQLString!=null){
           if(nativeSQLString.equalsIgnoreCase("true") ){
                 session.getProject().getLogin().useNativeSQL();
           }else if (nativeSQLString.equalsIgnoreCase("false")){
                 session.getProject().getLogin().dontUseNativeSQL();
           }else{
                 session.handleException(ValidationException.invalidBooleanValueForSettingNativeSQL(nativeSQLString));
           }
        }
    }

    /**
     * Configure sequencing settings.
     */
    protected void updateSequencing(Map m){
        String useTable = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.SEQUENCING_SEQUENCE_DEFAULT, m, session);
        if (useTable != null) {
           if (useTable.equalsIgnoreCase("true")) {
               this.session.getPlatform().setDefaultNativeSequenceToTable(true);
           } else if (useTable.equalsIgnoreCase("false")) {
               this.session.getPlatform().setDefaultNativeSequenceToTable(false);
           } else {
               this.session.handleException(ValidationException.invalidBooleanValueForProperty(useTable, PersistenceUnitProperties.SEQUENCING_SEQUENCE_DEFAULT));
           }
        }
    }

    /**
     * Load the projectCacheAccessor for JPA project caching
     */
    protected void updateProjectCache(Map m, ClassLoader loader){
        Object accessor = EntityManagerFactoryProvider.getConfigPropertyLogDebug(PersistenceUnitProperties.PROJECT_CACHE, m, session);
        if (accessor != null ) {
            if (accessor instanceof ProjectCache) {
                projectCacheAccessor = (ProjectCache)accessor;
            } else {
                String accessorType = (String)accessor;
                if (accessorType.equalsIgnoreCase("java-serialization")) {
                    projectCacheAccessor = new FileBasedProjectCache();
                } else {
                    Class transportClass = findClassForProperty(accessorType, PersistenceUnitProperties.PROJECT_CACHE, loader);
                    try {
                        projectCacheAccessor = (ProjectCache)transportClass.newInstance();
                    } catch (Exception invalid) {
                        session.handleException(EntityManagerSetupException.failedToInstantiateProperty(accessorType, PersistenceUnitProperties.METADATA_SOURCE,invalid));
                    }
                }
            }
        }
    }

    /**
     * Enable or disable the capability of Native SQL function.  
     * The method needs to be called in deploy stage.
     */
    protected void updateJPQLParser(Map m) {
        // Set JPQL parser if it was specified.
        String parser = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JPQL_PARSER, m, this.session);
        if (parser != null) {
            if (parser.equalsIgnoreCase(ParserType.Hermes)) {
                parser = "org.eclipse.persistence.internal.jpa.jpql.HermesParser";
            } else if (parser.equalsIgnoreCase(ParserType.ANTLR)) {
                parser = "org.eclipse.persistence.queries.ANTLRQueryBuilder";
            }
            this.session.setProperty(PersistenceUnitProperties.JPQL_PARSER, parser);
        }
        // Set JPQL parser validation mode if it was specified.
        String validation = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.JPQL_VALIDATION, m, this.session);
        if (validation != null) {
            this.session.setProperty(PersistenceUnitProperties.JPQL_VALIDATION, validation);
        }
    }
    
    /**
     * Enable or disable the capability of Native SQL function.  
     * The method needs to be called in deploy stage.
     */
    protected void updateAllowNativeSQLQueriesSetting(Map m){
        // Set allow native SQL queries flag if it was specified.
        String allowNativeSQLQueriesString = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.ALLOW_NATIVE_SQL_QUERIES, m, session);
        
        if (allowNativeSQLQueriesString != null) {
           if (allowNativeSQLQueriesString.equalsIgnoreCase("true")) {
               session.getProject().setAllowNativeSQLQueries(true);
           } else if (allowNativeSQLQueriesString.equalsIgnoreCase("false")) {
               session.getProject().setAllowNativeSQLQueries(false);
           } else {
               session.handleException(ValidationException.invalidBooleanValueForSettingAllowNativeSQLQueries(allowNativeSQLQueriesString));
           }
        }
    }
    
    /**
     * Enable or disable SQL casting.
     */
    protected void updateSQLCastSetting(Map m) {
        //Set Native SQL flag if it was specified.
        String sqlCastString = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.SQL_CAST, m, session);
        if (sqlCastString != null) {
           if (sqlCastString.equalsIgnoreCase("true")) {
                 session.getProject().getLogin().getPlatform().setIsCastRequired(true);
           } else if (sqlCastString.equalsIgnoreCase("false")) {
               session.getProject().getLogin().getPlatform().setIsCastRequired(false);
           } else {
                 session.handleException(ValidationException.invalidBooleanValueForProperty(sqlCastString, PersistenceUnitProperties.SQL_CAST));
           }
        }
    }
    
    /**
     * Enable or disable forcing field names to uppercase.  
     * The method needs to be called in deploy stage.
     */
    protected void updateUppercaseSetting(Map m){
        //Set Native SQL flag if it was specified.
        String uppercaseString = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.NATIVE_QUERY_UPPERCASE_COLUMNS, m, session);
        if (uppercaseString != null) {
           if (uppercaseString.equalsIgnoreCase("true") ){
               this.session.getProject().getLogin().setShouldForceFieldNamesToUpperCase(true);
           } else if (uppercaseString.equalsIgnoreCase("false")) {
               this.session.getProject().getLogin().setShouldForceFieldNamesToUpperCase(false);
           } else {
               this.session.handleException(ValidationException.invalidBooleanValueForProperty(uppercaseString, PersistenceUnitProperties.NATIVE_QUERY_UPPERCASE_COLUMNS));
           }
        }
    }

    /**

    /**
     * Enable or disable forcing field names to be case insensitive.  Implementation of case insensitive column handling relies on setting
     * both sides to uppercase (the column names from annotations/xml as well as what is returned from the JDBC/statement)
     * The method needs to be called in deploy stage.  
     */
    public static void updateCaseSensitivitySettings(Map m, MetadataProject project, AbstractSession session){
        //Set Native SQL flag if it was specified.
        String insensitiveString = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.UPPERCASE_COLUMN_NAMES, m, session);
        if (insensitiveString == null || insensitiveString.equalsIgnoreCase("true")) {
            // Set or default to case in-sensitive.
           project.setShouldForceFieldNamesToUpperCase(true);
           session.getProject().getLogin().setShouldForceFieldNamesToUpperCase(true);
        } else if (insensitiveString.equalsIgnoreCase("false")) {
            project.setShouldForceFieldNamesToUpperCase(false);
            session.getProject().getLogin().setShouldForceFieldNamesToUpperCase(false);
        } else {
            session.handleException(ValidationException.invalidBooleanValueForProperty(insensitiveString, PersistenceUnitProperties.UPPERCASE_COLUMN_NAMES));
        }
    }
    
    /**
     * Update the default pessimistic lock timeout value. 
     * @param persistenceProperties the properties map
     */
    protected void updatePessimisticLockTimeout(Map persistenceProperties) {
        String pessimisticLockTimeout = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.PESSIMISTIC_LOCK_TIMEOUT, persistenceProperties, session);

        if (pessimisticLockTimeout != null) {
            try {
                session.setPessimisticLockTimeoutDefault(Integer.parseInt(pessimisticLockTimeout));
            } catch (NumberFormatException invalid) {
                session.handleException(ValidationException.invalidValueForProperty(pessimisticLockTimeout, PersistenceUnitProperties.PESSIMISTIC_LOCK_TIMEOUT, invalid));
            }
        }
    }
    
    /**
     * Enable or disable statements cached, update statements cache size. 
     * The method needs to be called in deploy stage. 
     */
    protected void updateCacheStatementSettings(Map m){
        // Cache statements if flag was specified.
        String statmentsNeedBeCached = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.CACHE_STATEMENTS, m, session);
        if (statmentsNeedBeCached!=null) {
            if (statmentsNeedBeCached.equalsIgnoreCase("true")) {
                if (session.isServerSession() && ((ServerSession)session).getConnectionPools().isEmpty()){
                    session.log(SessionLog.WARNING, SessionLog.PROPERTIES, "persistence_unit_ignores_statments_cache_setting", new Object[]{null});
                 } else {
                     session.getProject().getLogin().setShouldCacheAllStatements(true);
                 }
            } else if (statmentsNeedBeCached.equalsIgnoreCase("false")) {
                session.getProject().getLogin().setShouldCacheAllStatements(false);
            } else {
                session.handleException(ValidationException.invalidBooleanValueForEnableStatmentsCached(statmentsNeedBeCached));
            }
        }
        
        // Set statement cache size if specified.
        String cacheStatementsSize = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.CACHE_STATEMENTS_SIZE, m, session);
        if (cacheStatementsSize!=null) {
            try {
                session.getProject().getLogin().setStatementCacheSize(Integer.parseInt(cacheStatementsSize));
            } catch (NumberFormatException e) {
                session.handleException(ValidationException.invalidCacheStatementsSize(cacheStatementsSize,e.getMessage()));
            }
        }
    }

    /**
     * Enable or disable default allowing 0 as an id. 
     */
    @SuppressWarnings("deprecation")
    protected void updateAllowZeroIdSetting(Map m) {
        String allowZero = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.ALLOW_ZERO_ID, m, this.session);
        if (allowZero != null) {
            if (allowZero.equalsIgnoreCase("true")) {
               Helper.isZeroValidPrimaryKey = true;
            } else if (allowZero.equalsIgnoreCase("false")) {
                Helper.isZeroValidPrimaryKey = false;
            } else {
                session.handleException(ValidationException.invalidBooleanValueForProperty(allowZero, PersistenceUnitProperties.ALLOW_ZERO_ID));
            }
        }
    }

    /**
     * Enable or disable default allowing 0 as an id. 
     */
    protected void updateIdValidation(Map m) {
        String idValidationString = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.ID_VALIDATION, m, session);
        if (idValidationString != null) {
            session.getProject().setDefaultIdValidation(IdValidation.valueOf(idValidationString));
        }
    }
    
    /**
     * sets the TABLE_CREATION_SUFFIX property on the session's project to be applied to all table creation statements (DDL)
     */
    protected void updateTableCreationSettings(Map m) {
        String tableCreationSuffix = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.TABLE_CREATION_SUFFIX, m, session);
        if (tableCreationSuffix != null && tableCreationSuffix.length()>0) {
            session.getPlatform().setTableCreationSuffix(tableCreationSuffix);
        }
    }
    
    /**
     * Sets shouldCreateIndicesOnForeignKeys DDL generation option.
     */
    protected void updateIndexForeignKeys(Map m) {
        String indexForeignKeys = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.DDL_GENERATION_INDEX_FOREIGN_KEYS, m, this.session);
        if (indexForeignKeys != null && (indexForeignKeys.length() > 0)) {
            if (indexForeignKeys.equalsIgnoreCase("true") ){
                this.session.getProject().getLogin().setShouldCreateIndicesOnForeignKeys(true);
            } else if (indexForeignKeys.equalsIgnoreCase("false")){
                this.session.getProject().getLogin().setShouldCreateIndicesOnForeignKeys(false);
            } else {
                this.session.handleException(ValidationException.invalidBooleanValueForProperty(indexForeignKeys, PersistenceUnitProperties.DDL_GENERATION_INDEX_FOREIGN_KEYS));
            }
        }
    }

    /**
     * Enable or disable default temporal mutable setting. 
     * The method needs to be called in deploy stage. 
     */
    protected void updateTemporalMutableSetting(Map m) {
        // Cache statements if flag was specified.
        String temporalMutable = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.TEMPORAL_MUTABLE, m, session);
        if (temporalMutable != null) {
            if (temporalMutable.equalsIgnoreCase("true")) {
               session.getProject().setDefaultTemporalMutable(true);
            } else if (temporalMutable.equalsIgnoreCase("false")) {
               session.getProject().setDefaultTemporalMutable(false);
            } else {
                session.handleException(ValidationException.invalidBooleanValueForProperty(temporalMutable, PersistenceUnitProperties.TEMPORAL_MUTABLE));
            }
        }
    }

    /**
     * Copy named queries defined in EclipseLink descriptor into the session if it was indicated to do so.
     */
    protected void setDescriptorNamedQueries(Map m) {
        // Copy named queries to session if the flag has been specified.
        String addNamedQueriesString  = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.INCLUDE_DESCRIPTOR_QUERIES, m, session);
        if (addNamedQueriesString!=null) {
            if (addNamedQueriesString.equalsIgnoreCase("true")) {
                session.copyDescriptorNamedQueries(false);
            } else {
                if (!addNamedQueriesString.equalsIgnoreCase("false")) {
                   session.handleException(ValidationException.invalidBooleanValueForAddingNamedQueries(addNamedQueriesString));
                }
            }
        }
    }

    private void updateQueryTimeout(Map persistenceProperties) {
        String timeout = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.QUERY_TIMEOUT, persistenceProperties, session);
        try {
            if (timeout != null) {
                session.setQueryTimeoutDefault(Integer.parseInt(timeout));
            }
        } catch (NumberFormatException exception) {
            this.session.handleException(ValidationException.invalidValueForProperty(timeout, PersistenceUnitProperties.QUERY_TIMEOUT, exception));
        }
    }

    //Bug #456067: Added persistence unit support for timeout units
    private void updateQueryTimeoutUnit(Map persistenceProperties) {
        String timeoutUnit = EntityManagerFactoryProvider.getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.QUERY_TIMEOUT_UNIT, persistenceProperties, session);
        try {
            if (timeoutUnit != null) {
                TimeUnit unit = TimeUnit.valueOf(timeoutUnit);
                session.setQueryTimeoutUnitDefault(unit);
            }
        } catch (IllegalArgumentException exception) {
            this.session.handleException(ValidationException.invalidValueForProperty(timeoutUnit, PersistenceUnitProperties.QUERY_TIMEOUT_UNIT, exception));
        }
    }

    /**
     * If Bean Validation is enabled, bootstraps Bean Validation on descriptors.
     * @param puProperties merged properties for this persistence unit
     */
    private void addBeanValidationListeners(Map puProperties, ClassLoader appClassLoader) {
        ValidationMode validationMode = getValidationMode(persistenceUnitInfo, puProperties);
        if (validationMode == ValidationMode.AUTO || validationMode == ValidationMode.CALLBACK) {
            // BeanValidationInitializationHelper contains static reference to javax.validation.* classes. We need to support
            // environment where these classes are not available.
            // To guard against some vms that eagerly resolve, reflectively load class to prevent any static reference to it
            String helperClassName = "org.eclipse.persistence.internal.jpa.deployment.BeanValidationInitializationHelper$BeanValidationInitializationHelperImpl";
            Class helperClass;
            try {
                if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
                    helperClass = AccessController.doPrivileged(
                            new PrivilegedClassForName(helperClassName, true, appClassLoader));
                } else {
                    helperClass = PrivilegedAccessHelper.getClassForName(helperClassName, true, appClassLoader);
                }
                BeanValidationInitializationHelper beanValidationInitializationHelper = (BeanValidationInitializationHelper)helperClass.newInstance();
                beanValidationInitializationHelper.bootstrapBeanValidation(puProperties, session, appClassLoader);
            } catch (Throwable e) {  //Catching Throwable to catch any linkage errors on vms that resolve eagerly
                if (validationMode == ValidationMode.CALLBACK) {
                    throw PersistenceUnitLoadingException.exceptionObtainingRequiredBeanValidatorFactory(e);
                } // else validationMode == ValidationMode.AUTO. Log a message, Ignore the exception
                this.session.log(SessionLog.FINEST, SessionLog.JPA, "validation_factory_not_initialized", new Object[]{ e.getMessage() });
            }
        }
    }

    /**
     * Validation mode from information in persistence.xml and properties specified at EMF creation
     * @param persitenceUnitInfo PersitenceUnitInfo instance for this persistence unit
     * @param puProperties merged properties for this persistence unit
     * @return validation mode
     */
    private static ValidationMode getValidationMode(PersistenceUnitInfo persitenceUnitInfo, Map puProperties) {
        //Check if overridden at emf creation
        String validationModeAtEMFCreation = (String) puProperties.get(PersistenceUnitProperties.VALIDATION_MODE);
        if(validationModeAtEMFCreation != null) {
            // User will receive IllegalArgumentException if an invalid mode has been specified
            return ValidationMode.valueOf(validationModeAtEMFCreation.toUpperCase());
        }

        //otherwise:
        ValidationMode validationMode = null;
        // Initialize with value in persitence.xml
        // Using reflection to call getValidationMode to prevent blowing up while we are running in JPA 1.0 environment
        // (This would be all JavaEE5 appservers) where PersistenceUnitInfo does not implement method getValidationMode().
        try {
            Method method = null;
            if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
                method = AccessController.doPrivileged(new PrivilegedGetDeclaredMethod(PersistenceUnitInfo.class, "getValidationMode", null));
                validationMode = (ValidationMode) AccessController.doPrivileged(new PrivilegedMethodInvoker(method, persitenceUnitInfo));
            } else {
                method = PrivilegedAccessHelper.getDeclaredMethod(PersistenceUnitInfo.class, "getValidationMode", null);
                validationMode = (ValidationMode) PrivilegedAccessHelper.invokeMethod(method, persitenceUnitInfo, null);
            }
        } catch (Throwable exception) {
            // We are running in JavaEE5 environment. Catch and swallow any exceptions and return null.
        }

        if(validationMode == null) {
            // Default to AUTO as specified in JPA spec.
            validationMode = ValidationMode.AUTO;
        }
        return validationMode;
    }
    
    /**
     * INTERNAL:
     * Return an instance of Metamodel interface for access to the
     * metamodel of the persistence unit.
     * This method will complete any initialization done in the predeploy phase
     * of deployment.
     * @return Metamodel instance
     * @since Java Persistence 2.0
     */
    public Metamodel getMetamodel(ClassLoader classLoader) {
        preInitializeMetamodel();
        if (!((MetamodelImpl)metaModel).isInitialized()){
            ((MetamodelImpl)metaModel).initialize(classLoader);
            // If the canonical metamodel classes exist, initialize them
            initializeCanonicalMetamodel(metaModel);
        }
        return metaModel;
    }

    /**
     * INTERNAL:
     * Initialize the Canonical Metamodel classes generated by EclipseLink
     * @since Java Persistence 2.0 
     */
    protected void initializeCanonicalMetamodel(Metamodel model) {
        // 338837: verify that the collection is not empty - this would mean entities did not make it into the search path
        if(null == model.getManagedTypes() || model.getManagedTypes().isEmpty()) {
            getSession().log(SessionLog.FINER, SessionLog.METAMODEL, "metamodel_type_collection_empty");
        }
        for (ManagedType manType : model.getManagedTypes()) {
            boolean classInitialized = false;
            String className = MetadataHelper.getQualifiedCanonicalName(manType.getJavaType().getName(), getSession());
            try {                
                Class clazz = (Class)this.getSession().getDatasourcePlatform().convertObject(className, ClassConstants.CLASS);
                classInitialized=true;
                this.getSession().log(SessionLog.FINER, SessionLog.METAMODEL, "metamodel_canonical_model_class_found", className);                
                String fieldName = "";
                for(Object attribute : manType.getDeclaredAttributes()) { 
                    try {
                        fieldName = ((Attribute)attribute).getName();
                        if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                            AccessController.doPrivileged(new PrivilegedGetDeclaredField(clazz, fieldName, false)).set(clazz, attribute);
                        } else {
                            PrivilegedAccessHelper.getDeclaredField(clazz, fieldName, false).set(clazz, attribute);
                        }  
                    } catch (NoSuchFieldException nsfe) {
                        // Ignore fields missing in canonical model (dclarke bug 346106)
                    } catch (Exception e) {
                       ValidationException v = ValidationException.invalidFieldForClass(fieldName, clazz);
                       v.setInternalException(e);
                       throw v;
                    }
                }
            } catch (ConversionException exception){
            }
            if (!classInitialized) {
                getSession().log(SessionLog.FINER, SessionLog.METAMODEL, "metamodel_canonical_model_class_not_found", className);
            }
        }
    }    
    
    /**
     * INTERNAL:
     * Convenience function to allow us to reset the Metamodel 
     * in the possible case that we want to regenerate it.
     * This function is outside of the JPA 2.0 specification.
     * @param aMetamodel
     * @since Java Persistence 2.0 
     */
    public void setMetamodel(Metamodel aMetamodel) {
        this.metaModel = aMetamodel;
    }
    
    public boolean mustBeCompositeMember() {
        return mustBeCompositeMember(this.persistenceUnitInfo);
    }
    
    public boolean isCompositeMember() {
        return this.compositeEmSetupImpl != null;
    }
    
    public boolean isComposite() {
        return this.compositeMemberEmSetupImpls != null;
    }
    
    public static boolean mustBeCompositeMember(PersistenceUnitInfo puInfo) {
        String mustBeCompositeMemberStr = PropertiesHandler.getPropertyValue(PersistenceUnitProperties.COMPOSITE_UNIT_MEMBER, puInfo.getProperties(), false);
        if(mustBeCompositeMemberStr != null) {
            return mustBeCompositeMemberStr.equals("true");
        } else {
            return false;
        }
    }
    public static boolean isComposite(PersistenceUnitInfo puInfo) {
        String isCompositeString = PropertiesHandler.getPropertyValue(PersistenceUnitProperties.COMPOSITE_UNIT, puInfo.getProperties(), false);
        if(isCompositeString != null) {
            return isCompositeString.equals("true");
        } else {
            return false;
        }
    }
    
    public void setCompositeEmSetupImpl(EntityManagerSetupImpl compositeEmSetupImpl) {
        this.compositeEmSetupImpl = compositeEmSetupImpl;
    }

    public EntityManagerSetupImpl getCompositeEmSetupImpl() {
        return this.compositeEmSetupImpl;
    }
    
    // predeploy method will be used for static weaving
    public void setStaticWeaveInfo(StaticWeaveInfo staticWeaveInfo) {
        this.staticWeaveInfo = staticWeaveInfo;
    }

    protected void predeployCompositeMembers(Map predeployProperties, ClassLoader tempClassLoader) {
        // get all puInfos found in jar-files specified in composite's persistence.xml
        // all these puInfos are not composite because composites are recursively "taken apart", too. 
        Set<SEPersistenceUnitInfo> compositeMemberPuInfos = getCompositeMemberPuInfoSet(persistenceUnitInfo, predeployProperties);
        // makes sure each member has a non-null property, overrides where required properties with composite's predeploy properties.
        updateCompositeMembersProperties(compositeMemberPuInfos, predeployProperties);
        // Don't log these properties - may contain passwords. The properties will be logged by contained persistence units.
        Map compositeMemberMapOfProperties = (Map)getConfigProperty(PersistenceUnitProperties.COMPOSITE_UNIT_PROPERTIES, predeployProperties);
        this.compositeMemberEmSetupImpls = new HashSet(compositeMemberPuInfos.size());
        this.processor = new MetadataProcessor();
        if (enableWeaving) {
            this.weaver = new PersistenceWeaver(this.session, new HashMap());
        }
        
        // create containedEmSetupImpls and predeploy them for the first time.
        // predeploy divided in three stages (modes):
        // all composite members should complete a stage before any of them can move to the next one.
        for (SEPersistenceUnitInfo compositeMemberPuInfo : compositeMemberPuInfos) {
            // set composite's temporary classloader
            compositeMemberPuInfo.setNewTempClassLoader(tempClassLoader);
            String containedPuName = compositeMemberPuInfo.getPersistenceUnitName();
            EntityManagerSetupImpl containedEmSetupImpl = new EntityManagerSetupImpl(containedPuName, containedPuName);
            // set composite
            containedEmSetupImpl.setCompositeEmSetupImpl(this);
            // non-null only in case predeploy is used for static weaving
            containedEmSetupImpl.setStaticWeaveInfo(this.staticWeaveInfo);
            // the properties guaranteed to be non-null after updateCompositeMemberProperties call
            Map compositeMemberProperties = (Map)compositeMemberMapOfProperties.get(containedPuName);
            containedEmSetupImpl.predeploy(compositeMemberPuInfo, compositeMemberProperties);
            // reset temporary classloader back to the original
            compositeMemberPuInfo.setNewTempClassLoader(compositeMemberPuInfo.getClassLoader());
            this.compositeMemberEmSetupImpls.add(containedEmSetupImpl);
        }
        
        // after the first loop containedEmSetupImpls are in HalfPredeployed state, 
        // mode = COMPOSITE_MEMBER_MIDDLE mode
        for(EntityManagerSetupImpl containedEmSetupImpl : this.compositeMemberEmSetupImpls) {
            // properties not used, puInfo already set
            containedEmSetupImpl.predeploy(null, null);
        }

        // after the second loop containedEmSetupImpls are still in HalfPredeployed state, 
        // mode = COMPOSITE_MEMBER_FINAL mode
        for(EntityManagerSetupImpl containedEmSetupImpl : this.compositeMemberEmSetupImpls) {
            // properties not used, puInfo already set
            PersistenceWeaver containedWeaver = (PersistenceWeaver)containedEmSetupImpl.predeploy(null, null);
            // containedEmSetupImpl is finally in Predeployed state.
            // if both composite and composite member weavings enabled copy class details from member's weaver to composite's one.
            if(enableWeaving && containedWeaver != null) {
                this.weaver.getClassDetailsMap().putAll(containedWeaver.getClassDetailsMap());
            }
        }

        if(enableWeaving && this.weaver.getClassDetailsMap().isEmpty()) {
            this.weaver = null;
        }
    }

    protected void deployCompositeMembers(Map deployProperties, ClassLoader realClassLoader) {
        // Don't log these properties - may contain passwords. The properties will be logged by contained persistence units.
        Map compositeMemberMapOfProperties = (Map)getConfigProperty(PersistenceUnitProperties.COMPOSITE_UNIT_PROPERTIES, deployProperties);
        for(EntityManagerSetupImpl compositeMemberEmSetupImpl : this.compositeMemberEmSetupImpls) {
            // the properties guaranteed to be non-null after updateCompositeMemberProperties call
            Map compositeMemberProperties = (Map)compositeMemberMapOfProperties.get(compositeMemberEmSetupImpl.getPersistenceUnitInfo().getPersistenceUnitName());
            compositeMemberEmSetupImpl.deploy(realClassLoader, compositeMemberProperties);
            AbstractSession containedSession = compositeMemberEmSetupImpl.getSession(); 
            ((SessionBroker)session).registerSession(containedSession.getName(), containedSession);
        }
    }

    /**
     * INTERNAL:
     * Cause the first phase of metamodel initialization.  This phase involves detecting the classes involved
     * and build metamodel instances for them. 
     */
    public void preInitializeMetamodel(){
        // perform lazy initialisation
        Metamodel tempMetaModel = null;
        if (null == metaModel){
            // 338837: verify that the collection is not empty - this would mean entities did not make it into the search path
            tempMetaModel = new MetamodelImpl(this);
            // set variable after init has executed without exception
            metaModel = tempMetaModel;
        }
    }
    
    /**
     * INTERNAL:
     * First phase of canonical metamodel initialization.  For each class the metamodel is aware of, check
     * for a canonical metamodel class and initialize each attribute in it with a proxy that can cause the
     * rest of the metamodel population.  Attributes are found reflectively rather than through the metamodel
     * to avoid having to further initialize the metamodel.
     * @param factory
     */
    public void preInitializeCanonicalMetamodel(EntityManagerFactoryImpl factory){
        // 338837: verify that the collection is not empty - this would mean entities did not make it into the search path
        if(null == metaModel.getManagedTypes() || metaModel.getManagedTypes().isEmpty()) {
            getSession().log(SessionLog.FINER, SessionLog.METAMODEL, "metamodel_type_collection_empty");
        }
        for (ManagedType manType : metaModel.getManagedTypes()) {
            boolean classInitialized = false;
            String className = MetadataHelper.getQualifiedCanonicalName(((ManagedTypeImpl)manType).getJavaTypeName(), getSession());
            try {                
                Class clazz = (Class)this.getSession().getDatasourcePlatform().convertObject(className, ClassConstants.CLASS);
                classInitialized=true;
                this.getSession().log(SessionLog.FINER, SessionLog.METAMODEL, "metamodel_canonical_model_class_found", className); 
                Field[] fields = null;
                if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                    fields = AccessController.doPrivileged(new PrivilegedGetDeclaredFields(clazz));
                } else {
                    fields = PrivilegedAccessHelper.getDeclaredFields(clazz);
                }
                for(Field attribute : fields) {
                    if (Attribute.class.isAssignableFrom(attribute.getType())){
                        Object assignedAttribute = null;
                        if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                            assignedAttribute = AccessController.doPrivileged(new PrivilegedGetValueFromField(attribute, null));
                        } else {
                            assignedAttribute =PrivilegedAccessHelper.getValueFromField(attribute, null);
                        }
                        AttributeProxyImpl proxy = null;
                        if (assignedAttribute == null){
                            if (SingularAttribute.class.isAssignableFrom(attribute.getType())){
                                proxy = new SingularAttributeProxyImpl();
                            } else if (MapAttribute.class.isAssignableFrom(attribute.getType())){
                                proxy = new MapAttributeProxyImpl();
                            } else if (SetAttribute.class.isAssignableFrom(attribute.getType())){
                                proxy = new SetAttributeProxyImpl();
                            } else if (ListAttribute.class.isAssignableFrom(attribute.getType())){
                                proxy = new ListAttributeProxyImpl();
                            } else if (CollectionAttribute.class.isAssignableFrom(attribute.getType())){
                                proxy = new CollectionAttributeProxyImpl();
                            }
                            if (proxy != null){
                                attribute.setAccessible(true);
                                attribute.set(null, proxy);
                            }
                        } else if (assignedAttribute instanceof AttributeProxyImpl){
                            proxy = (AttributeProxyImpl)assignedAttribute;
                        }
                        if (proxy != null){
                            proxy.addFactory(factory);
                        }
                    }
                }
            } catch (PrivilegedActionException pae){
                getSession().logThrowable(SessionLog.FINEST,  SessionLog.METAMODEL, pae);
            } catch (IllegalAccessException iae){
                getSession().logThrowable(SessionLog.FINEST,  SessionLog.METAMODEL, iae);
            } catch (ConversionException ce){
            }
            if (!classInitialized) {
                getSession().log(SessionLog.FINER, SessionLog.METAMODEL, "metamodel_canonical_model_class_not_found", className);
            }
        }
    }
    
    /*
     * Overide composite member properties' map with a new one, which
     * has (possibly empty but non-null) properties for each composite member,
     * for required properties overrides values with those from composite properties. 
     */
    protected void updateCompositeMembersProperties(Map compositeProperties) {
        Set<SEPersistenceUnitInfo> compositePuInfos = new HashSet(compositeMemberEmSetupImpls.size());
        for (EntityManagerSetupImpl compositeMemberEmSetupImpl : compositeMemberEmSetupImpls) {
            compositePuInfos.add((SEPersistenceUnitInfo)compositeMemberEmSetupImpl.persistenceUnitInfo);
        }
        updateCompositeMembersProperties(compositePuInfos, compositeProperties);
    }

    /*
     * Overide composite member properties' map with a new one, which
     * has (possibly empty but non-null) properties for each composite member,
     * for required properties overrides values with those from composite properties. 
     * Parameter compositePuInfo indicates whether compositeMemberPreoperties should be merged (overriding) with its puInfo properties
     * (false for predeploy, true for deploy).
     */
    protected void updateCompositeMembersProperties(Set<SEPersistenceUnitInfo> compositePuInfos, Map compositeProperties) {
        // Don't log these properties - may contain passwords. The properties will be logged by contained persistence units.
        Map compositeMemberMapOfProperties = (Map)getConfigProperty(PersistenceUnitProperties.COMPOSITE_UNIT_PROPERTIES, compositeProperties);
        Map newCompositeMemberMapOfProperties;
        if (compositeMemberMapOfProperties == null) {
            newCompositeMemberMapOfProperties = new HashMap(compositePuInfos.size());
        } else {
            // Don't alter user-supplied properties' map - create a copy instead
            newCompositeMemberMapOfProperties = new HashMap(compositeMemberMapOfProperties);
        }
        
        for (SEPersistenceUnitInfo compositePuInfo : compositePuInfos) {
            String compositeMemberPuName = compositePuInfo.getPersistenceUnitName();
            Map compositeMemberProperties = (Map)newCompositeMemberMapOfProperties.get(compositeMemberPuName);
            Map newCompositeMemberProperties;
            if (compositeMemberProperties == null) {
                newCompositeMemberProperties = new HashMap();
            } else {
                // Don't alter user-supplied properties - create a copy instead
                newCompositeMemberProperties = new HashMap(compositeMemberProperties);
            }
            overrideMemberProperties(newCompositeMemberProperties, compositeProperties);
            newCompositeMemberProperties = mergeMaps(newCompositeMemberProperties, compositePuInfo.getProperties());
            translateOldProperties(newCompositeMemberProperties, session);
            newCompositeMemberMapOfProperties.put(compositeMemberPuName, newCompositeMemberProperties);
        }
        
        // set the new COMPOSITE_PROPERTIES into compositeProperties
        compositeProperties.put(PersistenceUnitProperties.COMPOSITE_UNIT_PROPERTIES, newCompositeMemberMapOfProperties);
    }
    
    /*
     * For required properties overrides values with those from composite properties. 
     */
    protected static void overrideMemberProperties(Map memberProperties, Map compositeProperties) {
        String transactionTypeProp =  (String)compositeProperties.get(PersistenceUnitProperties.TRANSACTION_TYPE);
        if (transactionTypeProp != null) {
            memberProperties.put(PersistenceUnitProperties.TRANSACTION_TYPE, transactionTypeProp);
        } else {
            memberProperties.remove(PersistenceUnitProperties.TRANSACTION_TYPE);
        }

        String serverPlatformProp =  (String)compositeProperties.get(PersistenceUnitProperties.TARGET_SERVER);
        if (serverPlatformProp != null) {
            memberProperties.put(PersistenceUnitProperties.TARGET_SERVER, serverPlatformProp);
        } else {
            memberProperties.remove(PersistenceUnitProperties.TARGET_SERVER);
        }

        Boolean isValidationOnly =  (Boolean)compositeProperties.get(PersistenceUnitProperties.VALIDATION_ONLY_PROPERTY);
        if (isValidationOnly != null) {
            memberProperties.put(PersistenceUnitProperties.VALIDATION_ONLY_PROPERTY, isValidationOnly);
        } else {
            memberProperties.remove(PersistenceUnitProperties.VALIDATION_ONLY_PROPERTY);
        }
    }
    
    /*
     * If a member is composite then add its members instead.
     * All members' puNames must be unique.
     * Return a Map of composite member SEPersistenceUnitInfo keyed by persistence unit name.
     */
    protected static Map<String, SEPersistenceUnitInfo> getCompositeMemberPuInfoMap(PersistenceUnitInfo puInfo, Map predeployProperties) {
        Set<SEPersistenceUnitInfo> memeberPuInfoSet = PersistenceUnitProcessor.getPersistenceUnits(puInfo.getClassLoader(), predeployProperties, puInfo.getJarFileUrls());
        HashMap<String, SEPersistenceUnitInfo> memberPuInfoMap = new HashMap(memeberPuInfoSet.size());        
        for (SEPersistenceUnitInfo memberPuInfo : memeberPuInfoSet) {
            // override transaction type with composite's transaction type
            memberPuInfo.setTransactionType(puInfo.getTransactionType());
            // override properties that should be overridden by composit's properties
            overrideMemberProperties(memberPuInfo.getProperties(), puInfo.getProperties());
            if (isComposite(memberPuInfo)) {
                Map<String, SEPersistenceUnitInfo> containedMemberPuInfoMap = getCompositeMemberPuInfoMap(memberPuInfo, memberPuInfo.getProperties());
                Iterator<Map.Entry<String, SEPersistenceUnitInfo>> it = containedMemberPuInfoMap.entrySet().iterator();
                while (it.hasNext()) {
                    Map.Entry<String, SEPersistenceUnitInfo> entry = it.next();
                    String containedMemberPuName = entry.getKey();
                    SEPersistenceUnitInfo containedMemberPuInfo = entry.getValue();
                    SEPersistenceUnitInfo anotherMemeberPuInfo = memberPuInfoMap.get(containedMemberPuName); 
                    if (anotherMemeberPuInfo == null) {
                        memberPuInfoMap.put(containedMemberPuName, containedMemberPuInfo);
                    } else {
                        throwPersistenceUnitNameAlreadyInUseException(containedMemberPuName, containedMemberPuInfo, anotherMemeberPuInfo);
                    }
                }
            } else {
                String memberPuName = memberPuInfo.getPersistenceUnitName();
                SEPersistenceUnitInfo anotherMemeberPuInfo = memberPuInfoMap.get(memberPuName); 
                if (anotherMemeberPuInfo == null) {
                    memberPuInfoMap.put(memberPuName, memberPuInfo);
                } else {
                    throwPersistenceUnitNameAlreadyInUseException(memberPuName, memberPuInfo, anotherMemeberPuInfo);
                }
            }
        }
        return memberPuInfoMap;
    }

    /*
     * If a member is composite then add its members instead.
     * All members' puNames must be unique.
     * Return a Set of composite member SEPersistenceUnitInfo.
     */
    protected static Set<SEPersistenceUnitInfo> getCompositeMemberPuInfoSet(PersistenceUnitInfo puInfo, Map predeployProperties) {
        return new HashSet(getCompositeMemberPuInfoMap(puInfo, predeployProperties).values());
    }
    
    public static void throwPersistenceUnitNameAlreadyInUseException(String puName, PersistenceUnitInfo newPuInfo, PersistenceUnitInfo exsitingPuInfo) { 
        String puUrl;
        String anotherPuUrl;
        try {
            puUrl = URLDecoder.decode(newPuInfo.getPersistenceUnitRootUrl().toString(), "UTF8");
            anotherPuUrl = URLDecoder.decode(exsitingPuInfo.getPersistenceUnitRootUrl().toString(), "UTF8");
        } catch (UnsupportedEncodingException e) {
            puUrl = newPuInfo.getPersistenceUnitRootUrl().toString();
            anotherPuUrl = exsitingPuInfo.getPersistenceUnitRootUrl().toString();
        }
        throw PersistenceUnitLoadingException.persistenceUnitNameAlreadyInUse(puName, puUrl, anotherPuUrl);
    }
    
    /**
     * Create a new version of this EntityManagerSetupImpl and cache it.  Prepare "this" EntityManagerSetupImpl
     * for garbage collection.
     * 
     * This call will mean any users of this EntityManagerSetupImpl will get the new version the next time 
     * they look it up (for instance and EntityManager creation time)
     * @param properties
     * @return
     */
    public EntityManagerSetupImpl refreshMetadata(Map properties){
        String sessionName = getSessionName();
        String uniqueName = getPersistenceUnitUniqueName();
        EntityManagerSetupImpl newSetupImpl = new EntityManagerSetupImpl(uniqueName, sessionName);
        newSetupImpl.setIsInContainerMode(isInContainerMode);
        newSetupImpl.enableWeaving = enableWeaving;
        Map refreshProperties = new HashMap();
        refreshProperties.putAll(getSession().getProperties());
        if (properties != null){
            refreshProperties.putAll(properties);
        }
        newSetupImpl.predeploy(getPersistenceUnitInfo(), refreshProperties);
        // newSetupImpl has been already predeployed, predeploy will just increment factoryCount.
        if (!isInContainerMode) {
            newSetupImpl.predeploy(getPersistenceUnitInfo(), refreshProperties);
        }
        synchronized (EntityManagerFactoryProvider.emSetupImpls) {
            SessionManager.getManager().getSessions().remove(sessionName, getSession());
            if (EntityManagerFactoryProvider.emSetupImpls.get(sessionName).equals(this)){
                EntityManagerFactoryProvider.emSetupImpls.remove(sessionName);
            }
            setIsMetadataExpired(true);
            //stop this EntityManagerSetupImpl's session from processing refresh commands. The new session should listen instead.
            getSession().setRefreshMetadataListener(null);
            
            EntityManagerFactoryProvider.addEntityManagerSetupImpl(sessionName, newSetupImpl);
        }
        return newSetupImpl;
    }

    /**
     * This method is just a wrapper on refreshMetadata so that core does not need a dependency on JPA 
     * due to the EntityManagerSetupImpl return value.  This method satisfies the MetedataRefreshListener implementation
     * and is called by incoming RCM refresh commands
     * 
     * @see refreshMetadata
     */
    public void triggerMetadataRefresh(Map properties){
        refreshMetadata(properties);
    }
    
    /**
     * INTERNAL:
     * Generate the DDL per the properties specified.
     */
    public void writeDDL(Map props, DatabaseSessionImpl session, ClassLoader classLoader) {
        if (this.compositeMemberEmSetupImpls == null) {
            // Generate the DDL if we find either EclipseLink or JPA DDL generation properties.
            // Note, we do one or the other, that is, we do not mix the properties by default
            // but we may use some with both, e.g. APP_LOCATION. EclipseLink properties
            // override JPA properties.
            
            if (hasConfigProperty(DDL_GENERATION, props)) {
                // We have EclipseLink DDL generation properties.
                String ddlGeneration = getConfigPropertyAsString(DDL_GENERATION, props).toLowerCase();
                
                if (! ddlGeneration.equals(NONE)) {
                    writeDDL(ddlGeneration, props, session, classLoader);
                }
            } else {
                // Look for JPA schema generation properties.
                
                // Schema generation for the database.
                if (hasConfigProperty(SCHEMA_GENERATION_DATABASE_ACTION, props)) {
                    String databaseGenerationAction = getConfigPropertyAsString(SCHEMA_GENERATION_DATABASE_ACTION, props).toLowerCase();
                
                    if (! databaseGenerationAction.equals(SCHEMA_GENERATION_NONE_ACTION)) {
                        if (databaseGenerationAction.equals(SCHEMA_GENERATION_CREATE_ACTION)) {
                            writeDDL(SCHEMA_GENERATION_CREATE_SOURCE, SCHEMA_GENERATION_CREATE_SCRIPT_SOURCE, TableCreationType.CREATE, props, session, classLoader);
                        } else if (databaseGenerationAction.equals(PersistenceUnitProperties.CREATE_OR_EXTEND)) {
                            writeDDL(SCHEMA_GENERATION_CREATE_SOURCE, SCHEMA_GENERATION_CREATE_SCRIPT_SOURCE, TableCreationType.EXTEND, props, session, classLoader);
                        } else if (databaseGenerationAction.equals(SCHEMA_GENERATION_DROP_ACTION)) {
                            writeDDL(SCHEMA_GENERATION_DROP_SOURCE, SCHEMA_GENERATION_DROP_SCRIPT_SOURCE, TableCreationType.DROP, props, session, classLoader);                        
                        } else if (databaseGenerationAction.equals(SCHEMA_GENERATION_DROP_AND_CREATE_ACTION)) {
                            writeDDL(SCHEMA_GENERATION_DROP_SOURCE, SCHEMA_GENERATION_DROP_SCRIPT_SOURCE, TableCreationType.DROP, props, session, classLoader);
                            writeDDL(SCHEMA_GENERATION_CREATE_SOURCE, SCHEMA_GENERATION_CREATE_SCRIPT_SOURCE, TableCreationType.CREATE, props, session, classLoader);
                        } else {
                            String validOptions = SCHEMA_GENERATION_CREATE_ACTION + ", " + SCHEMA_GENERATION_DROP_ACTION + ", " +  SCHEMA_GENERATION_DROP_AND_CREATE_ACTION + ", " + PersistenceUnitProperties.CREATE_OR_EXTEND;
                            session.log(SessionLog.WARNING, SessionLog.PROPERTIES, "ddl_generation_unknown_property_value", new Object[] {SCHEMA_GENERATION_DATABASE_ACTION, databaseGenerationAction, persistenceUnitInfo.getPersistenceUnitName(), validOptions});
                        }
                    }
                }
                
                // Schema generation for target scripts.
                if (hasConfigProperty(SCHEMA_GENERATION_SCRIPTS_ACTION, props)) {
                    String scriptsGenerationAction = getConfigPropertyAsString(SCHEMA_GENERATION_SCRIPTS_ACTION, props).toLowerCase();

                    if (! scriptsGenerationAction.equals(SCHEMA_GENERATION_NONE_ACTION)) {
                        if (scriptsGenerationAction.equals(SCHEMA_GENERATION_CREATE_ACTION)) {
                            writeMetadataDDLToScript(TableCreationType.CREATE, props, session, classLoader);
                        } else if (scriptsGenerationAction.equals(PersistenceUnitProperties.CREATE_OR_EXTEND)) {
                            writeMetadataDDLToScript(TableCreationType.EXTEND, props, session, classLoader);
                        } else if (scriptsGenerationAction.equals(SCHEMA_GENERATION_DROP_ACTION)) {
                            writeMetadataDDLToScript(TableCreationType.DROP, props, session, classLoader);
                        } else if (scriptsGenerationAction.equals(SCHEMA_GENERATION_DROP_AND_CREATE_ACTION)) {
                            writeMetadataDDLToScript(TableCreationType.DROP_AND_CREATE, props, session, classLoader);
                        } else {
                            String validOptions = SCHEMA_GENERATION_CREATE_ACTION + ", " + SCHEMA_GENERATION_DROP_ACTION + ", " +  SCHEMA_GENERATION_DROP_AND_CREATE_ACTION;
                            session.log(SessionLog.WARNING, SessionLog.PROPERTIES, "ddl_generation_unknown_property_value", new Object[] {SCHEMA_GENERATION_SCRIPTS_ACTION, scriptsGenerationAction, persistenceUnitInfo.getPersistenceUnitName(), validOptions});
                        }
                    }
                }
                
                // Once we've generated any and all DDL, check for load scripts.
                writeSourceScriptToDatabase(getConfigProperty(SCHEMA_GENERATION_SQL_LOAD_SCRIPT_SOURCE, props), session, classLoader);
            }
        } else {
            // composite
            Map compositeMemberMapOfProperties = (Map)getConfigProperty(PersistenceUnitProperties.COMPOSITE_UNIT_PROPERTIES, props);
            for(EntityManagerSetupImpl compositeMemberEmSetupImpl : this.compositeMemberEmSetupImpls) {
                // the properties guaranteed to be non-null after updateCompositeMemberProperties call
                Map compositeMemberProperties = (Map)compositeMemberMapOfProperties.get(compositeMemberEmSetupImpl.getPersistenceUnitInfo().getPersistenceUnitName());
                compositeMemberEmSetupImpl.writeDDL(compositeMemberProperties, session, classLoader);
            }
        }
    }
    
    /**
     * INTERNAL:
     * Generate the DDL from the persistence unit metadata. This DDL generation
     * utilizes the EclipseLink DDL properties.
     */
    protected void writeDDL(String ddlGeneration, Map props, DatabaseSessionImpl session, ClassLoader classLoader) {
        // By default the table creation type will be 'none'.
        TableCreationType ddlType = TableCreationType.NONE;

        if (ddlGeneration.equals(PersistenceUnitProperties.CREATE_ONLY)) {
            ddlType = TableCreationType.CREATE;
        } else if (ddlGeneration.equals(PersistenceUnitProperties.DROP_ONLY)) {
            ddlType = TableCreationType.DROP;
        } else if (ddlGeneration.equals(PersistenceUnitProperties.DROP_AND_CREATE)) {
            ddlType = TableCreationType.DROP_AND_CREATE;
        } else if (ddlGeneration.equals(PersistenceUnitProperties.CREATE_OR_EXTEND)) {
            ddlType = TableCreationType.EXTEND;
        } else {
            // Log a warning if we have an unknown ddl generation.
            String validOptions = PersistenceUnitProperties.NONE + ", " + 
                                  PersistenceUnitProperties.CREATE_ONLY + ", " + 
                                  PersistenceUnitProperties.DROP_ONLY + ", " + 
                                  PersistenceUnitProperties.DROP_AND_CREATE + ", " + 
                                  PersistenceUnitProperties.CREATE_OR_EXTEND;
            session.log(SessionLog.WARNING, SessionLog.PROPERTIES, "ddl_generation_unknown_property_value", new Object[] {PersistenceUnitProperties.DDL_GENERATION, ddlGeneration, persistenceUnitInfo.getPersistenceUnitName(), validOptions});
        }

        if (ddlType != TableCreationType.NONE) {
            String ddlGenerationMode = getConfigPropertyAsString(PersistenceUnitProperties.DDL_GENERATION_MODE, props, PersistenceUnitProperties.DEFAULT_DDL_GENERATION_MODE);
    
            // Optimize for cases where the value is explicitly set to NONE 
            if (! ddlGenerationMode.equals(NONE)) {
                if (isCompositeMember()) {
                    // debug output added to make it easier to navigate the log because the method is called outside of composite member deploy
                    session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "composite_member_begin_call", new Object[]{"generateDDL", persistenceUnitInfo.getPersistenceUnitName(), state});
                }
                
                SchemaManager mgr = new SchemaManager(session);
                
                if (ddlGenerationMode.equals(PersistenceUnitProperties.DDL_DATABASE_GENERATION) || ddlGenerationMode.equals(PersistenceUnitProperties.DDL_BOTH_GENERATION)) {
                    writeDDLToDatabase(mgr, ddlType);
                }

                if (ddlGenerationMode.equals(PersistenceUnitProperties.DDL_SQL_SCRIPT_GENERATION)|| ddlGenerationMode.equals(PersistenceUnitProperties.DDL_BOTH_GENERATION)) {
                    String appLocation = getConfigPropertyAsString(PersistenceUnitProperties.APP_LOCATION, props, PersistenceUnitProperties.DEFAULT_APP_LOCATION);
                    
                    // These could be a string (file name urls) or actual writers.
                    Object createDDLJdbc = getConfigProperty(PersistenceUnitProperties.CREATE_JDBC_DDL_FILE, props, PersistenceUnitProperties.DEFAULT_CREATE_JDBC_FILE_NAME);
                    Object dropDDLJdbc = getConfigProperty(PersistenceUnitProperties.DROP_JDBC_DDL_FILE, props, PersistenceUnitProperties.DEFAULT_DROP_JDBC_FILE_NAME);
                    writeDDLToFiles(mgr, appLocation,  createDDLJdbc,  dropDDLJdbc, ddlType, props);                
                }
    
                // Log a warning if we have an unknown ddl generation mode. 
                if ( (! ddlGenerationMode.equals(PersistenceUnitProperties.DDL_DATABASE_GENERATION)) && (! ddlGenerationMode.equals(PersistenceUnitProperties.DDL_SQL_SCRIPT_GENERATION)) && (! ddlGenerationMode.equals(PersistenceUnitProperties.DDL_BOTH_GENERATION))) {
                    String validOptions = PersistenceUnitProperties.DDL_DATABASE_GENERATION + ", " + 
                                          PersistenceUnitProperties.DDL_SQL_SCRIPT_GENERATION + ", " + 
                                          PersistenceUnitProperties.DDL_BOTH_GENERATION;
                    session.log(SessionLog.WARNING, SessionLog.PROPERTIES, "ddl_generation_unknown_property_value", new Object[] {PersistenceUnitProperties.DDL_GENERATION_MODE, ddlGenerationMode, persistenceUnitInfo.getPersistenceUnitName(), validOptions});
                }
                
                if (isCompositeMember()) {
                    // debug output added to make it easier to navigate the log because the method is called outside of composite member deploy
                    session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "composite_member_end_call", new Object[]{"generateDDL", persistenceUnitInfo.getPersistenceUnitName(), state});
                }
            }
        }
    }
    
    /**
     * INTERNAL:
     * Generate the DDL per the properties given.
     */
    protected void writeDDL(String generationSourceProperty, String scriptGenerationSourceProperty, TableCreationType tableCreationType, Map props, DatabaseSessionImpl session, ClassLoader loader) {
        String generationSource = getConfigPropertyAsString(generationSourceProperty, props);
        Object scriptGenerationSource = getConfigProperty(scriptGenerationSourceProperty, props);
        
        if (generationSource == null) {
            if (scriptGenerationSource == null) {
                writeMetadataDDLToDatabase(tableCreationType, props, session, loader);
            } else {
                writeSourceScriptToDatabase(scriptGenerationSource, session, loader);
            }
        } else if (generationSource.equals(SCHEMA_GENERATION_METADATA_SOURCE)) {
            writeMetadataDDLToDatabase(tableCreationType, props, session, loader);
        } else if (generationSource.equals(SCHEMA_GENERATION_SCRIPT_SOURCE)) {
            writeSourceScriptToDatabase(scriptGenerationSource, session, loader);
        } else if (generationSource.equals(SCHEMA_GENERATION_METADATA_THEN_SCRIPT_SOURCE)) {
            writeMetadataDDLToDatabase(tableCreationType, props, session, loader);
            writeSourceScriptToDatabase(scriptGenerationSource, session, loader);
        } else if (generationSource.equals(SCHEMA_GENERATION_SCRIPT_THEN_METADATA_SOURCE)) {
            writeSourceScriptToDatabase(scriptGenerationSource, session, loader);
            writeMetadataDDLToDatabase(tableCreationType, props, session, loader);
        } else {
            String validOptions = SCHEMA_GENERATION_METADATA_SOURCE + ", " + 
                                  SCHEMA_GENERATION_SCRIPT_SOURCE + ", " + 
                                  SCHEMA_GENERATION_METADATA_THEN_SCRIPT_SOURCE + ", " + 
                                  SCHEMA_GENERATION_SCRIPT_THEN_METADATA_SOURCE;
            session.log(SessionLog.WARNING, SessionLog.PROPERTIES, "ddl_generation_unknown_property_value", new Object[] {generationSourceProperty, generationSource, persistenceUnitInfo.getPersistenceUnitName(), validOptions});
        }
    }
    
    /**
     * INTERNAL:
     * Generate and write DDL from the persistence unit metadata to the database.
     */
    protected void writeDDLToDatabase(SchemaManager mgr, TableCreationType ddlType) {
        String str = getConfigPropertyAsString(PersistenceUnitProperties.JAVASE_DB_INTERACTION, null ,"true");
        boolean interactWithDB = Boolean.valueOf(str.toLowerCase()).booleanValue();
        if (!interactWithDB){
            return;
        }
        
        generateDefaultTables(mgr, ddlType);
    }

    /**
     * @deprecated Extenders should now use
     *             {@link #writeDDLToFiles(SchemaManager, String, Object, Object, TableCreationType, Map)}
     */
    protected void writeDDLToFiles(SchemaManager mgr, String appLocation, Object createDDLJdbc, Object dropDDLJdbc,
            TableCreationType ddlType) {
        writeDDLToFiles(mgr, appLocation, createDDLJdbc, dropDDLJdbc, ddlType, Collections.EMPTY_MAP);
    }

    /**
     * Write the DDL to the files provided.
     */
    protected void writeDDLToFiles(SchemaManager mgr, String appLocation, Object createDDLJdbc, Object dropDDLJdbc, TableCreationType ddlType, Map props) {
        // Ensure that the appLocation string ends with File.separator if 
        // specified. In JPA there is no default for app location, however, if 
        // the user did specify one, we'll use it.
        String appLocationPrefix = (appLocation == null) ? "" : addFileSeperator(appLocation); 
        
        if (ddlType.equals(TableCreationType.CREATE) || ddlType.equals(TableCreationType.DROP_AND_CREATE) || ddlType.equals(TableCreationType.EXTEND)) {
            if (createDDLJdbc == null) {
                // Using EclipseLink properties, the create script has a default.
                // Using JPA properties, the user must specify the target else an exception must be thrown.
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("jpa21-ddl-create-script-target-not-specified"));
            } else if (createDDLJdbc instanceof Writer) {
                mgr.outputCreateDDLToWriter((Writer) createDDLJdbc);
            } else {
                // Assume it is a String file name.
                mgr.outputCreateDDLToFile(appLocationPrefix + createDDLJdbc);
            }
        }

        if (ddlType.equals(TableCreationType.DROP) || ddlType.equals(TableCreationType.DROP_AND_CREATE)) {
            if (dropDDLJdbc == null) {
                // Using EclipseLink properties, the drop script has a default.
                // Using JPA properties, the user must specify the target else an exception must be thrown.
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("jpa21-ddl-drop-script-target-not-specified"));
            } else if (dropDDLJdbc instanceof Writer) {
                mgr.outputDropDDLToWriter((Writer) dropDDLJdbc);
            } else if (dropDDLJdbc instanceof String) {
                mgr.outputDropDDLToFile(appLocationPrefix + dropDDLJdbc);
            } else {
                throw new PersistenceException(ExceptionLocalization.buildMessage("jpa21-ddl-invalid-target-script-type", new Object[]{ dropDDLJdbc , dropDDLJdbc.getClass()}));
            }
        }

        String propString = getConfigPropertyAsString(PersistenceUnitProperties.SCHEMA_GENERATION_SCRIPT_TERMINATE_STATEMENTS, props);
        boolean terminator = Boolean.parseBoolean(propString);
        mgr.setCreateSQLFiles(terminator);
        generateDefaultTables(mgr, ddlType);
        mgr.closeDDLWriter();
    }
    
    /**
     * INTERNAL:
     * Generate and write DDL from the persistence unit metadata to the database.
     */
    protected void writeMetadataDDLToDatabase(TableCreationType tableCreationType, Map props, DatabaseSessionImpl session, ClassLoader classLoader) {
        SchemaManager mgr = new SchemaManager(session);
                
        // Set the create database schemas flag on the schema manager.
        String createSchemas = getConfigPropertyAsString(SCHEMA_GENERATION_CREATE_DATABASE_SCHEMAS, props);
        mgr.setCreateDatabaseSchemas(createSchemas != null && createSchemas.equalsIgnoreCase("true"));
                
        writeDDLToDatabase(mgr, tableCreationType);
    }
    
    /**
     * INTERNAL:
     * Generate and write DDL from the persistence unit metadata to scripts.
     */
    protected void writeMetadataDDLToScript(TableCreationType tableCreationType, Map props, DatabaseSessionImpl session, ClassLoader classLoader) {
        SchemaManager mgr = new SchemaManager(session);
                
        // Set the create database schemas flag on the schema manager.
        String createSchemas = getConfigPropertyAsString(SCHEMA_GENERATION_CREATE_DATABASE_SCHEMAS, props);
        mgr.setCreateDatabaseSchemas(createSchemas != null && createSchemas.equalsIgnoreCase("true"));

        writeDDLToFiles(mgr, getConfigPropertyAsString(PersistenceUnitProperties.APP_LOCATION, props),  getConfigProperty(SCHEMA_GENERATION_SCRIPTS_CREATE_TARGET, props),  getConfigProperty(SCHEMA_GENERATION_SCRIPTS_DROP_TARGET, props), tableCreationType, props);
    }
    
    /**
     * This method will read SQL from a reader or URL and send it through
     * to the database. This could open up the database to SQL injection if
     * not careful.
     */
    protected void writeSourceScriptToDatabase(Object source, DatabaseSessionImpl session, ClassLoader loader) {
        if (source != null) {
            Reader reader = null;
            
            try {
                if (source instanceof Reader) {
                    reader = (Reader) source;
                } else if (source instanceof String) {
                    // Try to load the resource first, if not assume it as a well formed URL. If not, throw an exception. 
                    URL sourceURL = loader.getResource((String) source);
                    
                    if (sourceURL == null) {
                        try {
                            sourceURL = new URL((String) source);
                        }  catch (MalformedURLException e) {
                            throw new PersistenceException(ExceptionLocalization.buildMessage("jpa21-ddl-source-script-not-found", new Object[]{ source }), e);
                        }
                    }
                    
                    URLConnection connection = sourceURL.openConnection();
                    // Set to false to prevent locking of jar files on Windows. EclipseLink issue 249664
                    connection.setUseCaches(false);
                    reader = new InputStreamReader(connection.getInputStream(), "UTF-8");
                } else {
                    throw new PersistenceException(ExceptionLocalization.buildMessage("jpa21-ddl-invalid-source-script-type", new Object[]{ source , source.getClass()}));
                }
            
                if (reader != null) {
                    StringBuffer sqlBuffer;
                    int data = reader.read();
                    
                    // While there is still data to read, read line by line.
                    while (data != -1) {
                        sqlBuffer = new StringBuffer();
                        char aChar = (char) data;
        
                        // Read till the end of the line or maybe even file.
                        while (aChar != '\n' && data != -1) {
                            sqlBuffer.append(aChar);
                                
                            // Read next character.
                            data = reader.read();
                            aChar = (char) data;
                        }

                        // Remove trailing and leading white space characters.
                        String sqlString = sqlBuffer.toString().trim();
                        
                        // If the string isn't empty, then fire it.
                        if ((! sqlString.equals("")) && (! sqlString.startsWith("#"))) {
                            try {
                                session.executeNonSelectingSQL(sqlString);
                            } catch (DatabaseException e) {
                                // Swallow any database exceptions as these could 
                                // be drop failures of a table that doesn't exist etc.
                                // SQLExceptions will be thrown to the user.
                            }
                        }
                            
                        data = reader.read();
                    }
        
                    reader.close();
                }
            } catch (IOException e) {
                throw new PersistenceException(ExceptionLocalization.buildMessage("jpa21-ddl-source-script-io-exception", new Object[]{source}), e);
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException e) {
                        // ignore.
                    }
                }
            }
        }
    }
}

