org.hibernate.internal.SessionFactoryImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.internal.SessionFactoryImpl.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.internal;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.persistence.EntityGraph;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContextType;
import javax.persistence.PersistenceException;
import javax.persistence.PersistenceUnitUtil;
import javax.persistence.Query;
import javax.persistence.SynchronizationType;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.spi.PersistenceUnitTransactionType;

import org.hibernate.ConnectionAcquisitionMode;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.CustomEntityDirtinessStrategy;
import org.hibernate.EmptyInterceptor;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.MappingException;
import org.hibernate.Session;
import org.hibernate.SessionBuilder;
import org.hibernate.SessionEventListener;
import org.hibernate.SessionFactory;
import org.hibernate.SessionFactoryObserver;
import org.hibernate.StatelessSession;
import org.hibernate.StatelessSessionBuilder;
import org.hibernate.TypeHelper;
import org.hibernate.boot.cfgxml.spi.CfgXmlAccessService;
import org.hibernate.boot.cfgxml.spi.LoadedConfig;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.cache.spi.CacheImplementor;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.cfg.Settings;
import org.hibernate.context.internal.JTASessionContext;
import org.hibernate.context.internal.ManagedSessionContext;
import org.hibernate.context.internal.ThreadLocalSessionContext;
import org.hibernate.context.spi.CurrentSessionContext;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.dialect.function.SQLFunctionRegistry;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jndi.spi.JndiService;
import org.hibernate.engine.profile.Association;
import org.hibernate.engine.profile.Fetch;
import org.hibernate.engine.profile.FetchProfile;
import org.hibernate.engine.query.spi.QueryPlanCache;
import org.hibernate.engine.query.spi.ReturnMetadata;
import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.engine.spi.NamedQueryDefinition;
import org.hibernate.engine.spi.NamedQueryDefinitionBuilder;
import org.hibernate.engine.spi.NamedSQLQueryDefinition;
import org.hibernate.engine.spi.NamedSQLQueryDefinitionBuilder;
import org.hibernate.engine.spi.SessionBuilderImplementor;
import org.hibernate.engine.spi.SessionEventListenerManager;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionOwner;
import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
import org.hibernate.event.service.spi.EventListenerGroup;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.integrator.spi.IntegratorService;
import org.hibernate.internal.util.config.ConfigurationException;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.jpa.internal.AfterCompletionActionLegacyJpaImpl;
import org.hibernate.jpa.internal.ExceptionMapperLegacyJpaImpl;
import org.hibernate.jpa.internal.ManagedFlushCheckerLegacyJpaImpl;
import org.hibernate.jpa.internal.PersistenceUnitUtilImpl;
import org.hibernate.mapping.RootClass;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.metadata.CollectionMetadata;
import org.hibernate.metamodel.internal.MetamodelImpl;
import org.hibernate.metamodel.spi.MetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Loadable;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.proxy.EntityNotFoundDelegate;
import org.hibernate.proxy.HibernateProxyHelper;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.criteria.internal.CriteriaBuilderImpl;
import org.hibernate.query.spi.NamedQueryRepository;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.hibernate.resource.transaction.backend.jta.internal.synchronization.AfterCompletionAction;
import org.hibernate.resource.transaction.backend.jta.internal.synchronization.ExceptionMapper;
import org.hibernate.resource.transaction.backend.jta.internal.synchronization.ManagedFlushChecker;
import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder;
import org.hibernate.secure.spi.GrantedPermission;
import org.hibernate.secure.spi.JaccPermissionDeclarations;
import org.hibernate.secure.spi.JaccService;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import org.hibernate.service.spi.SessionFactoryServiceRegistryFactory;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.tool.schema.spi.DelayedDropAction;
import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator;
import org.hibernate.type.SerializableType;
import org.hibernate.type.Type;
import org.hibernate.type.TypeResolver;

import org.jboss.logging.Logger;

import static org.hibernate.metamodel.internal.JpaMetaModelPopulationSetting.determineJpaMetaModelPopulationSetting;

/**
 * Concrete implementation of the <tt>SessionFactory</tt> interface. Has the following
 * responsibilities
 * <ul>
 * <li>caches configuration settings (immutably)
 * <li>caches "compiled" mappings ie. <tt>EntityPersister</tt>s and
 *     <tt>CollectionPersister</tt>s (immutable)
 * <li>caches "compiled" queries (memory sensitive cache)
 * <li>manages <tt>PreparedStatement</tt>s
 * <li> delegates JDBC <tt>Connection</tt> management to the <tt>ConnectionProvider</tt>
 * <li>factory for instances of <tt>SessionImpl</tt>
 * </ul>
 * This class must appear immutable to clients, even if it does all kinds of caching
 * and pooling under the covers. It is crucial that the class is not only thread
 * safe, but also highly concurrent. Synchronization must be used extremely sparingly.
 *
 * @author Gavin King
 * @author Steve Ebersole
 * @author Chris Cranford
 */
public final class SessionFactoryImpl implements SessionFactoryImplementor {
    private static final CoreMessageLogger LOG = CoreLogging.messageLogger(SessionFactoryImpl.class);
    private static final Pattern LISTENER_SEPARATION_PATTERN = Pattern.compile(" ,");

    private final String name;
    private final String uuid;

    private transient volatile boolean isClosed;

    private final transient SessionFactoryObserverChain observer = new SessionFactoryObserverChain();

    private final transient SessionFactoryOptions sessionFactoryOptions;
    private final transient Settings settings;
    private final transient Map<String, Object> properties;

    private final transient SessionFactoryServiceRegistry serviceRegistry;
    private final transient JdbcServices jdbcServices;

    private final transient SQLFunctionRegistry sqlFunctionRegistry;

    // todo : org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor too?

    private final transient MetamodelImplementor metamodel;
    private final transient CriteriaBuilderImpl criteriaBuilder;
    private final PersistenceUnitUtil jpaPersistenceUnitUtil;
    private final transient CacheImplementor cacheAccess;
    private final transient NamedQueryRepository namedQueryRepository;
    private final transient QueryPlanCache queryPlanCache;

    private final transient CurrentSessionContext currentSessionContext;

    private volatile DelayedDropAction delayedDropAction;

    // todo : move to MetamodelImpl
    private final transient Map<String, IdentifierGenerator> identifierGenerators;
    private final transient Map<String, FilterDefinition> filters;
    private final transient Map<String, FetchProfile> fetchProfiles;

    private final transient TypeHelper typeHelper;

    private final transient FastSessionServices fastSessionServices;
    private final transient SessionBuilder defaultSessionOpenOptions;
    private final transient SessionBuilder temporarySessionOpenOptions;

    public SessionFactoryImpl(final MetadataImplementor metadata, SessionFactoryOptions options) {
        LOG.debug("Building session factory");

        this.sessionFactoryOptions = options;
        this.settings = new Settings(options, metadata);

        this.serviceRegistry = options.getServiceRegistry().getService(SessionFactoryServiceRegistryFactory.class)
                .buildServiceRegistry(this, options);

        prepareEventListeners(metadata);

        final CfgXmlAccessService cfgXmlAccessService = serviceRegistry.getService(CfgXmlAccessService.class);

        String sfName = settings.getSessionFactoryName();
        if (cfgXmlAccessService.getAggregatedConfig() != null) {
            if (sfName == null) {
                sfName = cfgXmlAccessService.getAggregatedConfig().getSessionFactoryName();
            }
            applyCfgXmlValues(cfgXmlAccessService.getAggregatedConfig(), serviceRegistry);
        }

        this.name = sfName;
        this.uuid = options.getUuid();

        jdbcServices = serviceRegistry.getService(JdbcServices.class);

        this.properties = new HashMap<>();
        this.properties.putAll(serviceRegistry.getService(ConfigurationService.class).getSettings());
        if (!properties.containsKey(AvailableSettings.JPA_VALIDATION_FACTORY)) {
            if (getSessionFactoryOptions().getValidatorFactoryReference() != null) {
                properties.put(AvailableSettings.JPA_VALIDATION_FACTORY,
                        getSessionFactoryOptions().getValidatorFactoryReference());
            }
        }

        maskOutSensitiveInformation(this.properties);
        logIfEmptyCompositesEnabled(this.properties);

        this.sqlFunctionRegistry = new SQLFunctionRegistry(jdbcServices.getJdbcEnvironment().getDialect(),
                options.getCustomSqlFunctionMap());
        this.cacheAccess = this.serviceRegistry.getService(CacheImplementor.class);
        this.criteriaBuilder = new CriteriaBuilderImpl(this);
        this.jpaPersistenceUnitUtil = new PersistenceUnitUtilImpl(this);

        for (SessionFactoryObserver sessionFactoryObserver : options.getSessionFactoryObservers()) {
            this.observer.addObserver(sessionFactoryObserver);
        }

        this.typeHelper = new TypeLocatorImpl(metadata.getTypeConfiguration().getTypeResolver());

        this.filters = new HashMap<>();
        this.filters.putAll(metadata.getFilterDefinitions());

        LOG.debugf("Session factory constructed with filter configurations : %s", filters);
        LOG.debugf("Instantiating session factory with properties: %s", properties);

        this.queryPlanCache = new QueryPlanCache(this);

        class IntegratorObserver implements SessionFactoryObserver {
            private ArrayList<Integrator> integrators = new ArrayList<>();

            @Override
            public void sessionFactoryCreated(SessionFactory factory) {
            }

            @Override
            public void sessionFactoryClosed(SessionFactory factory) {
                for (Integrator integrator : integrators) {
                    integrator.disintegrate(SessionFactoryImpl.this, SessionFactoryImpl.this.serviceRegistry);
                }
                integrators.clear();
            }
        }
        final IntegratorObserver integratorObserver = new IntegratorObserver();
        this.observer.addObserver(integratorObserver);
        try {
            for (Integrator integrator : serviceRegistry.getService(IntegratorService.class).getIntegrators()) {
                integrator.integrate(metadata, this, this.serviceRegistry);
                integratorObserver.integrators.add(integrator);
            }
            //Generators:
            this.identifierGenerators = new HashMap<>();
            metadata.getEntityBindings().stream().filter(model -> !model.isInherited()).forEach(model -> {
                IdentifierGenerator generator = model.getIdentifier().createIdentifierGenerator(
                        metadata.getIdentifierGeneratorFactory(), jdbcServices.getJdbcEnvironment().getDialect(),
                        settings.getDefaultCatalogName(), settings.getDefaultSchemaName(), (RootClass) model);
                identifierGenerators.put(model.getEntityName(), generator);
            });

            LOG.debug("Instantiated session factory");

            this.metamodel = metadata.getTypeConfiguration().scope(this);
            ((MetamodelImpl) this.metamodel).initialize(metadata,
                    determineJpaMetaModelPopulationSetting(properties));

            //Named Queries:
            this.namedQueryRepository = metadata.buildNamedQueryRepository(this);

            settings.getMultiTableBulkIdStrategy().prepare(jdbcServices, buildLocalConnectionAccess(), metadata,
                    sessionFactoryOptions);

            SchemaManagementToolCoordinator.process(metadata, serviceRegistry, properties,
                    action -> SessionFactoryImpl.this.delayedDropAction = action);

            currentSessionContext = buildCurrentSessionContext();

            //checking for named queries
            if (settings.isNamedQueryStartupCheckingEnabled()) {
                final Map<String, HibernateException> errors = checkNamedQueries();
                if (!errors.isEmpty()) {
                    StringBuilder failingQueries = new StringBuilder("Errors in named queries: ");
                    String separator = System.lineSeparator();

                    for (Map.Entry<String, HibernateException> entry : errors.entrySet()) {
                        LOG.namedQueryError(entry.getKey(), entry.getValue());

                        failingQueries.append(separator).append(entry.getKey()).append(" failed because of: ")
                                .append(entry.getValue());
                    }
                    throw new HibernateException(failingQueries.toString());
                }
            }

            // this needs to happen after persisters are all ready to go...
            this.fetchProfiles = new HashMap<>();
            for (org.hibernate.mapping.FetchProfile mappingProfile : metadata.getFetchProfiles()) {
                final FetchProfile fetchProfile = new FetchProfile(mappingProfile.getName());
                for (org.hibernate.mapping.FetchProfile.Fetch mappingFetch : mappingProfile.getFetches()) {
                    // resolve the persister owning the fetch
                    final String entityName = metamodel.getImportedClassName(mappingFetch.getEntity());
                    final EntityPersister owner = entityName == null ? null : metamodel.entityPersister(entityName);
                    if (owner == null) {
                        throw new HibernateException("Unable to resolve entity reference ["
                                + mappingFetch.getEntity() + "] in fetch profile [" + fetchProfile.getName() + "]");
                    }

                    // validate the specified association fetch
                    Type associationType = owner.getPropertyType(mappingFetch.getAssociation());
                    if (associationType == null || !associationType.isAssociationType()) {
                        throw new HibernateException(
                                "Fetch profile [" + fetchProfile.getName() + "] specified an invalid association");
                    }

                    // resolve the style
                    final Fetch.Style fetchStyle = Fetch.Style.parse(mappingFetch.getStyle());

                    // then construct the fetch instance...
                    fetchProfile.addFetch(new Association(owner, mappingFetch.getAssociation()), fetchStyle);
                    ((Loadable) owner).registerAffectingFetchProfile(fetchProfile.getName());
                }
                fetchProfiles.put(fetchProfile.getName(), fetchProfile);
            }

            this.defaultSessionOpenOptions = withOptions();
            this.temporarySessionOpenOptions = buildTemporarySessionOpenOptions();
            this.fastSessionServices = new FastSessionServices(this);

            this.observer.sessionFactoryCreated(this);

            SessionFactoryRegistry.INSTANCE.addSessionFactory(getUuid(), name,
                    settings.isSessionFactoryNameAlsoJndiName(), this,
                    serviceRegistry.getService(JndiService.class));
        } catch (Exception e) {
            for (Integrator integrator : serviceRegistry.getService(IntegratorService.class).getIntegrators()) {
                integrator.disintegrate(this, serviceRegistry);
                integratorObserver.integrators.remove(integrator);
            }
            close();
            throw e;
        }
    }

    private SessionBuilder buildTemporarySessionOpenOptions() {
        return withOptions().autoClose(false).flushMode(FlushMode.MANUAL).connectionHandlingMode(
                PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT);
    }

    private void prepareEventListeners(MetadataImplementor metadata) {
        final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
        final ConfigurationService cfgService = serviceRegistry.getService(ConfigurationService.class);
        final ClassLoaderService classLoaderService = serviceRegistry.getService(ClassLoaderService.class);

        eventListenerRegistry.prepare(metadata);

        for (Map.Entry entry : ((Map<?, ?>) cfgService.getSettings()).entrySet()) {
            if (!String.class.isInstance(entry.getKey())) {
                continue;
            }
            final String propertyName = (String) entry.getKey();
            if (!propertyName.startsWith(org.hibernate.jpa.AvailableSettings.EVENT_LISTENER_PREFIX)) {
                continue;
            }
            final String eventTypeName = propertyName
                    .substring(org.hibernate.jpa.AvailableSettings.EVENT_LISTENER_PREFIX.length() + 1);
            final EventType eventType = EventType.resolveEventTypeByName(eventTypeName);
            final EventListenerGroup eventListenerGroup = eventListenerRegistry.getEventListenerGroup(eventType);
            for (String listenerImpl : LISTENER_SEPARATION_PATTERN.split(((String) entry.getValue()))) {
                eventListenerGroup.appendListener(instantiate(listenerImpl, classLoaderService));
            }
        }
    }

    private Object instantiate(String listenerImpl, ClassLoaderService classLoaderService) {
        try {
            return classLoaderService.classForName(listenerImpl).newInstance();
        } catch (Exception e) {
            throw new HibernateException("Could not instantiate requested listener [" + listenerImpl + "]", e);
        }
    }

    private void applyCfgXmlValues(LoadedConfig aggregatedConfig, SessionFactoryServiceRegistry serviceRegistry) {
        final JaccService jaccService = serviceRegistry.getService(JaccService.class);
        if (jaccService.getContextId() != null) {
            final JaccPermissionDeclarations permissions = aggregatedConfig
                    .getJaccPermissions(jaccService.getContextId());
            if (permissions != null) {
                for (GrantedPermission grantedPermission : permissions.getPermissionDeclarations()) {
                    jaccService.addPermission(grantedPermission);
                }
            }
        }

        if (aggregatedConfig.getEventListenerMap() != null) {
            final ClassLoaderService cls = serviceRegistry.getService(ClassLoaderService.class);
            final EventListenerRegistry eventListenerRegistry = serviceRegistry
                    .getService(EventListenerRegistry.class);
            for (Map.Entry<EventType, Set<String>> entry : aggregatedConfig.getEventListenerMap().entrySet()) {
                final EventListenerGroup group = eventListenerRegistry.getEventListenerGroup(entry.getKey());
                for (String listenerClassName : entry.getValue()) {
                    try {
                        group.appendListener(cls.classForName(listenerClassName).newInstance());
                    } catch (Exception e) {
                        throw new ConfigurationException(
                                "Unable to instantiate event listener class : " + listenerClassName, e);
                    }
                }
            }
        }
    }

    private JdbcConnectionAccess buildLocalConnectionAccess() {
        if (settings.getMultiTenancyStrategy().requiresMultiTenantConnectionProvider()) {
            final MultiTenantConnectionProvider mTenantConnectionProvider = serviceRegistry
                    .getService(MultiTenantConnectionProvider.class);
            return new JdbcEnvironmentInitiator.MultiTenantConnectionProviderJdbcConnectionAccess(
                    mTenantConnectionProvider);
        } else {
            final ConnectionProvider connectionProvider = serviceRegistry.getService(ConnectionProvider.class);
            return new JdbcEnvironmentInitiator.ConnectionProviderJdbcConnectionAccess(connectionProvider);
        }
    }

    public Session openSession() throws HibernateException {
        final CurrentTenantIdentifierResolver currentTenantIdentifierResolver = getCurrentTenantIdentifierResolver();
        //We can only use reuse the defaultSessionOpenOptions as a constant when there is no TenantIdentifierResolver
        if (currentTenantIdentifierResolver != null) {
            return this.withOptions().openSession();
        } else {
            return this.defaultSessionOpenOptions.openSession();
        }
    }

    public Session openTemporarySession() throws HibernateException {
        final CurrentTenantIdentifierResolver currentTenantIdentifierResolver = getCurrentTenantIdentifierResolver();
        //We can only use reuse the defaultSessionOpenOptions as a constant when there is no TenantIdentifierResolver
        if (currentTenantIdentifierResolver != null) {
            return buildTemporarySessionOpenOptions().openSession();
        } else {
            return this.temporarySessionOpenOptions.openSession();
        }
    }

    public Session getCurrentSession() throws HibernateException {
        if (currentSessionContext == null) {
            throw new HibernateException("No CurrentSessionContext configured!");
        }
        return currentSessionContext.currentSession();
    }

    @Override
    public SessionBuilderImplementor withOptions() {
        return new SessionBuilderImpl(this);
    }

    @Override
    public StatelessSessionBuilder withStatelessOptions() {
        return new StatelessSessionBuilderImpl(this);
    }

    public StatelessSession openStatelessSession() {
        return withStatelessOptions().openStatelessSession();
    }

    public StatelessSession openStatelessSession(Connection connection) {
        return withStatelessOptions().connection(connection).openStatelessSession();
    }

    @Override
    public void addObserver(SessionFactoryObserver observer) {
        this.observer.addObserver(observer);
    }

    @Override
    public Map<String, Object> getProperties() {
        validateNotClosed();
        return properties;
    }

    protected void validateNotClosed() {
        if (isClosed) {
            throw new IllegalStateException("EntityManagerFactory is closed");
        }
    }

    @Override
    public String getUuid() {
        return uuid;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public JdbcServices getJdbcServices() {
        return jdbcServices;
    }

    public IdentifierGeneratorFactory getIdentifierGeneratorFactory() {
        return null;
    }

    /**
     * Retrieve the {@link Type} resolver associated with this factory.
     *
     * @return The type resolver
     *
     * @deprecated (since 5.3) No replacement, access to and handling of Types will be much different in 6.0
     */
    @Deprecated
    public TypeResolver getTypeResolver() {
        return metamodel.getTypeConfiguration().getTypeResolver();
    }

    public QueryPlanCache getQueryPlanCache() {
        return queryPlanCache;
    }

    private Map<String, HibernateException> checkNamedQueries() throws HibernateException {
        return namedQueryRepository.checkNamedQueries(queryPlanCache);
    }

    @Override
    public DeserializationResolver getDeserializationResolver() {
        return new DeserializationResolver() {
            @Override
            public SessionFactoryImplementor resolve() {
                return (SessionFactoryImplementor) SessionFactoryRegistry.INSTANCE.findSessionFactory(uuid, name);
            }
        };
    }

    @SuppressWarnings("deprecation")
    public Settings getSettings() {
        return settings;
    }

    @Override
    public <T> List<RootGraphImplementor<? super T>> findEntityGraphsByJavaType(Class<T> entityClass) {
        return getMetamodel().findEntityGraphsByJavaType(entityClass);
    }

    // todo : (5.2) review synchronizationType, persistenceContextType, transactionType usage

    // SynchronizationType -> should we auto enlist in transactions
    private transient SynchronizationType synchronizationType;

    // PersistenceContextType -> influences FlushMode and 'autoClose'
    private transient PersistenceContextType persistenceContextType;

    @Override
    public Session createEntityManager() {
        validateNotClosed();
        return buildEntityManager(SynchronizationType.SYNCHRONIZED, null);
    }

    private <K, V> Session buildEntityManager(final SynchronizationType synchronizationType, final Map<K, V> map) {
        assert !isClosed;

        SessionBuilderImplementor builder = withOptions();
        if (synchronizationType == SynchronizationType.SYNCHRONIZED) {
            builder.autoJoinTransactions(true);
        } else {
            builder.autoJoinTransactions(false);
        }

        final Session session = builder.openSession();
        if (map != null) {
            for (Map.Entry<K, V> o : map.entrySet()) {
                final K key = o.getKey();
                if (key instanceof String) {
                    final String sKey = (String) key;
                    session.setProperty(sKey, o.getValue());
                }
            }
        }
        return session;
    }

    @Override
    public Session createEntityManager(Map map) {
        validateNotClosed();
        return buildEntityManager(SynchronizationType.SYNCHRONIZED, map);
    }

    @Override
    public Session createEntityManager(SynchronizationType synchronizationType) {
        validateNotClosed();
        errorIfResourceLocalDueToExplicitSynchronizationType();
        return buildEntityManager(synchronizationType, null);
    }

    private void errorIfResourceLocalDueToExplicitSynchronizationType() {
        // JPA requires that we throw IllegalStateException in cases where:
        //      1) the PersistenceUnitTransactionType (TransactionCoordinator) is non-JTA
        //      2) an explicit SynchronizationType is specified
        if (!getServiceRegistry().getService(TransactionCoordinatorBuilder.class).isJta()) {
            throw new IllegalStateException(
                    "Illegal attempt to specify a SynchronizationType when building an EntityManager from a "
                            + "EntityManagerFactory defined as RESOURCE_LOCAL (as opposed to JTA)");
        }
    }

    @Override
    public Session createEntityManager(SynchronizationType synchronizationType, Map map) {
        validateNotClosed();
        errorIfResourceLocalDueToExplicitSynchronizationType();
        return buildEntityManager(synchronizationType, map);
    }

    @Override
    public CriteriaBuilder getCriteriaBuilder() {
        validateNotClosed();
        return criteriaBuilder;
    }

    @Override
    public MetamodelImplementor getMetamodel() {
        validateNotClosed();
        return metamodel;
    }

    @Override
    public boolean isOpen() {
        return !isClosed;
    }

    @Override
    public RootGraphImplementor findEntityGraphByName(String name) {
        return getMetamodel().findEntityGraphByName(name);
    }

    @Override
    public SessionFactoryOptions getSessionFactoryOptions() {
        return sessionFactoryOptions;
    }

    public Interceptor getInterceptor() {
        return sessionFactoryOptions.getInterceptor();
    }

    @Override
    public Reference getReference() {
        // from javax.naming.Referenceable
        LOG.debug("Returning a Reference to the SessionFactory");
        return new Reference(SessionFactoryImpl.class.getName(), new StringRefAddr("uuid", getUuid()),
                SessionFactoryRegistry.ObjectFactoryImpl.class.getName(), null);
    }

    @Override
    public NamedQueryRepository getNamedQueryRepository() {
        return namedQueryRepository;
    }

    public Type getIdentifierType(String className) throws MappingException {
        return getMetamodel().entityPersister(className).getIdentifierType();
    }

    public String getIdentifierPropertyName(String className) throws MappingException {
        return getMetamodel().entityPersister(className).getIdentifierPropertyName();
    }

    public Type[] getReturnTypes(String queryString) throws HibernateException {
        final ReturnMetadata metadata = queryPlanCache.getHQLQueryPlan(queryString, false, Collections.EMPTY_MAP)
                .getReturnMetadata();
        return metadata == null ? null : metadata.getReturnTypes();
    }

    public String[] getReturnAliases(String queryString) throws HibernateException {
        final ReturnMetadata metadata = queryPlanCache.getHQLQueryPlan(queryString, false, Collections.EMPTY_MAP)
                .getReturnMetadata();
        return metadata == null ? null : metadata.getReturnAliases();
    }

    public ClassMetadata getClassMetadata(Class persistentClass) throws HibernateException {
        return getClassMetadata(persistentClass.getName());
    }

    public CollectionMetadata getCollectionMetadata(String roleName) throws HibernateException {
        return (CollectionMetadata) getMetamodel().collectionPersister(roleName);
    }

    public ClassMetadata getClassMetadata(String entityName) throws HibernateException {
        return (ClassMetadata) getMetamodel().entityPersister(entityName);
    }

    @Override
    public Map<String, ClassMetadata> getAllClassMetadata() throws HibernateException {
        throw new UnsupportedOperationException(
                "org.hibernate.SessionFactory.getAllClassMetadata is no longer supported");
    }

    public Map getAllCollectionMetadata() throws HibernateException {
        throw new UnsupportedOperationException(
                "org.hibernate.SessionFactory.getAllCollectionMetadata is no longer supported");
    }

    public Type getReferencedPropertyType(String className, String propertyName) throws MappingException {
        return getMetamodel().entityPersister(className).getPropertyType(propertyName);
    }

    /**
     * Closes the session factory, releasing all held resources.
     *
     * <ol>
     * <li>cleans up used cache regions and "stops" the cache provider.
     * <li>close the JDBC connection
     * <li>remove the JNDI binding
     * </ol>
     *
     * Note: Be aware that the sessionFactory instance still can
     * be a "heavy" object memory wise after close() has been called.  Thus
     * it is important to not keep referencing the instance to let the garbage
     * collector release the memory.
     * @throws HibernateException
     */
    public void close() throws HibernateException {
        //This is an idempotent operation so we can do it even before the checks (it won't hurt):
        Environment.getBytecodeProvider().resetCaches();
        synchronized (this) {
            if (isClosed) {
                if (getSessionFactoryOptions().getJpaCompliance().isJpaClosedComplianceEnabled()) {
                    throw new IllegalStateException("EntityManagerFactory is already closed");
                }

                LOG.trace("Already closed");
                return;
            }

            isClosed = true;
        }

        LOG.closing();
        observer.sessionFactoryClosing(this);

        settings.getMultiTableBulkIdStrategy().release(serviceRegistry.getService(JdbcServices.class),
                buildLocalConnectionAccess());

        // NOTE : the null checks below handle cases where close is called from
        //      a failed attempt to create the SessionFactory

        if (cacheAccess != null) {
            cacheAccess.close();
        }

        if (metamodel != null) {
            metamodel.close();
        }

        if (queryPlanCache != null) {
            queryPlanCache.cleanup();
        }

        if (delayedDropAction != null) {
            delayedDropAction.perform(serviceRegistry);
        }

        SessionFactoryRegistry.INSTANCE.removeSessionFactory(getUuid(), name,
                settings.isSessionFactoryNameAlsoJndiName(), serviceRegistry.getService(JndiService.class));

        observer.sessionFactoryClosed(this);
        serviceRegistry.destroy();
    }

    public CacheImplementor getCache() {
        validateNotClosed();
        return cacheAccess;
    }

    @Override
    public PersistenceUnitUtil getPersistenceUnitUtil() {
        validateNotClosed();
        return jpaPersistenceUnitUtil;
    }

    @Override
    public void addNamedQuery(String name, Query query) {
        validateNotClosed();

        // NOTE : we use Query#unwrap here (rather than direct type checking) to account for possibly wrapped
        // query implementations

        // first, handle StoredProcedureQuery
        try {
            final ProcedureCall unwrapped = query.unwrap(ProcedureCall.class);
            if (unwrapped != null) {
                addNamedStoredProcedureQuery(name, unwrapped);
                return;
            }
        } catch (PersistenceException ignore) {
            // this means 'query' is not a StoredProcedureQueryImpl
        }

        // then try as a native-SQL or JPQL query
        try {
            org.hibernate.query.Query hibernateQuery = query.unwrap(org.hibernate.query.Query.class);
            if (hibernateQuery != null) {
                // create and register the proper NamedQueryDefinition...
                if (NativeQuery.class.isInstance(hibernateQuery)) {
                    getNamedQueryRepository().registerNamedSQLQueryDefinition(name,
                            extractSqlQueryDefinition((NativeQuery) hibernateQuery, name));
                } else {
                    getNamedQueryRepository().registerNamedQueryDefinition(name,
                            extractHqlQueryDefinition(hibernateQuery, name));
                }
                return;
            }
        } catch (PersistenceException ignore) {
            // this means 'query' is not a native-SQL or JPQL query
        }

        // if we get here, we are unsure how to properly unwrap the incoming query to extract the needed information
        throw new PersistenceException(String
                .format("Unsure how to how to properly unwrap given Query [%s] as basis for named query", query));
    }

    private void addNamedStoredProcedureQuery(String name, ProcedureCall procedureCall) {
        getNamedQueryRepository().registerNamedProcedureCallMemento(name,
                procedureCall.extractMemento(procedureCall.getHints()));
    }

    private NamedSQLQueryDefinition extractSqlQueryDefinition(NativeQuery nativeSqlQuery, String name) {
        final NamedSQLQueryDefinitionBuilder builder = new NamedSQLQueryDefinitionBuilder(name);
        fillInNamedQueryBuilder(builder, nativeSqlQuery);
        builder.setCallable(nativeSqlQuery.isCallable()).setQuerySpaces(nativeSqlQuery.getSynchronizedQuerySpaces())
                .setQueryReturns(nativeSqlQuery.getQueryReturns());
        return builder.createNamedQueryDefinition();
    }

    private NamedQueryDefinition extractHqlQueryDefinition(org.hibernate.query.Query hqlQuery, String name) {
        final NamedQueryDefinitionBuilder builder = new NamedQueryDefinitionBuilder(name);
        fillInNamedQueryBuilder(builder, hqlQuery);
        // LockOptions only valid for HQL/JPQL queries...
        builder.setLockOptions(hqlQuery.getLockOptions().makeCopy());
        return builder.createNamedQueryDefinition();
    }

    private void fillInNamedQueryBuilder(NamedQueryDefinitionBuilder builder, org.hibernate.query.Query query) {
        builder.setQuery(query.getQueryString()).setComment(query.getComment()).setCacheable(query.isCacheable())
                .setCacheRegion(query.getCacheRegion()).setCacheMode(query.getCacheMode())
                .setReadOnly(query.isReadOnly()).setFlushMode(query.getHibernateFlushMode());

        if (query.getQueryOptions().getFirstRow() != null) {
            builder.setFirstResult(query.getQueryOptions().getFirstRow());
        }

        if (query.getQueryOptions().getMaxRows() != null) {
            builder.setMaxResults(query.getQueryOptions().getMaxRows());
        }

        if (query.getQueryOptions().getTimeout() != null) {
            builder.setTimeout(query.getQueryOptions().getTimeout());
        }

        if (query.getQueryOptions().getFetchSize() != null) {
            builder.setFetchSize(query.getQueryOptions().getFetchSize());
        }
    }

    @Override
    public <T> T unwrap(Class<T> type) {
        if (type.isAssignableFrom(SessionFactory.class)) {
            return type.cast(this);
        }

        if (type.isAssignableFrom(SessionFactoryImplementor.class)) {
            return type.cast(this);
        }

        if (type.isAssignableFrom(SessionFactoryImpl.class)) {
            return type.cast(this);
        }

        if (type.isAssignableFrom(EntityManagerFactory.class)) {
            return type.cast(this);
        }

        throw new PersistenceException("Hibernate cannot unwrap EntityManagerFactory as '" + type.getName() + "'");
    }

    @Override
    public <T> void addNamedEntityGraph(String graphName, EntityGraph<T> entityGraph) {
        getMetamodel().addNamedEntityGraph(graphName, (RootGraphImplementor<T>) entityGraph);
    }

    public boolean isClosed() {
        return isClosed;
    }

    private transient StatisticsImplementor statistics;

    public StatisticsImplementor getStatistics() {
        if (statistics == null) {
            statistics = serviceRegistry.getService(StatisticsImplementor.class);
        }
        return statistics;
    }

    public FilterDefinition getFilterDefinition(String filterName) throws HibernateException {
        FilterDefinition def = filters.get(filterName);
        if (def == null) {
            throw new HibernateException("No such filter configured [" + filterName + "]");
        }
        return def;
    }

    public boolean containsFetchProfileDefinition(String name) {
        return fetchProfiles.containsKey(name);
    }

    public Set getDefinedFilterNames() {
        return filters.keySet();
    }

    public IdentifierGenerator getIdentifierGenerator(String rootEntityName) {
        return identifierGenerators.get(rootEntityName);
    }

    private boolean canAccessTransactionManager() {
        try {
            return serviceRegistry.getService(JtaPlatform.class).retrieveTransactionManager() != null;
        } catch (Exception e) {
            return false;
        }
    }

    private CurrentSessionContext buildCurrentSessionContext() {
        String impl = (String) properties.get(Environment.CURRENT_SESSION_CONTEXT_CLASS);
        // for backward-compatibility
        if (impl == null) {
            if (canAccessTransactionManager()) {
                impl = "jta";
            } else {
                return null;
            }
        }

        if ("jta".equals(impl)) {
            //         if ( ! transactionFactory().compatibleWithJtaSynchronization() ) {
            //            LOG.autoFlushWillNotWork();
            //         }
            return new JTASessionContext(this);
        } else if ("thread".equals(impl)) {
            return new ThreadLocalSessionContext(this);
        } else if ("managed".equals(impl)) {
            return new ManagedSessionContext(this);
        } else {
            try {
                Class implClass = serviceRegistry.getService(ClassLoaderService.class).classForName(impl);
                return (CurrentSessionContext) implClass
                        .getConstructor(new Class[] { SessionFactoryImplementor.class }).newInstance(this);
            } catch (Throwable t) {
                LOG.unableToConstructCurrentSessionContext(impl, t);
                return null;
            }
        }
    }

    @Override
    public ServiceRegistryImplementor getServiceRegistry() {
        return serviceRegistry;
    }

    @Override
    public EntityNotFoundDelegate getEntityNotFoundDelegate() {
        return sessionFactoryOptions.getEntityNotFoundDelegate();
    }

    public SQLFunctionRegistry getSqlFunctionRegistry() {
        return sqlFunctionRegistry;
    }

    public FetchProfile getFetchProfile(String name) {
        return fetchProfiles.get(name);
    }

    public TypeHelper getTypeHelper() {
        return typeHelper;
    }

    @Override
    public Type resolveParameterBindType(Object bindValue) {
        if (bindValue == null) {
            // we can't guess
            return null;
        }

        return resolveParameterBindType(HibernateProxyHelper.getClassWithoutInitializingProxy(bindValue));
    }

    @Override
    public Type resolveParameterBindType(Class clazz) {
        String typename = clazz.getName();
        Type type = getTypeResolver().heuristicType(typename);
        boolean serializable = type != null && type instanceof SerializableType;
        if (type == null || serializable) {
            try {
                getMetamodel().entityPersister(clazz.getName());
            } catch (MappingException me) {
                if (serializable) {
                    return type;
                } else {
                    throw new HibernateException("Could not determine a type for class: " + typename);
                }
            }
            return getTypeHelper().entity(clazz);
        } else {
            return type;
        }
    }

    public static Interceptor configuredInterceptor(Interceptor interceptor, SessionFactoryOptions options) {
        // NOTE : DO NOT return EmptyInterceptor.INSTANCE from here as a "default for the Session"
        //       we "filter" that one out here.  The return from here should represent the
        //      explicitly configured Interceptor (if one).  Return null from here instead; Session
        //      will handle it

        if (interceptor != null && interceptor != EmptyInterceptor.INSTANCE) {
            return interceptor;
        }

        // prefer the SF-scoped interceptor, prefer that to any Session-scoped interceptor prototype
        final Interceptor optionsInterceptor = options.getInterceptor();
        if (optionsInterceptor != null && optionsInterceptor != EmptyInterceptor.INSTANCE) {
            return optionsInterceptor;
        }

        // then check the Session-scoped interceptor prototype
        final Class<? extends Interceptor> statelessInterceptorImplementor = options
                .getStatelessInterceptorImplementor();
        final Supplier<? extends Interceptor> statelessInterceptorImplementorSupplier = options
                .getStatelessInterceptorImplementorSupplier();
        if (statelessInterceptorImplementor != null && statelessInterceptorImplementorSupplier != null) {
            throw new HibernateException(
                    "A session scoped interceptor class or supplier are allowed, but not both!");
        } else if (statelessInterceptorImplementor != null) {
            try {
                /**
                 * We could remove the getStatelessInterceptorImplementor method and use just the getStatelessInterceptorImplementorSupplier
                 * since it can cover both cases when the user has given a Supplier<? extends Interceptor> or just the
                 * Class<? extends Interceptor>, in which case, we simply instantiate the Interceptor when calling the Supplier.
                 */
                return statelessInterceptorImplementor.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new HibernateException("Could not supply session-scoped SessionFactory Interceptor", e);
            }
        } else if (statelessInterceptorImplementorSupplier != null) {
            return statelessInterceptorImplementorSupplier.get();
        }

        return null;
    }

    static class SessionBuilderImpl<T extends SessionBuilder>
            implements SessionBuilderImplementor<T>, SessionCreationOptions {
        private static final Logger log = CoreLogging.logger(SessionBuilderImpl.class);

        private final SessionFactoryImpl sessionFactory;
        private Interceptor interceptor;
        private StatementInspector statementInspector;
        private Connection connection;
        private PhysicalConnectionHandlingMode connectionHandlingMode;
        private boolean autoJoinTransactions = true;
        private FlushMode flushMode;
        private boolean autoClose;
        private boolean autoClear;
        private String tenantIdentifier;
        private TimeZone jdbcTimeZone;
        private boolean queryParametersValidationEnabled;

        // Lazy: defaults can be built by invoking the builder in fastSessionServices.defaultSessionEventListeners
        // (Need a fresh build for each Session as the listener instances can't be reused across sessions)
        // Only initialize of the builder is overriding the default.
        private List<SessionEventListener> listeners;

        //todo : expose setting
        private SessionOwnerBehavior sessionOwnerBehavior = SessionOwnerBehavior.LEGACY_NATIVE;

        SessionBuilderImpl(SessionFactoryImpl sessionFactory) {
            this.sessionFactory = sessionFactory;

            // set up default builder values...
            final SessionFactoryOptions sessionFactoryOptions = sessionFactory.getSessionFactoryOptions();
            this.statementInspector = sessionFactoryOptions.getStatementInspector();
            this.connectionHandlingMode = sessionFactoryOptions.getPhysicalConnectionHandlingMode();
            this.autoClose = sessionFactoryOptions.isAutoCloseSessionEnabled();
            this.flushMode = sessionFactoryOptions.isFlushBeforeCompletionEnabled() ? FlushMode.AUTO
                    : FlushMode.MANUAL;

            final CurrentTenantIdentifierResolver currentTenantIdentifierResolver = sessionFactory
                    .getCurrentTenantIdentifierResolver();
            if (currentTenantIdentifierResolver != null) {
                tenantIdentifier = currentTenantIdentifierResolver.resolveCurrentTenantIdentifier();
            }
            this.jdbcTimeZone = sessionFactoryOptions.getJdbcTimeZone();
            this.queryParametersValidationEnabled = sessionFactoryOptions.isQueryParametersValidationEnabled();
        }

        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        // SessionCreationOptions

        @Override
        public SessionOwner getSessionOwner() {
            return null;
        }

        @Override
        public ExceptionMapper getExceptionMapper() {
            return sessionOwnerBehavior == SessionOwnerBehavior.LEGACY_JPA ? ExceptionMapperLegacyJpaImpl.INSTANCE
                    : null;
        }

        @Override
        public AfterCompletionAction getAfterCompletionAction() {
            return sessionOwnerBehavior == SessionOwnerBehavior.LEGACY_JPA
                    ? AfterCompletionActionLegacyJpaImpl.INSTANCE
                    : null;
        }

        @Override
        public ManagedFlushChecker getManagedFlushChecker() {
            return sessionOwnerBehavior == SessionOwnerBehavior.LEGACY_JPA
                    ? ManagedFlushCheckerLegacyJpaImpl.INSTANCE
                    : null;
        }

        @Override
        public boolean isQueryParametersValidationEnabled() {
            return this.queryParametersValidationEnabled;
        }

        @Override
        public boolean shouldAutoJoinTransactions() {
            return autoJoinTransactions;
        }

        @Override
        public FlushMode getInitialSessionFlushMode() {
            return flushMode;
        }

        @Override
        public boolean shouldAutoClose() {
            return autoClose;
        }

        @Override
        public boolean shouldAutoClear() {
            return autoClear;
        }

        @Override
        public Connection getConnection() {
            return connection;
        }

        @Override
        public Interceptor getInterceptor() {
            return configuredInterceptor(interceptor, sessionFactory.getSessionFactoryOptions());
        }

        @Override
        public StatementInspector getStatementInspector() {
            return statementInspector;
        }

        @Override
        public PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode() {
            return connectionHandlingMode;
        }

        @Override
        public String getTenantIdentifier() {
            return tenantIdentifier;
        }

        @Override
        public TimeZone getJdbcTimeZone() {
            return jdbcTimeZone;
        }

        @Override
        public List<SessionEventListener> getCustomSessionEventListener() {
            return listeners;
        }

        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        // SessionBuilder

        @Override
        public Session openSession() {
            log.tracef("Opening Hibernate Session.  tenant=%s", tenantIdentifier);
            return new SessionImpl(sessionFactory, this);
        }

        @Override
        @SuppressWarnings("unchecked")
        public T owner(SessionOwner sessionOwner) {
            throw new UnsupportedOperationException(
                    "SessionOwner was long deprecated and this method should no longer be invoked");
        }

        @Override
        @SuppressWarnings("unchecked")
        public T interceptor(Interceptor interceptor) {
            this.interceptor = interceptor;
            return (T) this;
        }

        @Override
        @SuppressWarnings("unchecked")
        public T noInterceptor() {
            this.interceptor = EmptyInterceptor.INSTANCE;
            return (T) this;
        }

        @Override
        @SuppressWarnings("unchecked")
        public T statementInspector(StatementInspector statementInspector) {
            this.statementInspector = statementInspector;
            return (T) this;
        }

        @Override
        @SuppressWarnings("unchecked")
        public T connection(Connection connection) {
            this.connection = connection;
            return (T) this;
        }

        @Override
        @SuppressWarnings("unchecked")
        public T connectionReleaseMode(ConnectionReleaseMode connectionReleaseMode) {
            // NOTE : Legacy behavior (when only ConnectionReleaseMode was exposed) was to always acquire a
            // Connection using ConnectionAcquisitionMode.AS_NEEDED..

            final PhysicalConnectionHandlingMode handlingMode = PhysicalConnectionHandlingMode
                    .interpret(ConnectionAcquisitionMode.AS_NEEDED, connectionReleaseMode);
            connectionHandlingMode(handlingMode);
            return (T) this;
        }

        @Override
        @SuppressWarnings("unchecked")
        public T connectionHandlingMode(PhysicalConnectionHandlingMode connectionHandlingMode) {
            this.connectionHandlingMode = connectionHandlingMode;
            return (T) this;
        }

        @Override
        @SuppressWarnings("unchecked")
        public T autoJoinTransactions(boolean autoJoinTransactions) {
            this.autoJoinTransactions = autoJoinTransactions;
            return (T) this;
        }

        @Override
        @SuppressWarnings("unchecked")
        public T autoClose(boolean autoClose) {
            this.autoClose = autoClose;
            return (T) this;
        }

        @Override
        @SuppressWarnings("unchecked")
        public T autoClear(boolean autoClear) {
            this.autoClear = autoClear;
            return (T) this;
        }

        @Override
        @SuppressWarnings("unchecked")
        public T flushMode(FlushMode flushMode) {
            this.flushMode = flushMode;
            return (T) this;
        }

        @Override
        @SuppressWarnings("unchecked")
        public T tenantIdentifier(String tenantIdentifier) {
            this.tenantIdentifier = tenantIdentifier;
            return (T) this;
        }

        @Override
        @SuppressWarnings("unchecked")
        public T eventListeners(SessionEventListener... listeners) {
            if (this.listeners == null) {
                this.listeners = sessionFactory.getSessionFactoryOptions().getBaselineSessionEventsListenerBuilder()
                        .buildBaselineList();
            }
            Collections.addAll(this.listeners, listeners);
            return (T) this;
        }

        @Override
        @SuppressWarnings("unchecked")
        public T clearEventListeners() {
            if (listeners == null) {
                //Needs to initialize explicitly to an empty list as otherwise "null" immplies the default listeners will be applied
                this.listeners = new ArrayList<SessionEventListener>(3);
            } else {
                listeners.clear();
            }
            return (T) this;
        }

        @Override
        public T jdbcTimeZone(TimeZone timeZone) {
            jdbcTimeZone = timeZone;
            return (T) this;
        }

        @Override
        public T setQueryParameterValidation(boolean enabled) {
            queryParametersValidationEnabled = enabled;
            return (T) this;
        }
    }

    public static class StatelessSessionBuilderImpl implements StatelessSessionBuilder, SessionCreationOptions {
        private final SessionFactoryImpl sessionFactory;
        private Connection connection;
        private String tenantIdentifier;
        private boolean queryParametersValidationEnabled;

        public StatelessSessionBuilderImpl(SessionFactoryImpl sessionFactory) {
            this.sessionFactory = sessionFactory;

            CurrentTenantIdentifierResolver tenantIdentifierResolver = sessionFactory
                    .getCurrentTenantIdentifierResolver();
            if (tenantIdentifierResolver != null) {
                tenantIdentifier = tenantIdentifierResolver.resolveCurrentTenantIdentifier();
            }
            queryParametersValidationEnabled = sessionFactory.getSessionFactoryOptions()
                    .isQueryParametersValidationEnabled();
        }

        @Override
        public StatelessSession openStatelessSession() {
            return new StatelessSessionImpl(sessionFactory, this);
        }

        @Override
        public StatelessSessionBuilder connection(Connection connection) {
            this.connection = connection;
            return this;
        }

        @Override
        public StatelessSessionBuilder tenantIdentifier(String tenantIdentifier) {
            this.tenantIdentifier = tenantIdentifier;
            return this;
        }

        @Override
        public boolean shouldAutoJoinTransactions() {
            return true;
        }

        @Override
        public FlushMode getInitialSessionFlushMode() {
            return FlushMode.ALWAYS;
        }

        @Override
        public boolean shouldAutoClose() {
            return false;
        }

        @Override
        public boolean shouldAutoClear() {
            return false;
        }

        @Override
        public Connection getConnection() {
            return connection;
        }

        @Override
        public Interceptor getInterceptor() {
            return configuredInterceptor(EmptyInterceptor.INSTANCE, sessionFactory.getSessionFactoryOptions());

        }

        @Override
        public StatementInspector getStatementInspector() {
            return null;
        }

        @Override
        public PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode() {
            return null;
        }

        @Override
        public String getTenantIdentifier() {
            return tenantIdentifier;
        }

        @Override
        public TimeZone getJdbcTimeZone() {
            return sessionFactory.getSessionFactoryOptions().getJdbcTimeZone();
        }

        @Override
        public List<SessionEventListener> getCustomSessionEventListener() {
            return null;
        }

        @Override
        public SessionOwner getSessionOwner() {
            return null;
        }

        @Override
        public ExceptionMapper getExceptionMapper() {
            return null;
        }

        @Override
        public AfterCompletionAction getAfterCompletionAction() {
            return null;
        }

        @Override
        public ManagedFlushChecker getManagedFlushChecker() {
            return null;
        }

        @Override
        public boolean isQueryParametersValidationEnabled() {
            return queryParametersValidationEnabled;
        }

        @Override
        public StatelessSessionBuilder setQueryParameterValidation(boolean enabled) {
            queryParametersValidationEnabled = enabled;
            return this;
        }
    }

    @Override
    public CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy() {
        return getSessionFactoryOptions().getCustomEntityDirtinessStrategy();
    }

    @Override
    public CurrentTenantIdentifierResolver getCurrentTenantIdentifierResolver() {
        return getSessionFactoryOptions().getCurrentTenantIdentifierResolver();
    }

    // Serialization handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /**
     * Custom serialization hook defined by Java spec.  Used when the factory is directly serialized
     *
     * @param out The stream into which the object is being serialized.
     *
     * @throws IOException Can be thrown by the stream
     */
    private void writeObject(ObjectOutputStream out) throws IOException {
        LOG.debugf("Serializing: %s", getUuid());
        out.defaultWriteObject();
        LOG.trace("Serialized");
    }

    /**
     * Custom serialization hook defined by Java spec.  Used when the factory is directly deserialized
     *
     * @param in The stream from which the object is being deserialized.
     *
     * @throws IOException Can be thrown by the stream
     * @throws ClassNotFoundException Again, can be thrown by the stream
     */
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        LOG.trace("Deserializing");
        in.defaultReadObject();
        LOG.debugf("Deserialized: %s", getUuid());
    }

    /**
     * Custom serialization hook defined by Java spec.  Used when the factory is directly deserialized.
     * Here we resolve the uuid/name read from the stream previously to resolve the SessionFactory
     * instance to use based on the registrations with the {@link SessionFactoryRegistry}
     *
     * @return The resolved factory to use.
     *
     * @throws InvalidObjectException Thrown if we could not resolve the factory by uuid/name.
     */
    private Object readResolve() throws InvalidObjectException {
        LOG.trace("Resolving serialized SessionFactory");
        return locateSessionFactoryOnDeserialization(getUuid(), name);
    }

    private static SessionFactory locateSessionFactoryOnDeserialization(String uuid, String name)
            throws InvalidObjectException {
        final SessionFactory uuidResult = SessionFactoryRegistry.INSTANCE.getSessionFactory(uuid);
        if (uuidResult != null) {
            LOG.debugf("Resolved SessionFactory by UUID [%s]", uuid);
            return uuidResult;
        }

        // in case we were deserialized in a different JVM, look for an instance with the same name
        // (provided we were given a name)
        if (name != null) {
            final SessionFactory namedResult = SessionFactoryRegistry.INSTANCE.getNamedSessionFactory(name);
            if (namedResult != null) {
                LOG.debugf("Resolved SessionFactory by name [%s]", name);
                return namedResult;
            }
        }

        throw new InvalidObjectException("Could not find a SessionFactory [uuid=" + uuid + ",name=" + name + "]");
    }

    /**
     * Custom serialization hook used during Session serialization.
     *
     * @param oos The stream to which to write the factory
     * @throws IOException Indicates problems writing out the serial data stream
     */
    void serialize(ObjectOutputStream oos) throws IOException {
        oos.writeUTF(getUuid());
        oos.writeBoolean(name != null);
        if (name != null) {
            oos.writeUTF(name);
        }
    }

    /**
     * Custom deserialization hook used during Session deserialization.
     *
     * @param ois The stream from which to "read" the factory
     * @return The deserialized factory
     * @throws IOException indicates problems reading back serial data stream
     * @throws ClassNotFoundException indicates problems reading back serial data stream
     */
    static SessionFactoryImpl deserialize(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        LOG.trace("Deserializing SessionFactory from Session");
        final String uuid = ois.readUTF();
        boolean isNamed = ois.readBoolean();
        final String name = isNamed ? ois.readUTF() : null;
        return (SessionFactoryImpl) locateSessionFactoryOnDeserialization(uuid, name);
    }

    private void maskOutSensitiveInformation(Map<String, Object> props) {
        maskOutIfSet(props, AvailableSettings.JPA_JDBC_USER);
        maskOutIfSet(props, AvailableSettings.JPA_JDBC_PASSWORD);
        maskOutIfSet(props, AvailableSettings.USER);
        maskOutIfSet(props, AvailableSettings.PASS);
    }

    private void maskOutIfSet(Map<String, Object> props, String setting) {
        if (props.containsKey(setting)) {
            props.put(setting, "****");
        }
    }

    private void logIfEmptyCompositesEnabled(Map<String, Object> props) {
        final boolean isEmptyCompositesEnabled = ConfigurationHelper
                .getBoolean(AvailableSettings.CREATE_EMPTY_COMPOSITES_ENABLED, props, false);
        if (isEmptyCompositesEnabled) {
            // It would be nice to do this logging in ComponentMetamodel, where
            // AvailableSettings.CREATE_EMPTY_COMPOSITES_ENABLED is actually used.
            // Unfortunately that would end up logging a message several times for
            // each embeddable/composite. Doing it here will log the message only
            // once.
            LOG.emptyCompositesEnabled();
        }
    }

    /**
     * @return the FastSessionServices for this SessionFactory.
     */
    FastSessionServices getFastSessionServices() {
        return this.fastSessionServices;
    }

}