org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.jpa.boot.internal;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import javax.persistence.AttributeConverter;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceException;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.sql.DataSource;

import org.hibernate.SessionFactory;
import org.hibernate.SessionFactoryObserver;
import org.hibernate.boot.CacheRegionDefinition;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.archive.scan.internal.StandardScanOptions;
import org.hibernate.boot.cfgxml.spi.CfgXmlAccessService;
import org.hibernate.boot.cfgxml.spi.LoadedConfig;
import org.hibernate.boot.cfgxml.spi.MappingReference;
import org.hibernate.boot.model.process.spi.ManagedResources;
import org.hibernate.boot.model.process.spi.MetadataBuildingProcess;
import org.hibernate.boot.registry.BootstrapServiceRegistry;
import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.registry.classloading.internal.TcclLookupPrecedence;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.selector.StrategyRegistrationProvider;
import org.hibernate.boot.registry.selector.spi.StrategySelector;
import org.hibernate.boot.spi.MetadataBuilderContributor;
import org.hibernate.boot.spi.MetadataBuilderImplementor;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.boot.spi.SessionFactoryBuilderImplementor;
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.UnloadedClass;
import org.hibernate.bytecode.enhance.spi.UnloadedField;
import org.hibernate.cfg.AttributeConverterDefinition;
import org.hibernate.cfg.Environment;
import org.hibernate.cfg.beanvalidation.BeanValidationIntegrator;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.id.factory.spi.MutableIdentifierGeneratorFactory;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.internal.EntityManagerMessageLogger;
import org.hibernate.internal.util.NullnessHelper;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.jpa.AvailableSettings;
import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder;
import org.hibernate.jpa.boot.spi.IntegratorProvider;
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
import org.hibernate.jpa.boot.spi.StrategyRegistrationProviderList;
import org.hibernate.jpa.boot.spi.TypeContributorList;
import org.hibernate.jpa.internal.util.LogHelper;
import org.hibernate.jpa.internal.util.PersistenceUnitTransactionTypeHelper;
import org.hibernate.jpa.spi.IdentifierGeneratorStrategyProvider;
import org.hibernate.proxy.EntityNotFoundDelegate;
import org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl;
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl;
import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder;
import org.hibernate.secure.spi.GrantedPermission;
import org.hibernate.secure.spi.JaccPermissionDeclarations;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.tool.schema.spi.DelayedDropRegistryNotAvailableImpl;
import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator;

import org.jboss.jandex.Index;

import static org.hibernate.cfg.AvailableSettings.DATASOURCE;
import static org.hibernate.cfg.AvailableSettings.DRIVER;
import static org.hibernate.cfg.AvailableSettings.JACC_CONTEXT_ID;
import static org.hibernate.cfg.AvailableSettings.JACC_ENABLED;
import static org.hibernate.cfg.AvailableSettings.JACC_PREFIX;
import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_DRIVER;
import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_PASSWORD;
import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_URL;
import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_USER;
import static org.hibernate.cfg.AvailableSettings.JPA_JTA_DATASOURCE;
import static org.hibernate.cfg.AvailableSettings.JPA_NON_JTA_DATASOURCE;
import static org.hibernate.cfg.AvailableSettings.JPA_SHARED_CACHE_MODE;
import static org.hibernate.cfg.AvailableSettings.JPA_TRANSACTION_TYPE;
import static org.hibernate.cfg.AvailableSettings.JPA_VALIDATION_MODE;
import static org.hibernate.cfg.AvailableSettings.PASS;
import static org.hibernate.cfg.AvailableSettings.SESSION_FACTORY_NAME;
import static org.hibernate.cfg.AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY;
import static org.hibernate.cfg.AvailableSettings.URL;
import static org.hibernate.cfg.AvailableSettings.USER;
import static org.hibernate.internal.HEMLogging.messageLogger;
import static org.hibernate.jpa.AvailableSettings.CFG_FILE;
import static org.hibernate.jpa.AvailableSettings.CLASS_CACHE_PREFIX;
import static org.hibernate.jpa.AvailableSettings.COLLECTION_CACHE_PREFIX;
import static org.hibernate.jpa.AvailableSettings.PERSISTENCE_UNIT_NAME;

/**
 * @author Steve Ebersole
 */
public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuilder {
    private static final EntityManagerMessageLogger LOG = messageLogger(EntityManagerFactoryBuilderImpl.class);

    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // New settings

    /**
     * Names a {@link IntegratorProvider}
     */
    public static final String INTEGRATOR_PROVIDER = "hibernate.integrator_provider";

    /**
     * Names a {@link StrategyRegistrationProviderList}
     */
    public static final String STRATEGY_REGISTRATION_PROVIDERS = "hibernate.strategy_registration_provider";

    /**
     * Names a {@link TypeContributorList}
     */
    public static final String TYPE_CONTRIBUTORS = "hibernate.type_contributors";

    /**
     * Names a {@link MetadataBuilderImplementor}
     */
    public static final String METADATA_BUILDER_CONTRIBUTOR = "hibernate.metadata_builder_contributor";

    /**
     * Names a Jandex {@link Index} instance to use.
     */
    public static final String JANDEX_INDEX = "hibernate.jandex_index";

    private final PersistenceUnitDescriptor persistenceUnit;

    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // things built in first phase, needed for second phase..
    private final Map configurationValues;
    private final StandardServiceRegistry standardServiceRegistry;
    private final ManagedResources managedResources;
    private final MetadataBuilderImplementor metamodelBuilder;

    private static class JpaEntityNotFoundDelegate implements EntityNotFoundDelegate, Serializable {
        /**
         * Singleton access
         */
        public static final JpaEntityNotFoundDelegate INSTANCE = new JpaEntityNotFoundDelegate();

        public void handleEntityNotFound(String entityName, Serializable id) {
            throw new EntityNotFoundException("Unable to find " + entityName + " with id " + id);
        }
    }

    public EntityManagerFactoryBuilderImpl(PersistenceUnitDescriptor persistenceUnit, Map integrationSettings) {
        this(persistenceUnit, integrationSettings, null, null);
    }

    public EntityManagerFactoryBuilderImpl(PersistenceUnitDescriptor persistenceUnit, Map integrationSettings,
            ClassLoader providedClassLoader) {
        this(persistenceUnit, integrationSettings, providedClassLoader, null);
    }

    public EntityManagerFactoryBuilderImpl(PersistenceUnitDescriptor persistenceUnit, Map integrationSettings,
            ClassLoaderService providedClassLoaderService) {
        this(persistenceUnit, integrationSettings, null, providedClassLoaderService);
    }

    private EntityManagerFactoryBuilderImpl(PersistenceUnitDescriptor persistenceUnit, Map integrationSettings,
            ClassLoader providedClassLoader, ClassLoaderService providedClassLoaderService) {

        LogHelper.logPersistenceUnitInformation(persistenceUnit);

        this.persistenceUnit = persistenceUnit;

        if (integrationSettings == null) {
            integrationSettings = Collections.emptyMap();
        }

        // Build the boot-strap service registry, which mainly handles class loader interactions
        final BootstrapServiceRegistry bsr = buildBootstrapServiceRegistry(integrationSettings, providedClassLoader,
                providedClassLoaderService);

        // merge configuration sources and build the "standard" service registry
        final StandardServiceRegistryBuilder ssrBuilder = StandardServiceRegistryBuilder.forJpa(bsr);

        final MergedSettings mergedSettings = mergeSettings(persistenceUnit, integrationSettings, ssrBuilder);

        // flush before completion validation
        if ("true".equals(mergedSettings.configurationValues.get(Environment.FLUSH_BEFORE_COMPLETION))) {
            LOG.definingFlushBeforeCompletionIgnoredInHem(Environment.FLUSH_BEFORE_COMPLETION);
            mergedSettings.configurationValues.put(Environment.FLUSH_BEFORE_COMPLETION, "false");
        }

        // keep the merged config values for phase-2
        this.configurationValues = mergedSettings.getConfigurationValues();

        // Build the "standard" service registry
        ssrBuilder.applySettings(configurationValues);

        this.standardServiceRegistry = ssrBuilder.build();

        configureIdentifierGenerators(standardServiceRegistry);

        final MetadataSources metadataSources = new MetadataSources(bsr);
        List<AttributeConverterDefinition> attributeConverterDefinitions = applyMappingResources(metadataSources);

        this.metamodelBuilder = (MetadataBuilderImplementor) metadataSources
                .getMetadataBuilder(standardServiceRegistry);
        applyMetamodelBuilderSettings(mergedSettings, attributeConverterDefinitions);

        applyMetadataBuilderContributor();

        // todo : would be nice to have MetadataBuilder still do the handling of CfgXmlAccessService here
        //      another option is to immediately handle them here (probably in mergeSettings?) as we encounter them...
        final CfgXmlAccessService cfgXmlAccessService = standardServiceRegistry
                .getService(CfgXmlAccessService.class);
        if (cfgXmlAccessService.getAggregatedConfig() != null) {
            if (cfgXmlAccessService.getAggregatedConfig().getMappingReferences() != null) {
                for (MappingReference mappingReference : cfgXmlAccessService.getAggregatedConfig()
                        .getMappingReferences()) {
                    mappingReference.apply(metadataSources);
                }
            }
        }

        this.managedResources = MetadataBuildingProcess.prepare(metadataSources,
                metamodelBuilder.getBootstrapContext());

        withValidatorFactory(configurationValues.get(org.hibernate.cfg.AvailableSettings.JPA_VALIDATION_FACTORY));

        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        // push back class transformation to the environment; for the time being this only has any effect in EE
        // container situations, calling back into PersistenceUnitInfo#addClassTransformer

        final boolean dirtyTrackingEnabled = readBooleanConfigurationValue(
                AvailableSettings.ENHANCER_ENABLE_DIRTY_TRACKING);
        final boolean lazyInitializationEnabled = readBooleanConfigurationValue(
                AvailableSettings.ENHANCER_ENABLE_LAZY_INITIALIZATION);
        final boolean associationManagementEnabled = readBooleanConfigurationValue(
                AvailableSettings.ENHANCER_ENABLE_ASSOCIATION_MANAGEMENT);

        if (dirtyTrackingEnabled || lazyInitializationEnabled || associationManagementEnabled) {
            EnhancementContext enhancementContext = getEnhancementContext(dirtyTrackingEnabled,
                    lazyInitializationEnabled, associationManagementEnabled);

            persistenceUnit.pushClassTransformer(enhancementContext);
        }

        // for the time being we want to revoke access to the temp ClassLoader if one was passed
        metamodelBuilder.applyTempClassLoader(null);
    }

    private void applyMetadataBuilderContributor() {

        Object metadataBuilderContributorSetting = configurationValues.get(METADATA_BUILDER_CONTRIBUTOR);

        if (metadataBuilderContributorSetting == null) {
            return;
        }

        MetadataBuilderContributor metadataBuilderContributor = loadSettingInstance(METADATA_BUILDER_CONTRIBUTOR,
                metadataBuilderContributorSetting, MetadataBuilderContributor.class);

        if (metadataBuilderContributor != null) {
            metadataBuilderContributor.contribute(metamodelBuilder);
        }
    }

    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // temporary!
    public Map getConfigurationValues() {
        return Collections.unmodifiableMap(configurationValues);
    }
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    private boolean readBooleanConfigurationValue(String propertyName) {
        Object propertyValue = configurationValues.remove(propertyName);
        return propertyValue != null && Boolean.parseBoolean(propertyValue.toString());
    }

    /**
     * Builds the context to be used in runtime bytecode enhancement
     *
     * @param dirtyTrackingEnabled To enable dirty tracking feature
     * @param lazyInitializationEnabled To enable lazy initialization feature
     * @param associationManagementEnabled To enable association management feature
     * @return An enhancement context for classes managed by this EM
     */
    protected EnhancementContext getEnhancementContext(final boolean dirtyTrackingEnabled,
            final boolean lazyInitializationEnabled, final boolean associationManagementEnabled) {
        return new DefaultEnhancementContext() {

            @Override
            public boolean isEntityClass(UnloadedClass classDescriptor) {
                return managedResources.getAnnotatedClassNames().contains(classDescriptor.getName())
                        && super.isEntityClass(classDescriptor);
            }

            @Override
            public boolean isCompositeClass(UnloadedClass classDescriptor) {
                return managedResources.getAnnotatedClassNames().contains(classDescriptor.getName())
                        && super.isCompositeClass(classDescriptor);
            }

            @Override
            public boolean doBiDirectionalAssociationManagement(UnloadedField field) {
                return associationManagementEnabled;
            }

            @Override
            public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) {
                return dirtyTrackingEnabled;
            }

            @Override
            public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) {
                return lazyInitializationEnabled;
            }

            @Override
            public boolean isLazyLoadable(UnloadedField field) {
                return lazyInitializationEnabled;
            }

            @Override
            public boolean doExtendedEnhancement(UnloadedClass classDescriptor) {
                // doesn't make any sense to have extended enhancement enabled at runtime. we only enhance entities anyway.
                return false;
            }

        };
    }

    /**
     * Builds the {@link BootstrapServiceRegistry} used to eventually build the {@link org.hibernate.boot.registry.StandardServiceRegistryBuilder}; mainly
     * used here during instantiation to define class-loading behavior.
     *
     * @param integrationSettings Any integration settings passed by the EE container or SE application
     *
     * @return The built BootstrapServiceRegistry
     */
    private BootstrapServiceRegistry buildBootstrapServiceRegistry(Map integrationSettings,
            ClassLoader providedClassLoader, ClassLoaderService providedClassLoaderService) {
        final BootstrapServiceRegistryBuilder bsrBuilder = new BootstrapServiceRegistryBuilder();

        applyIntegrationProvider(integrationSettings, bsrBuilder);

        final StrategyRegistrationProviderList strategyRegistrationProviderList = (StrategyRegistrationProviderList) integrationSettings
                .get(STRATEGY_REGISTRATION_PROVIDERS);
        if (strategyRegistrationProviderList != null) {
            for (StrategyRegistrationProvider strategyRegistrationProvider : strategyRegistrationProviderList
                    .getStrategyRegistrationProviders()) {
                bsrBuilder.applyStrategySelectors(strategyRegistrationProvider);
            }
        }

        // ClassLoaders ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        // NOTE: See BootstrapServiceRegistryBuilder#build.  providedClassLoaderService and providedClassLoaders are
        // mutually exclusive concepts, with priority given to the former

        if (providedClassLoaderService != null) {
            bsrBuilder.applyClassLoaderService(providedClassLoaderService);
        } else {
            if (persistenceUnit.getClassLoader() != null) {
                bsrBuilder.applyClassLoader(persistenceUnit.getClassLoader());
            }

            if (providedClassLoader != null) {
                bsrBuilder.applyClassLoader(providedClassLoader);
            }

            final ClassLoader appClassLoader = (ClassLoader) integrationSettings
                    .get(org.hibernate.cfg.AvailableSettings.APP_CLASSLOADER);
            if (appClassLoader != null) {
                LOG.debugf("Found use of deprecated `%s` setting; use `%s` instead.",
                        org.hibernate.cfg.AvailableSettings.APP_CLASSLOADER,
                        org.hibernate.cfg.AvailableSettings.CLASSLOADERS);
            }
            final Object classLoadersSetting = integrationSettings
                    .get(org.hibernate.cfg.AvailableSettings.CLASSLOADERS);
            if (classLoadersSetting != null) {
                if (java.util.Collection.class.isInstance(classLoadersSetting)) {
                    for (ClassLoader classLoader : (java.util.Collection<ClassLoader>) classLoadersSetting) {
                        bsrBuilder.applyClassLoader(classLoader);
                    }
                } else if (classLoadersSetting.getClass().isArray()) {
                    for (ClassLoader classLoader : (ClassLoader[]) classLoadersSetting) {
                        bsrBuilder.applyClassLoader(classLoader);
                    }
                } else if (ClassLoader.class.isInstance(classLoadersSetting)) {
                    bsrBuilder.applyClassLoader((ClassLoader) classLoadersSetting);
                }
            }

            //configurationValues not assigned yet, using directly the properties of the PU
            Properties puProperties = persistenceUnit.getProperties();
            if (puProperties != null) {
                final String tcclLookupPrecedence = puProperties
                        .getProperty(org.hibernate.cfg.AvailableSettings.TC_CLASSLOADER);
                if (tcclLookupPrecedence != null) {
                    bsrBuilder.applyTcclLookupPrecedence(
                            TcclLookupPrecedence.valueOf(tcclLookupPrecedence.toUpperCase(Locale.ROOT)));
                }
            }
        }

        return bsrBuilder.build();
    }

    private void applyIntegrationProvider(Map integrationSettings, BootstrapServiceRegistryBuilder bsrBuilder) {
        Object integrationSetting = integrationSettings.get(INTEGRATOR_PROVIDER);
        if (integrationSetting == null) {
            return;
        }
        final IntegratorProvider integratorProvider = loadSettingInstance(INTEGRATOR_PROVIDER, integrationSetting,
                IntegratorProvider.class);

        if (integratorProvider != null) {
            for (Integrator integrator : integratorProvider.getIntegrators()) {
                bsrBuilder.applyIntegrator(integrator);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private MergedSettings mergeSettings(PersistenceUnitDescriptor persistenceUnit, Map<?, ?> integrationSettings,
            StandardServiceRegistryBuilder ssrBuilder) {
        final MergedSettings mergedSettings = new MergedSettings();
        mergedSettings.processPersistenceUnitDescriptorProperties(persistenceUnit);

        // see if the persistence.xml settings named a Hibernate config file....
        String cfgXmlResourceName = (String) mergedSettings.configurationValues.remove(CFG_FILE);
        if (StringHelper.isEmpty(cfgXmlResourceName)) {
            // see if integration settings named a Hibernate config file....
            cfgXmlResourceName = (String) integrationSettings.get(CFG_FILE);
        }

        if (StringHelper.isNotEmpty(cfgXmlResourceName)) {
            processHibernateConfigXmlResources(ssrBuilder, mergedSettings, cfgXmlResourceName);
        }

        normalizeSettings(persistenceUnit, integrationSettings, mergedSettings);

        final String jaccContextId = (String) mergedSettings.configurationValues.get(JACC_CONTEXT_ID);

        // here we are going to iterate the merged config settings looking for:
        //      1) additional JACC permissions
        //      2) additional cache region declarations
        //
        // we will also clean up any references with null entries
        Iterator itr = mergedSettings.configurationValues.entrySet().iterator();
        while (itr.hasNext()) {
            final Map.Entry entry = (Map.Entry) itr.next();
            if (entry.getValue() == null) {
                // remove entries with null values
                itr.remove();
                break;
            }

            if (String.class.isInstance(entry.getKey()) && String.class.isInstance(entry.getValue())) {
                final String keyString = (String) entry.getKey();
                final String valueString = (String) entry.getValue();

                if (keyString.startsWith(JACC_PREFIX)) {
                    if (!JACC_CONTEXT_ID.equals(keyString) && !JACC_ENABLED.equals(keyString)) {
                        if (jaccContextId == null) {
                            LOG.debug(
                                    "Found JACC permission grant [%s] in properties, but no JACC context id was specified; ignoring");
                        } else {
                            mergedSettings.getJaccPermissions(jaccContextId)
                                    .addPermissionDeclaration(parseJaccConfigEntry(keyString, valueString));
                        }
                    }
                } else if (keyString.startsWith(CLASS_CACHE_PREFIX)) {
                    mergedSettings.addCacheRegionDefinition(
                            parseCacheRegionDefinitionEntry(keyString.substring(CLASS_CACHE_PREFIX.length() + 1),
                                    valueString, CacheRegionDefinition.CacheRegionType.ENTITY));
                } else if (keyString.startsWith(COLLECTION_CACHE_PREFIX)) {
                    mergedSettings.addCacheRegionDefinition(parseCacheRegionDefinitionEntry(
                            keyString.substring(COLLECTION_CACHE_PREFIX.length() + 1), (String) entry.getValue(),
                            CacheRegionDefinition.CacheRegionType.COLLECTION));
                }
            }

        }

        return mergedSettings;
    }

    /**
     * Handles normalizing the settings coming from multiple sources, applying proper precedences
     */
    @SuppressWarnings("unchecked")
    private void normalizeSettings(PersistenceUnitDescriptor persistenceUnit, Map<?, ?> integrationSettings,
            MergedSettings mergedSettings) {
        // make a copy so we can remove things as we process them
        final HashMap<?, ?> integrationSettingsCopy = new HashMap<>(integrationSettings);

        normalizeConnectionAccessUserAndPass(integrationSettingsCopy, mergedSettings);

        normalizeTransactionCoordinator(persistenceUnit, integrationSettingsCopy, mergedSettings);

        normalizeDataAccess(integrationSettingsCopy, mergedSettings, persistenceUnit);

        // normalize ValidationMode
        final Object intgValidationMode = integrationSettingsCopy.remove(JPA_VALIDATION_MODE);
        if (intgValidationMode != null) {
            mergedSettings.configurationValues.put(JPA_VALIDATION_MODE, intgValidationMode);
        } else if (persistenceUnit.getValidationMode() != null) {
            mergedSettings.configurationValues.put(JPA_VALIDATION_MODE, persistenceUnit.getValidationMode());
        }

        // normalize SharedCacheMode
        final Object intgCacheMode = integrationSettingsCopy.remove(JPA_SHARED_CACHE_MODE);
        if (intgCacheMode != null) {
            mergedSettings.configurationValues.put(JPA_SHARED_CACHE_MODE, intgCacheMode);
        } else if (persistenceUnit.getSharedCacheMode() != null) {
            mergedSettings.configurationValues.put(JPA_SHARED_CACHE_MODE, persistenceUnit.getSharedCacheMode());
        }

        // Apply all "integration overrides" as the last step.  By specification,
        // these should have precedence.
        //
        // NOTE that this occurs after the specialized normalize calls above which remove
        // any specially-handled settings.
        for (Map.Entry<?, ?> entry : integrationSettingsCopy.entrySet()) {
            if (entry.getKey() == null) {
                continue;
            }

            if (entry.getValue() == null) {
                mergedSettings.configurationValues.remove(entry.getKey());
            } else {
                mergedSettings.configurationValues.put(entry.getKey(), entry.getValue());
            }
        }
    }

    /**
     * Because a DataSource can be secured (requiring Hibernate to pass the USER/PASSWORD when accessing the DataSource)
     * we apply precedence to the USER and PASS separately
     */
    private void normalizeConnectionAccessUserAndPass(HashMap<?, ?> integrationSettingsCopy,
            MergedSettings mergedSettings) {
        //noinspection unchecked
        final Object effectiveUser = NullnessHelper.coalesceSuppliedValues(
                () -> integrationSettingsCopy.remove(USER), () -> integrationSettingsCopy.remove(JPA_JDBC_USER),
                () -> extractPuProperty(persistenceUnit, USER),
                () -> extractPuProperty(persistenceUnit, JPA_JDBC_USER));

        //noinspection unchecked
        final Object effectivePass = NullnessHelper.coalesceSuppliedValues(
                () -> integrationSettingsCopy.remove(PASS), () -> integrationSettingsCopy.remove(JPA_JDBC_PASSWORD),
                () -> extractPuProperty(persistenceUnit, PASS),
                () -> extractPuProperty(persistenceUnit, JPA_JDBC_PASSWORD));

        if (effectiveUser != null || effectivePass != null) {
            applyUserAndPass(effectiveUser, effectivePass, mergedSettings);
        }
    }

    private <T> T extractPuProperty(PersistenceUnitDescriptor persistenceUnit, String propertyName) {
        //noinspection unchecked
        return persistenceUnit.getProperties() == null ? null
                : (T) persistenceUnit.getProperties().get(propertyName);
    }

    @SuppressWarnings("unchecked")
    private void applyUserAndPass(Object effectiveUser, Object effectivePass, MergedSettings mergedSettings) {
        if (effectiveUser != null) {
            mergedSettings.configurationValues.put(USER, effectiveUser);
            mergedSettings.configurationValues.put(JPA_JDBC_USER, effectiveUser);
        }

        if (effectivePass != null) {
            mergedSettings.configurationValues.put(PASS, effectivePass);
            mergedSettings.configurationValues.put(JPA_JDBC_PASSWORD, effectivePass);
        }
    }

    private static final String IS_JTA_TXN_COORD = "local.setting.IS_JTA_TXN_COORD";

    @SuppressWarnings("unchecked")
    private void normalizeTransactionCoordinator(PersistenceUnitDescriptor persistenceUnit,
            HashMap<?, ?> integrationSettingsCopy, MergedSettings mergedSettings) {
        PersistenceUnitTransactionType txnType = null;

        final Object intgTxnType = integrationSettingsCopy.remove(JPA_TRANSACTION_TYPE);

        if (intgTxnType != null) {
            txnType = PersistenceUnitTransactionTypeHelper.interpretTransactionType(intgTxnType);
        } else if (persistenceUnit.getTransactionType() != null) {
            txnType = persistenceUnit.getTransactionType();
        } else {
            final Object puPropTxnType = mergedSettings.configurationValues.get(JPA_TRANSACTION_TYPE);
            if (puPropTxnType != null) {
                txnType = PersistenceUnitTransactionTypeHelper.interpretTransactionType(puPropTxnType);
            }
        }

        if (txnType == null) {
            // is it more appropriate to have this be based on bootstrap entry point (EE vs SE)?
            LOG.debugf("PersistenceUnitTransactionType not specified - falling back to RESOURCE_LOCAL");
            txnType = PersistenceUnitTransactionType.RESOURCE_LOCAL;
        }

        boolean hasTxStrategy = mergedSettings.configurationValues.containsKey(TRANSACTION_COORDINATOR_STRATEGY);
        final Boolean definiteJtaCoordinator;

        if (hasTxStrategy) {
            LOG.overridingTransactionStrategyDangerous(TRANSACTION_COORDINATOR_STRATEGY);

            // see if we can tell whether it is a JTA coordinator
            final Object strategy = mergedSettings.configurationValues.get(TRANSACTION_COORDINATOR_STRATEGY);
            if (strategy instanceof TransactionCoordinatorBuilder) {
                definiteJtaCoordinator = ((TransactionCoordinatorBuilder) strategy).isJta();
            } else {
                definiteJtaCoordinator = false;
            }
        } else {
            if (txnType == PersistenceUnitTransactionType.JTA) {
                mergedSettings.configurationValues.put(TRANSACTION_COORDINATOR_STRATEGY,
                        JtaTransactionCoordinatorBuilderImpl.class);
                definiteJtaCoordinator = true;
            } else if (txnType == PersistenceUnitTransactionType.RESOURCE_LOCAL) {
                mergedSettings.configurationValues.put(TRANSACTION_COORDINATOR_STRATEGY,
                        JdbcResourceLocalTransactionCoordinatorBuilderImpl.class);
                definiteJtaCoordinator = false;
            } else {
                throw new IllegalStateException("Could not determine TransactionCoordinator strategy to use");
            }
        }

        mergedSettings.configurationValues.put(IS_JTA_TXN_COORD, definiteJtaCoordinator);
    }

    private void normalizeDataAccess(HashMap<?, ?> integrationSettingsCopy, MergedSettings mergedSettings,
            PersistenceUnitDescriptor persistenceUnit) {
        if (dataSource != null) {
            applyDataSource(dataSource,
                    // we don't explicitly know
                    null, integrationSettingsCopy, mergedSettings);

            // EARLY EXIT!!
            return;
        }

        if (integrationSettingsCopy.containsKey(DATASOURCE)) {
            final Object dataSourceRef = integrationSettingsCopy.remove(DATASOURCE);
            if (dataSourceRef != null) {
                applyDataSource(dataSourceRef, null, integrationSettingsCopy, mergedSettings);

                // EARLY EXIT!!
                return;
            }
        }

        if (integrationSettingsCopy.containsKey(JPA_JTA_DATASOURCE)) {
            final Object dataSourceRef = integrationSettingsCopy.remove(JPA_JTA_DATASOURCE);
            if (dataSourceRef != null) {
                applyDataSource(dataSourceRef, true, integrationSettingsCopy, mergedSettings);

                // EARLY EXIT!!
                return;
            }
        }

        if (integrationSettingsCopy.containsKey(JPA_NON_JTA_DATASOURCE)) {
            final Object dataSourceRef = integrationSettingsCopy.remove(JPA_NON_JTA_DATASOURCE);

            applyDataSource(dataSourceRef, false, integrationSettingsCopy, mergedSettings);

            // EARLY EXIT!!
            return;
        }

        if (integrationSettingsCopy.containsKey(URL)) {
            // these have precedence over the JPA ones
            final Object integrationJdbcUrl = integrationSettingsCopy.get(URL);
            if (integrationJdbcUrl != null) {
                //noinspection unchecked
                applyJdbcSettings(integrationJdbcUrl, NullnessHelper.coalesceSuppliedValues(
                        () -> ConfigurationHelper.getString(DRIVER, integrationSettingsCopy),
                        () -> ConfigurationHelper.getString(JPA_JDBC_DRIVER, integrationSettingsCopy),
                        () -> ConfigurationHelper.getString(DRIVER, mergedSettings.configurationValues),
                        () -> ConfigurationHelper.getString(JPA_JDBC_DRIVER, mergedSettings.configurationValues)),
                        integrationSettingsCopy, mergedSettings);

                // EARLY EXIT!!
                return;
            }
        }

        if (integrationSettingsCopy.containsKey(JPA_JDBC_URL)) {
            final Object integrationJdbcUrl = integrationSettingsCopy.get(JPA_JDBC_URL);

            if (integrationJdbcUrl != null) {
                //noinspection unchecked
                applyJdbcSettings(integrationJdbcUrl, NullnessHelper.coalesceSuppliedValues(
                        () -> ConfigurationHelper.getString(JPA_JDBC_DRIVER, integrationSettingsCopy),
                        () -> ConfigurationHelper.getString(JPA_JDBC_DRIVER, mergedSettings.configurationValues)),
                        integrationSettingsCopy, mergedSettings);

                // EARLY EXIT!!
                return;
            }
        }

        if (persistenceUnit.getJtaDataSource() != null) {
            applyDataSource(persistenceUnit.getJtaDataSource(), true, integrationSettingsCopy, mergedSettings);

            // EARLY EXIT!!
            return;
        }

        if (persistenceUnit.getNonJtaDataSource() != null) {
            applyDataSource(persistenceUnit.getNonJtaDataSource(), false, integrationSettingsCopy, mergedSettings);

            // EARLY EXIT!!
            return;
        }

        if (mergedSettings.configurationValues.containsKey(URL)) {
            final Object url = mergedSettings.configurationValues.get(URL);

            if (url != null && (!(url instanceof String) || StringHelper.isNotEmpty((String) url))) {
                applyJdbcSettings(url, ConfigurationHelper.getString(DRIVER, mergedSettings.configurationValues),
                        integrationSettingsCopy, mergedSettings);

                // EARLY EXIT!!
                return;
            }
        }

        if (mergedSettings.configurationValues.containsKey(JPA_JDBC_URL)) {
            final Object url = mergedSettings.configurationValues.get(JPA_JDBC_URL);

            if (url != null && (!(url instanceof String) || StringHelper.isNotEmpty((String) url))) {
                applyJdbcSettings(url,
                        ConfigurationHelper.getString(JPA_JDBC_DRIVER, mergedSettings.configurationValues),
                        integrationSettingsCopy, mergedSettings);

                // EARLY EXIT!!
                return;
            }
        }

        // any other conditions to account for?
    }

    @SuppressWarnings("unchecked")
    private void applyDataSource(Object dataSourceRef, Boolean useJtaDataSource,
            HashMap<?, ?> integrationSettingsCopy, MergedSettings mergedSettings) {

        // `IS_JTA_TXN_COORD` is a value set during `#normalizeTransactionCoordinator` to indicate whether
        // the execution environment "is JTA" as best as it can tell..
        //
        // we use this value when JTA was not explicitly specified in regards the DataSource
        final boolean isJtaTransactionCoordinator = (boolean) mergedSettings.configurationValues
                .remove(IS_JTA_TXN_COORD);
        final boolean isJta = useJtaDataSource == null ? isJtaTransactionCoordinator : useJtaDataSource;

        // add to EMF properties (questionable - see HHH-13432)
        final String emfKey;
        final String inverseEmfKey;
        if (isJta) {
            emfKey = JPA_JTA_DATASOURCE;
            inverseEmfKey = JPA_NON_JTA_DATASOURCE;
        } else {
            emfKey = JPA_NON_JTA_DATASOURCE;
            inverseEmfKey = JPA_JTA_DATASOURCE;
        }
        mergedSettings.configurationValues.put(emfKey, dataSourceRef);

        // clear any settings logically overridden by this datasource
        cleanUpConfigKeys(integrationSettingsCopy, mergedSettings, inverseEmfKey, JPA_JDBC_DRIVER, DRIVER,
                JPA_JDBC_URL, URL);

        // clean-up the entries in the "integration overrides" so they do not get get picked
        // up in the general "integration overrides" handling
        cleanUpConfigKeys(integrationSettingsCopy, DATASOURCE, JPA_JTA_DATASOURCE, JPA_NON_JTA_DATASOURCE);

        // add under Hibernate's DATASOURCE setting where the ConnectionProvider will find it
        mergedSettings.configurationValues.put(DATASOURCE, dataSourceRef);
    }

    private void cleanUpConfigKeys(HashMap<?, ?> integrationSettingsCopy, MergedSettings mergedSettings,
            String... keys) {
        for (String key : keys) {
            final Object removedIntgSetting = integrationSettingsCopy.remove(key);
            if (removedIntgSetting != null) {
                LOG.debugf("Removed integration override setting [%s] due to normalization", key);
            }

            final Object removedMergedSetting = mergedSettings.configurationValues.remove(key);
            if (removedMergedSetting != null) {
                LOG.debugf("Removed merged setting [%s] due to normalization", key);
            }
        }
    }

    private void cleanUpConfigKeys(Map<?, ?> settings, String... keys) {
        for (String key : keys) {
            settings.remove(key);
        }
    }

    @SuppressWarnings("unchecked")
    private void applyJdbcSettings(Object url, String driver, HashMap<?, ?> integrationSettingsCopy,
            MergedSettings mergedSettings) {
        mergedSettings.configurationValues.put(URL, url);
        mergedSettings.configurationValues.put(JPA_JDBC_URL, url);

        if (driver != null) {
            mergedSettings.configurationValues.put(DRIVER, driver);
            mergedSettings.configurationValues.put(JPA_JDBC_DRIVER, driver);
        } else {
            mergedSettings.configurationValues.remove(DRIVER);
            mergedSettings.configurationValues.remove(JPA_JDBC_DRIVER);
        }

        // clean up the integration-map values
        cleanUpConfigKeys(integrationSettingsCopy, DRIVER, JPA_JDBC_DRIVER, URL, JPA_JDBC_URL, USER, JPA_JDBC_USER,
                PASS, JPA_JDBC_PASSWORD);

        cleanUpConfigKeys(integrationSettingsCopy, mergedSettings, DATASOURCE, JPA_JTA_DATASOURCE,
                JPA_NON_JTA_DATASOURCE);
    }

    private void processHibernateConfigXmlResources(StandardServiceRegistryBuilder ssrBuilder,
            MergedSettings mergedSettings, String cfgXmlResourceName) {
        final LoadedConfig loadedConfig = ssrBuilder.getConfigLoader().loadConfigXmlResource(cfgXmlResourceName);

        mergedSettings.processHibernateConfigXmlResources(loadedConfig);

        ssrBuilder.getAggregatedCfgXml().merge(loadedConfig);
    }

    private GrantedPermission parseJaccConfigEntry(String keyString, String valueString) {
        try {
            final int roleStart = JACC_PREFIX.length() + 1;
            final String role = keyString.substring(roleStart, keyString.indexOf('.', roleStart));
            final int classStart = roleStart + role.length() + 1;
            final String clazz = keyString.substring(classStart, keyString.length());
            return new GrantedPermission(role, clazz, valueString);
        } catch (IndexOutOfBoundsException e) {
            throw persistenceException("Illegal usage of " + JACC_PREFIX + ": " + keyString);
        }
    }

    private CacheRegionDefinition parseCacheRegionDefinitionEntry(String role, String value,
            CacheRegionDefinition.CacheRegionType cacheType) {
        final StringTokenizer params = new StringTokenizer(value, ";, ");
        if (!params.hasMoreTokens()) {
            StringBuilder error = new StringBuilder("Illegal usage of ");
            if (cacheType == CacheRegionDefinition.CacheRegionType.ENTITY) {
                error.append(CLASS_CACHE_PREFIX).append(": ").append(CLASS_CACHE_PREFIX);
            } else {
                error.append(COLLECTION_CACHE_PREFIX).append(": ").append(COLLECTION_CACHE_PREFIX);
            }
            error.append('.').append(role).append(' ').append(value)
                    .append(".  Was expecting configuration (usage[,region[,lazy]]), but found none");
            throw persistenceException(error.toString());
        }

        String usage = params.nextToken();
        String region = null;
        if (params.hasMoreTokens()) {
            region = params.nextToken();
        }
        boolean lazyProperty = true;
        if (cacheType == CacheRegionDefinition.CacheRegionType.ENTITY) {
            if (params.hasMoreTokens()) {
                lazyProperty = "all".equalsIgnoreCase(params.nextToken());
            }
        } else {
            lazyProperty = false;
        }

        return new CacheRegionDefinition(cacheType, role, usage, region, lazyProperty);
    }

    private void configureIdentifierGenerators(StandardServiceRegistry ssr) {
        final StrategySelector strategySelector = ssr.getService(StrategySelector.class);

        // apply id generators
        final Object idGeneratorStrategyProviderSetting = configurationValues
                .remove(AvailableSettings.IDENTIFIER_GENERATOR_STRATEGY_PROVIDER);
        if (idGeneratorStrategyProviderSetting != null) {
            final IdentifierGeneratorStrategyProvider idGeneratorStrategyProvider = strategySelector
                    .resolveStrategy(IdentifierGeneratorStrategyProvider.class, idGeneratorStrategyProviderSetting);
            final MutableIdentifierGeneratorFactory identifierGeneratorFactory = ssr
                    .getService(MutableIdentifierGeneratorFactory.class);
            if (identifierGeneratorFactory == null) {
                throw persistenceException("Application requested custom identifier generator strategies, "
                        + "but the MutableIdentifierGeneratorFactory could not be found");
            }
            for (Map.Entry<String, Class<?>> entry : idGeneratorStrategyProvider.getStrategies().entrySet()) {
                identifierGeneratorFactory.register(entry.getKey(), entry.getValue());
            }
        }
    }

    @SuppressWarnings("unchecked")
    private List<AttributeConverterDefinition> applyMappingResources(MetadataSources metadataSources) {
        // todo : where in the heck are `org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor.getManagedClassNames` handled?!?

        //      final ClassLoaderService classLoaderService = ssr.getService( ClassLoaderService.class );
        //
        //      // todo : make sure MetadataSources/Metadata are capable of handling duplicate sources
        //
        //      // explicit persistence unit mapping files listings
        //      if ( persistenceUnit.getMappingFileNames() != null ) {
        //         for ( String name : persistenceUnit.getMappingFileNames() ) {
        //            metadataSources.addResource( name );
        //         }
        //      }
        //
        //      // explicit persistence unit managed class listings
        //      //      IMPL NOTE : managed-classes can contain class or package names!!!
        //      if ( persistenceUnit.getManagedClassNames() != null ) {
        //         for ( String managedClassName : persistenceUnit.getManagedClassNames() ) {
        //            // try it as a class name first...
        //            final String classFileName = managedClassName.replace( '.', '/' ) + ".class";
        //            final URL classFileUrl = classLoaderService.locateResource( classFileName );
        //            if ( classFileUrl != null ) {
        //               // it is a class
        //               metadataSources.addAnnotatedClassName( managedClassName );
        //               continue;
        //            }
        //
        //            // otherwise, try it as a package name
        //            final String packageInfoFileName = managedClassName.replace( '.', '/' ) + "/package-info.class";
        //            final URL packageInfoFileUrl = classLoaderService.locateResource( packageInfoFileName );
        //            if ( packageInfoFileUrl != null ) {
        //               // it is a package
        //               metadataSources.addPackage( managedClassName );
        //               continue;
        //            }
        //
        //            LOG.debugf(
        //                  "Unable to resolve class [%s] named in persistence unit [%s]",
        //                  managedClassName,
        //                  persistenceUnit.getName()
        //            );
        //         }
        //      }

        List<AttributeConverterDefinition> attributeConverterDefinitions = null;

        // add any explicit Class references passed in
        final List<Class> loadedAnnotatedClasses = (List<Class>) configurationValues
                .remove(AvailableSettings.LOADED_CLASSES);
        if (loadedAnnotatedClasses != null) {
            for (Class cls : loadedAnnotatedClasses) {
                if (AttributeConverter.class.isAssignableFrom(cls)) {
                    if (attributeConverterDefinitions == null) {
                        attributeConverterDefinitions = new ArrayList<>();
                    }
                    attributeConverterDefinitions
                            .add(AttributeConverterDefinition.from((Class<? extends AttributeConverter>) cls));
                } else {
                    metadataSources.addAnnotatedClass(cls);
                }
            }
        }

        // add any explicit hbm.xml references passed in
        final String explicitHbmXmls = (String) configurationValues.remove(AvailableSettings.HBXML_FILES);
        if (explicitHbmXmls != null) {
            for (String hbmXml : StringHelper.split(", ", explicitHbmXmls)) {
                metadataSources.addResource(hbmXml);
            }
        }

        // add any explicit orm.xml references passed in
        final List<String> explicitOrmXmlList = (List<String>) configurationValues
                .remove(AvailableSettings.XML_FILE_NAMES);
        if (explicitOrmXmlList != null) {
            explicitOrmXmlList.forEach(metadataSources::addResource);
        }

        return attributeConverterDefinitions;
    }

    private void applyMetamodelBuilderSettings(MergedSettings mergedSettings,
            List<AttributeConverterDefinition> attributeConverterDefinitions) {
        metamodelBuilder.getBootstrapContext().markAsJpaBootstrap();

        if (persistenceUnit.getTempClassLoader() != null) {
            metamodelBuilder.applyTempClassLoader(persistenceUnit.getTempClassLoader());
        }

        metamodelBuilder.applyScanEnvironment(new StandardJpaScanEnvironmentImpl(persistenceUnit));
        metamodelBuilder.applyScanOptions(new StandardScanOptions(
                (String) configurationValues.get(org.hibernate.cfg.AvailableSettings.SCANNER_DISCOVERY),
                persistenceUnit.isExcludeUnlistedClasses()));

        if (mergedSettings.cacheRegionDefinitions != null) {
            mergedSettings.cacheRegionDefinitions.forEach(metamodelBuilder::applyCacheRegionDefinition);
        }

        final TypeContributorList typeContributorList = (TypeContributorList) configurationValues
                .remove(TYPE_CONTRIBUTORS);
        if (typeContributorList != null) {
            typeContributorList.getTypeContributors().forEach(metamodelBuilder::applyTypes);
        }

        if (attributeConverterDefinitions != null) {
            attributeConverterDefinitions.forEach(metamodelBuilder::applyAttributeConverter);
        }
    }

    // Phase 2 concerns ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    private Object validatorFactory;
    private Object cdiBeanManager;
    private DataSource dataSource;
    private MetadataImplementor metadata;

    /**
     * Intended for internal testing only...
     */
    public MetadataImplementor getMetadata() {
        return metadata;
    }

    @Override
    public EntityManagerFactoryBuilder withValidatorFactory(Object validatorFactory) {
        this.validatorFactory = validatorFactory;

        if (validatorFactory != null) {
            BeanValidationIntegrator.validateFactory(validatorFactory);
        }
        return this;
    }

    @Override
    public EntityManagerFactoryBuilder withDataSource(DataSource dataSource) {
        this.dataSource = dataSource;

        return this;
    }

    @Override
    public void cancel() {
        // todo : close the bootstrap registry (not critical, but nice to do)
    }

    private MetadataImplementor metadata() {
        if (this.metadata == null) {
            this.metadata = MetadataBuildingProcess.complete(managedResources,
                    metamodelBuilder.getBootstrapContext(), metamodelBuilder.getMetadataBuildingOptions());
        }
        return metadata;
    }

    @Override
    public void generateSchema() {
        // This seems overkill, but building the SF is necessary to get the Integrators to kick in.
        // Metamodel will clean this up...
        try {
            SessionFactoryBuilder sfBuilder = metadata().getSessionFactoryBuilder();
            populateSfBuilder(sfBuilder, standardServiceRegistry);

            SchemaManagementToolCoordinator.process(metadata, standardServiceRegistry, configurationValues,
                    DelayedDropRegistryNotAvailableImpl.INSTANCE);
        } catch (Exception e) {
            throw persistenceException("Error performing schema management", e);
        }

        // release this builder
        cancel();
    }

    @Override
    public EntityManagerFactory build() {
        final SessionFactoryBuilder sfBuilder = metadata().getSessionFactoryBuilder();
        populateSfBuilder(sfBuilder, standardServiceRegistry);

        try {
            return sfBuilder.build();
        } catch (Exception e) {
            throw persistenceException("Unable to build Hibernate SessionFactory", e);
        }
    }

    protected void populateSfBuilder(SessionFactoryBuilder sfBuilder, StandardServiceRegistry ssr) {

        final StrategySelector strategySelector = ssr.getService(StrategySelector.class);

        //      // Locate and apply the requested SessionFactory-level interceptor (if one)
        //      final Object sessionFactoryInterceptorSetting = configurationValues.remove( org.hibernate.cfg.AvailableSettings.INTERCEPTOR );
        //      if ( sessionFactoryInterceptorSetting != null ) {
        //         final Interceptor sessionFactoryInterceptor =
        //               strategySelector.resolveStrategy( Interceptor.class, sessionFactoryInterceptorSetting );
        //         sfBuilder.applyInterceptor( sessionFactoryInterceptor );
        //      }

        // will use user override value or default to false if not supplied to follow JPA spec.
        final boolean jtaTransactionAccessEnabled = readBooleanConfigurationValue(
                AvailableSettings.ALLOW_JTA_TRANSACTION_ACCESS);
        if (!jtaTransactionAccessEnabled) {
            ((SessionFactoryBuilderImplementor) sfBuilder).disableJtaTransactionAccess();
        }

        final boolean allowRefreshDetachedEntity = readBooleanConfigurationValue(
                org.hibernate.cfg.AvailableSettings.ALLOW_REFRESH_DETACHED_ENTITY);
        if (!allowRefreshDetachedEntity) {
            ((SessionFactoryBuilderImplementor) sfBuilder).disableRefreshDetachedEntity();
        }

        // Locate and apply any requested SessionFactoryObserver
        final Object sessionFactoryObserverSetting = configurationValues
                .remove(AvailableSettings.SESSION_FACTORY_OBSERVER);
        if (sessionFactoryObserverSetting != null) {
            final SessionFactoryObserver suppliedSessionFactoryObserver = strategySelector
                    .resolveStrategy(SessionFactoryObserver.class, sessionFactoryObserverSetting);
            sfBuilder.addSessionFactoryObservers(suppliedSessionFactoryObserver);
        }

        sfBuilder.addSessionFactoryObservers(ServiceRegistryCloser.INSTANCE);

        sfBuilder.applyEntityNotFoundDelegate(JpaEntityNotFoundDelegate.INSTANCE);

        if (this.validatorFactory != null) {
            sfBuilder.applyValidatorFactory(validatorFactory);
        }
        if (this.cdiBeanManager != null) {
            sfBuilder.applyBeanManager(cdiBeanManager);
        }
    }

    private static class ServiceRegistryCloser implements SessionFactoryObserver {
        /**
         * Singleton access
         */
        public static final ServiceRegistryCloser INSTANCE = new ServiceRegistryCloser();

        @Override
        public void sessionFactoryCreated(SessionFactory sessionFactory) {
            // nothing to do
        }

        @Override
        public void sessionFactoryClosed(SessionFactory sessionFactory) {
            SessionFactoryImplementor sfi = ((SessionFactoryImplementor) sessionFactory);
            sfi.getServiceRegistry().destroy();
            ServiceRegistry basicRegistry = sfi.getServiceRegistry().getParentServiceRegistry();
            ((ServiceRegistryImplementor) basicRegistry).destroy();
        }
    }

    private PersistenceException persistenceException(String message) {
        return persistenceException(message, null);
    }

    private PersistenceException persistenceException(String message, Exception cause) {
        return new PersistenceException(getExceptionHeader() + message, cause);
    }

    private String getExceptionHeader() {
        return "[PersistenceUnit: " + persistenceUnit.getName() + "] ";
    }

    private static class MergedSettings {
        private final Map configurationValues = new ConcurrentHashMap(16, 0.75f, 1);

        private Map<String, JaccPermissionDeclarations> jaccPermissionsByContextId;
        private List<CacheRegionDefinition> cacheRegionDefinitions;

        /**
         *    MergedSettings is initialized with hibernate.properties
         */
        private MergedSettings() {
            configurationValues.putAll(Environment.getProperties());
        }

        public void processPersistenceUnitDescriptorProperties(PersistenceUnitDescriptor persistenceUnit) {
            if (persistenceUnit.getProperties() != null) {
                configurationValues.putAll(persistenceUnit.getProperties());
            }

            configurationValues.put(PERSISTENCE_UNIT_NAME, persistenceUnit.getName());

        }

        public void processHibernateConfigXmlResources(LoadedConfig loadedConfig) {
            if (!configurationValues.containsKey(SESSION_FACTORY_NAME)) {
                // there is not already a SF-name in the merged settings
                final String sfName = loadedConfig.getSessionFactoryName();
                if (sfName != null) {
                    // but the cfg.xml file we are processing named one..
                    configurationValues.put(SESSION_FACTORY_NAME, sfName);
                }
            } else {
                // make sure they match?
            }

            configurationValues.putAll(loadedConfig.getConfigurationValues());
        }

        public Map getConfigurationValues() {
            return configurationValues;
        }

        private JaccPermissionDeclarations getJaccPermissions(String jaccContextId) {
            if (jaccPermissionsByContextId == null) {
                jaccPermissionsByContextId = new HashMap<>();
            }

            JaccPermissionDeclarations jaccPermissions = jaccPermissionsByContextId.get(jaccContextId);
            if (jaccPermissions == null) {
                jaccPermissions = new JaccPermissionDeclarations(jaccContextId);
                jaccPermissionsByContextId.put(jaccContextId, jaccPermissions);
            }
            return jaccPermissions;
        }

        private void addCacheRegionDefinition(CacheRegionDefinition cacheRegionDefinition) {
            if (this.cacheRegionDefinitions == null) {
                this.cacheRegionDefinitions = new ArrayList<>();
            }
            this.cacheRegionDefinitions.add(cacheRegionDefinition);
        }
    }

    private <T> T loadSettingInstance(String settingName, Object settingValue, Class<T> clazz) {
        T instance = null;
        Class<? extends T> instanceClass = null;

        if (clazz.isAssignableFrom(settingValue.getClass())) {
            instance = (T) settingValue;
        } else if (settingValue instanceof Class) {
            instanceClass = (Class<? extends T>) settingValue;
        } else if (settingValue instanceof String) {
            String settingStringValue = (String) settingValue;
            if (standardServiceRegistry != null) {
                final ClassLoaderService classLoaderService = standardServiceRegistry
                        .getService(ClassLoaderService.class);

                instanceClass = classLoaderService.classForName(settingStringValue);
            } else {
                try {
                    instanceClass = (Class<? extends T>) Class.forName(settingStringValue);
                } catch (ClassNotFoundException e) {
                    throw new IllegalArgumentException("Can't load class: " + settingStringValue, e);
                }
            }
        } else {
            throw new IllegalArgumentException(
                    "The provided " + settingName + " setting value [" + settingValue + "] is not supported!");
        }

        if (instanceClass != null) {
            try {
                instance = instanceClass.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new IllegalArgumentException("The " + clazz.getSimpleName() + " class [" + instanceClass
                        + "] could not be instantiated!", e);
            }
        }

        return instance;
    }
}