All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.quarkus.hibernate.orm.runtime.boot.FastBootMetadataBuilder Maven / Gradle / Ivy

package io.quarkus.hibernate.orm.runtime.boot;

import static org.hibernate.cfg.AvailableSettings.CLASS_CACHE_PREFIX;
import static org.hibernate.cfg.AvailableSettings.COLLECTION_CACHE_PREFIX;
import static org.hibernate.cfg.AvailableSettings.DRIVER;
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_TRANSACTION_TYPE;
import static org.hibernate.cfg.AvailableSettings.PASS;
import static org.hibernate.cfg.AvailableSettings.PERSISTENCE_UNIT_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.cfg.AvailableSettings.XML_MAPPING_ENABLED;
import static org.hibernate.internal.HEMLogging.messageLogger;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import javax.persistence.PersistenceException;
import javax.persistence.spi.PersistenceUnitTransactionType;

import org.hibernate.boot.CacheRegionDefinition;
import org.hibernate.boot.MetadataBuilder;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.archive.scan.internal.StandardScanOptions;
import org.hibernate.boot.archive.scan.spi.Scanner;
import org.hibernate.boot.internal.MetadataImpl;
import org.hibernate.boot.model.process.spi.ManagedResources;
import org.hibernate.boot.model.process.spi.MetadataBuildingProcess;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.selector.spi.StrategySelector;
import org.hibernate.boot.spi.MetadataBuilderContributor;
import org.hibernate.boot.spi.MetadataBuilderImplementor;
import org.hibernate.cache.internal.CollectionCacheInvalidator;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.beanvalidation.BeanValidationIntegrator;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.engine.jdbc.dialect.spi.DialectFactory;
import org.hibernate.id.factory.spi.MutableIdentifierGeneratorFactory;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.internal.EntityManagerMessageLogger;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
import org.hibernate.jpa.boot.internal.StandardJpaScanEnvironmentImpl;
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
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.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl;
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl;
import org.hibernate.service.Service;
import org.hibernate.service.internal.AbstractServiceRegistryImpl;
import org.hibernate.service.internal.ProvidedService;
import org.infinispan.quarkus.hibernate.cache.QuarkusInfinispanRegionFactory;

import io.quarkus.hibernate.orm.runtime.BuildTimeSettings;
import io.quarkus.hibernate.orm.runtime.IntegrationSettings;
import io.quarkus.hibernate.orm.runtime.boot.xml.RecordableXmlMapping;
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticDescriptor;
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticInitListener;
import io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy;
import io.quarkus.hibernate.orm.runtime.proxies.PreGeneratedProxies;
import io.quarkus.hibernate.orm.runtime.proxies.ProxyDefinitions;
import io.quarkus.hibernate.orm.runtime.recording.PrevalidatedQuarkusMetadata;
import io.quarkus.hibernate.orm.runtime.recording.RecordableBootstrap;
import io.quarkus.hibernate.orm.runtime.recording.RecordedState;
import io.quarkus.hibernate.orm.runtime.recording.RecordingDialectFactory;
import io.quarkus.hibernate.orm.runtime.tenant.HibernateMultiTenantConnectionProvider;

/**
 * Alternative to EntityManagerFactoryBuilderImpl so to have full control of how MetadataBuilderImplementor
 * is created, which configuration properties are supportable, custom overrides, etc...
 */
public class FastBootMetadataBuilder {

    /**
     * Old deprecated constants:
     */
    @Deprecated
    private static final String JACC_PREFIX = "hibernate.jacc";
    @Deprecated
    private static final String JACC_ENABLED = "hibernate.jacc.enabled";
    @Deprecated
    private static final String WRAP_RESULT_SETS = "hibernate.jdbc.wrap_result_sets";

    private static final EntityManagerMessageLogger LOG = messageLogger(FastBootMetadataBuilder.class);

    private final PersistenceUnitDescriptor persistenceUnit;
    private final BuildTimeSettings buildTimeSettings;
    private final StandardServiceRegistry standardServiceRegistry;
    private final ManagedResources managedResources;
    private final MetadataBuilderImplementor metamodelBuilder;
    private final Collection> additionalIntegrators;
    private final Collection providedServices;
    private final PreGeneratedProxies preGeneratedProxies;
    private final Optional dataSource;
    private final boolean isReactive;
    private final boolean fromPersistenceXml;
    private final List integrationStaticDescriptors;

    @SuppressWarnings("unchecked")
    public FastBootMetadataBuilder(final QuarkusPersistenceUnitDefinition puDefinition, Scanner scanner,
            Collection> additionalIntegrators, PreGeneratedProxies preGeneratedProxies) {
        this.persistenceUnit = puDefinition.getActualHibernateDescriptor();
        this.dataSource = puDefinition.getDataSource();
        this.isReactive = puDefinition.isReactive();
        this.fromPersistenceXml = puDefinition.isFromPersistenceXml();
        this.additionalIntegrators = additionalIntegrators;
        this.preGeneratedProxies = preGeneratedProxies;
        this.integrationStaticDescriptors = puDefinition.getIntegrationStaticDescriptors();

        // Copying semantics from: new EntityManagerFactoryBuilderImpl( unit,
        // integration, instance );
        // Except we remove support for several legacy features and XML binding

        LogHelper.logPersistenceUnitInformation(persistenceUnit);

        final RecordableBootstrap ssrBuilder = RecordableBootstrapFactory.createRecordableBootstrapBuilder(puDefinition);

        final MergedSettings mergedSettings = mergeSettings(puDefinition);
        this.buildTimeSettings = new BuildTimeSettings(mergedSettings.getConfigurationValues());

        // Build the "standard" service registry
        ssrBuilder.applySettings(buildTimeSettings.getSettings());
        // We don't add unsupported properties to mergedSettings/buildTimeSettings,
        // so that we can more easily differentiate between
        // properties coming from Quarkus and "unsupported" properties
        // on startup (see io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.buildRuntimeSettings)
        for (Map.Entry entry : puDefinition.getQuarkusConfigUnsupportedProperties().entrySet()) {
            var key = entry.getKey();
            if (buildTimeSettings.get(key) != null) {
                // Ignore properties that were already set by Quarkus;
                // we'll log a warning about those on startup.
                // (see io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.buildRuntimeSettings)
                continue;
            }
            ssrBuilder.applySetting(key, entry.getValue());
        }
        this.standardServiceRegistry = ssrBuilder.build();
        registerIdentifierGenerators(standardServiceRegistry);

        this.providedServices = ssrBuilder.getProvidedServices();

        /**
         * This is required to properly integrate Hibernate Envers.
         *
         * The EnversService requires multiple steps to be properly built, the most important ones are:
         *
         * 1. The EnversServiceContributor contributes the EnversServiceInitiator to the RecordableBootstrap.
         * 2. After RecordableBootstrap builds a StandardServiceRegistry, the first time the EnversService is
         * requested, it is created by the initiator and configured by the registry.
         * 3. The MetadataBuildingProcess completes by calling the AdditionalJaxbMappingProducer which
         * initializes the EnversService and produces some additional mapping documents.
         * 4. After that point the EnversService appears to be fully functional.
         *
         * The following trick uses the aforementioned steps to set up the EnversService and then turns it into
         * a ProvidedService so that it is not necessary to repeat all these complex steps during the reactivation
         * of the destroyed service registry in PreconfiguredServiceRegistryBuilder.
         *
         */
        for (Class postBuildProvidedService : ssrBuilder.getPostBuildProvidedServices()) {
            providedServices.add(new ProvidedService(postBuildProvidedService,
                    standardServiceRegistry.getService(postBuildProvidedService)));
        }

        final MetadataSources metadataSources = new MetadataSources(ssrBuilder.getBootstrapServiceRegistry());
        // No need to populate annotatedClassNames/annotatedPackages: they are populated through scanning
        // XML mappings, however, cannot be contributed through the scanner,
        // which only allows specifying mappings as files/resources,
        // and we really don't want any XML parsing here...
        for (RecordableXmlMapping mapping : puDefinition.getXmlMappings()) {
            metadataSources.addXmlBinding(mapping.toHibernateOrmBinding());
        }

        this.metamodelBuilder = (MetadataBuilderImplementor) metadataSources
                .getMetadataBuilder(standardServiceRegistry);
        if (scanner != null) {
            this.metamodelBuilder.applyScanner(scanner);
        }
        populate(metamodelBuilder, mergedSettings.cacheRegionDefinitions);

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

        applyMetadataBuilderContributor();

        // Unable to automatically handle:
        // AvailableSettings.ENHANCER_ENABLE_DIRTY_TRACKING,
        // AvailableSettings.ENHANCER_ENABLE_LAZY_INITIALIZATION,
        // AvailableSettings.ENHANCER_ENABLE_ASSOCIATION_MANAGEMENT

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

        final MultiTenancyStrategy multiTenancyStrategy = puDefinition.getMultitenancyStrategy();
        if (multiTenancyStrategy != null && multiTenancyStrategy != MultiTenancyStrategy.NONE) {
            ssrBuilder.addService(MultiTenantConnectionProvider.class,
                    new HibernateMultiTenantConnectionProvider(puDefinition.getName()));
        }
    }

    /**
     * Simplified copy of
     * org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl#mergeSettings(org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor,
     * java.util.Map, org.hibernate.boot.registry.StandardServiceRegistryBuilder)
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private MergedSettings mergeSettings(QuarkusPersistenceUnitDefinition puDefinition) {
        PersistenceUnitDescriptor persistenceUnit = puDefinition.getActualHibernateDescriptor();
        final MergedSettings mergedSettings = new MergedSettings();
        final Map cfg = mergedSettings.configurationValues;

        // first, apply persistence.xml-defined settings
        if (persistenceUnit.getProperties() != null) {
            cfg.putAll(persistenceUnit.getProperties());
        }

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

        MultiTenancyStrategy multiTenancyStrategy = puDefinition.getMultitenancyStrategy();
        if (multiTenancyStrategy != null) {
            cfg.put(AvailableSettings.MULTI_TENANT, multiTenancyStrategy);
        }

        applyTransactionProperties(persistenceUnit, cfg);
        applyJdbcConnectionProperties(cfg);

        // unsupported FLUSH_BEFORE_COMPLETION

        if (readBooleanConfigurationValue(cfg, AvailableSettings.FLUSH_BEFORE_COMPLETION)) {
            cfg.put(AvailableSettings.FLUSH_BEFORE_COMPLETION, "false");
            LOG.definingFlushBeforeCompletionIgnoredInHem(AvailableSettings.FLUSH_BEFORE_COMPLETION);
        }

        // Quarkus specific

        cfg.put("hibernate.temp.use_jdbc_metadata_defaults", "false");

        //This shouldn't be encouraged, but sometimes it's really useful - and it used to be the default
        //in Hibernate ORM before the JPA spec would require to change this.
        //At this time of transitioning we'll only expose it as a global system property, so to allow usage
        //for special circumstances and yet not encourage this.
        //Also, definitely don't override anything which was explicitly set in the configuration.
        if (!cfg.containsKey(AvailableSettings.ALLOW_UPDATE_OUTSIDE_TRANSACTION)) {
            cfg.put(AvailableSettings.ALLOW_UPDATE_OUTSIDE_TRANSACTION,
                    Boolean.getBoolean(AvailableSettings.ALLOW_UPDATE_OUTSIDE_TRANSACTION));
        }

        //Enable the new Enhanced Proxies capability (unless it was specifically disabled):
        if (!cfg.containsKey(AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY)) {
            cfg.put(AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, Boolean.TRUE.toString());
        }
        //Always Order batch updates as it prevents contention on the data (unless it was disabled)
        if (!cfg.containsKey(AvailableSettings.ORDER_UPDATES)) {
            cfg.put(AvailableSettings.ORDER_UPDATES, Boolean.TRUE.toString());
        }
        //Agroal already does disable auto-commit, so Hibernate ORM should trust that:
        cfg.put(AvailableSettings.CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT, Boolean.TRUE.toString());

        /*
         * Set CONNECTION_HANDLING to DELAYED_ACQUISITION_AND_RELEASE_BEFORE_TRANSACTION_COMPLETION
         * as it generally performs better, at no known drawbacks.
         * This is a new mode in Hibernate ORM, it might become the default in the future.
         *
         * Note: other connection handling modes lead to leaked resources, statements in particular.
         * See https://github.com/quarkusio/quarkus/issues/7242, https://github.com/quarkusio/quarkus/issues/13273
         *
         * @see org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode
         */
        cfg.putIfAbsent(AvailableSettings.CONNECTION_HANDLING,
                PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_BEFORE_TRANSACTION_COMPLETION);

        if (readBooleanConfigurationValue(cfg, WRAP_RESULT_SETS)) {
            LOG.warn("Wrapping result sets is no longer supported by Hibernate ORM. Setting " + WRAP_RESULT_SETS
                    + " is being ignored.");
        }
        cfg.put(WRAP_RESULT_SETS, "false");

        // XML mapping support can be costly, so we only enable it when XML mappings are actually used
        // or when integrations (e.g. Envers) need it.
        List integrationsRequiringXmlMapping = integrationStaticDescriptors.stream()
                .filter(HibernateOrmIntegrationStaticDescriptor::isXmlMappingRequired)
                .map(HibernateOrmIntegrationStaticDescriptor::getIntegrationName).collect(Collectors.toList());
        Optional xmlMappingEnabledOptional = readOptionalBooleanConfigurationValue(cfg, XML_MAPPING_ENABLED);
        if (!puDefinition.getXmlMappings().isEmpty() || !integrationsRequiringXmlMapping.isEmpty()) {
            if (xmlMappingEnabledOptional.isPresent() && !xmlMappingEnabledOptional.get()) {
                // Explicitly disabled even though we need it...
                LOG.warnf("XML mapping is necessary in persistence unit '%s':"
                        + " %d XML mapping files are used, and %d extensions require XML mapping (%s)."
                        + " Setting '%s' to false.",
                        persistenceUnit.getName(), XML_MAPPING_ENABLED,
                        puDefinition.getXmlMappings().size(), integrationsRequiringXmlMapping.size(),
                        integrationsRequiringXmlMapping);
            }
            cfg.put(XML_MAPPING_ENABLED, "true");
        } else {
            if (xmlMappingEnabledOptional.isPresent() && xmlMappingEnabledOptional.get()) {
                // Explicitly enabled even though we do not need it...
                LOG.warnf("XML mapping is not necessary in persistence unit '%s'."
                        + " Setting '%s' to false.",
                        persistenceUnit.getName(), XML_MAPPING_ENABLED);
            }
            cfg.put(XML_MAPPING_ENABLED, "false");
        }

        // Note: this one is not a boolean, just having the property enables it
        if (cfg.containsKey(JACC_ENABLED)) {
            LOG.warn("JACC integration is no longer supported by Hibernate ORM. Option ignored.");
            cfg.remove(JACC_ENABLED);
        }

        // 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 = cfg.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)) {
                    LOG.warn(
                            "Found JACC permission grant [%s] in properties, but JACC is no longer supported by Hibernate ORM: ignoring!");
                } 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));
                } else if (keyString.startsWith("hibernate.ejb.classcache")) {
                    LOG.warn(
                            "Deprecated configuration property prefixed by 'hibernate.ejb.classcache' is being ignored. Suggestion: change prefix to 'hibernate.classcache'");
                } else if (keyString.startsWith("hibernate.ejb.collectioncache")) {
                    LOG.warn(
                            "Deprecated configuration property prefixed by 'hibernate.ejb.collectioncache' is being ignored. Suggestion: change prefix to 'hibernate.collectioncache'");
                }
            }
        }

        cfg.put(org.hibernate.cfg.AvailableSettings.CACHE_REGION_FACTORY,
                QuarkusInfinispanRegionFactory.class.getName());

        for (HibernateOrmIntegrationStaticDescriptor descriptor : integrationStaticDescriptors) {
            Optional listenerOptional = descriptor.getInitListener();
            if (listenerOptional.isPresent()) {
                listenerOptional.get().contributeBootProperties(cfg::put);
            }
        }

        return mergedSettings;
    }

    public RecordedState build() {
        MetadataImpl fullMeta = (MetadataImpl) MetadataBuildingProcess.complete(
                managedResources,
                metamodelBuilder.getBootstrapContext(),
                metamodelBuilder.getMetadataBuildingOptions() //INTERCEPT & DESTROY :)
        );

        IntegrationSettings.Builder integrationSettingsBuilder = new IntegrationSettings.Builder();

        for (HibernateOrmIntegrationStaticDescriptor descriptor : integrationStaticDescriptors) {
            Optional listenerOptional = descriptor.getInitListener();
            if (listenerOptional.isPresent()) {
                listenerOptional.get().onMetadataInitialized(fullMeta, metamodelBuilder.getBootstrapContext(),
                        integrationSettingsBuilder::put);
            }
        }

        Dialect dialect = extractDialect();
        PrevalidatedQuarkusMetadata storeableMetadata = trimBootstrapMetadata(fullMeta);
        //Make sure that the service is destroyed after the metadata has been validated and trimmed, as validation needs to use it.
        destroyServiceRegistry();
        ProxyDefinitions proxyClassDefinitions = ProxyDefinitions.createFromMetadata(storeableMetadata, preGeneratedProxies);
        return new RecordedState(dialect, storeableMetadata, buildTimeSettings, getIntegrators(),
                providedServices, integrationSettingsBuilder.build(), proxyClassDefinitions, dataSource,
                isReactive, fromPersistenceXml);
    }

    private void destroyServiceRegistry() {
        final AbstractServiceRegistryImpl serviceRegistry = (AbstractServiceRegistryImpl) metamodelBuilder.getBootstrapContext()
                .getServiceRegistry();
        serviceRegistry.close();
        serviceRegistry.resetParent(null);
    }

    private PrevalidatedQuarkusMetadata trimBootstrapMetadata(MetadataImpl fullMeta) {
        MetadataImpl replacement = new MetadataImpl(
                fullMeta.getUUID(),
                fullMeta.getMetadataBuildingOptions(), //TODO Replace this
                fullMeta.getIdentifierGeneratorFactory(),
                fullMeta.getEntityBindingMap(),
                fullMeta.getMappedSuperclassMap(),
                fullMeta.getCollectionBindingMap(),
                fullMeta.getTypeDefinitionMap(),
                fullMeta.getFilterDefinitions(),
                fullMeta.getFetchProfileMap(),
                fullMeta.getImports(), // ok
                fullMeta.getIdGeneratorDefinitionMap(),
                fullMeta.getNamedQueryMap(),
                fullMeta.getNamedNativeQueryMap(), // TODO // might contain references to org.hibernate.loader.custom.ConstructorResultColumnProcessor, org.hibernate.type.TypeStandardSQLFunction
                fullMeta.getNamedProcedureCallMap(),
                fullMeta.getSqlResultSetMappingMap(), //TODO might contain NativeSQLQueryReturn (as namedNativeQueryMap above)
                fullMeta.getNamedEntityGraphs(), //TODO //reference to *annotation* instance ! FIXME or ignore feature?
                fullMeta.getSqlFunctionMap(), //ok
                fullMeta.getDatabase(), //Cleaned up: used to include references to MetadataBuildingOptions, etc..
                fullMeta.getBootstrapContext() //FIXME WHOA!
        );

        return PrevalidatedQuarkusMetadata.validateAndWrap(replacement);
    }

    private Dialect extractDialect() {
        DialectFactory service = standardServiceRegistry.getService(DialectFactory.class);
        RecordingDialectFactory casted = (RecordingDialectFactory) service;
        return casted.getDialect();
    }

    private Collection getIntegrators() {
        LinkedHashSet integrators = new LinkedHashSet<>();
        integrators.add(new BeanValidationIntegrator());
        integrators.add(new CollectionCacheInvalidator());

        for (Class integratorClass : additionalIntegrators) {
            try {
                integrators.add(integratorClass.getConstructor().newInstance());
            } catch (Exception e) {
                throw new IllegalArgumentException("Unable to instantiate integrator " + integratorClass, e);
            }
        }

        return integrators;
    }

    @SuppressWarnings("rawtypes")
    private static class MergedSettings {
        private final Map configurationValues = new ConcurrentHashMap(16, 0.75f, 1);
        private List cacheRegionDefinitions;

        private MergedSettings() {
        }

        public Map getConfigurationValues() {
            return configurationValues;
        }

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

    @SuppressWarnings("rawtypes")
    private boolean readBooleanConfigurationValue(Map configurationValues, String propertyName) {
        Object propertyValue = configurationValues.get(propertyName);
        return propertyValue != null && Boolean.parseBoolean(propertyValue.toString());
    }

    @SuppressWarnings("rawtypes")
    private Optional readOptionalBooleanConfigurationValue(Map configurationValues, String propertyName) {
        Object propertyValue = configurationValues.get(propertyName);
        return propertyValue == null ? Optional.empty() : Optional.of(Boolean.parseBoolean(propertyValue.toString()));
    }

    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 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 void applyJdbcConnectionProperties(Map configurationValues) {
        final String driver = (String) configurationValues.get(JPA_JDBC_DRIVER);
        if (StringHelper.isNotEmpty(driver)) {
            configurationValues.put(DRIVER, driver);
        }
        final String url = (String) configurationValues.get(JPA_JDBC_URL);
        if (StringHelper.isNotEmpty(url)) {
            configurationValues.put(URL, url);
        }
        final String user = (String) configurationValues.get(JPA_JDBC_USER);
        if (StringHelper.isNotEmpty(user)) {
            configurationValues.put(USER, user);
        }
        final String pass = (String) configurationValues.get(JPA_JDBC_PASSWORD);
        if (StringHelper.isNotEmpty(pass)) {
            configurationValues.put(PASS, pass);
        }
    }

    private static void applyTransactionProperties(PersistenceUnitDescriptor persistenceUnit,
            Map configurationValues) {
        PersistenceUnitTransactionType transactionType = PersistenceUnitTransactionTypeHelper
                .interpretTransactionType(configurationValues.get(JPA_TRANSACTION_TYPE));
        if (transactionType == null) {
            transactionType = persistenceUnit.getTransactionType();
        }
        if (transactionType == null) {
            // is it more appropriate to have this be based on bootstrap entry point (EE vs SE)?
            transactionType = PersistenceUnitTransactionType.RESOURCE_LOCAL;
        }
        boolean hasTransactionStrategy = configurationValues.containsKey(TRANSACTION_COORDINATOR_STRATEGY);
        if (hasTransactionStrategy) {
            LOG.overridingTransactionStrategyDangerous(TRANSACTION_COORDINATOR_STRATEGY);
        } else {
            if (transactionType == PersistenceUnitTransactionType.JTA) {
                configurationValues.put(TRANSACTION_COORDINATOR_STRATEGY,
                        JtaTransactionCoordinatorBuilderImpl.class);
            } else if (transactionType == PersistenceUnitTransactionType.RESOURCE_LOCAL) {
                configurationValues.put(TRANSACTION_COORDINATOR_STRATEGY,
                        JdbcResourceLocalTransactionCoordinatorBuilderImpl.class);
            }
        }
    }

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

        // apply id generators
        final Object idGeneratorStrategyProviderSetting = buildTimeSettings
                .get(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> entry : idGeneratorStrategyProvider.getStrategies().entrySet()) {
                identifierGeneratorFactory.register(entry.getKey(), entry.getValue());
            }
        }
    }

    /**
     * Greatly simplified copy of
     * org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl#populate(org.hibernate.boot.MetadataBuilder,
     * org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.MergedSettings,
     * org.hibernate.boot.registry.StandardServiceRegistry, java.util.List)
     */
    protected void populate(MetadataBuilder metamodelBuilder, List cacheRegionDefinitions) {

        ((MetadataBuilderImplementor) metamodelBuilder).getBootstrapContext().markAsJpaBootstrap();

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

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

        final TypeContributorList typeContributorList = (TypeContributorList) buildTimeSettings
                .get(EntityManagerFactoryBuilderImpl.TYPE_CONTRIBUTORS);
        if (typeContributorList != null) {
            typeContributorList.getTypeContributors().forEach(metamodelBuilder::applyTypes);
        }
    }

    private void applyMetadataBuilderContributor() {
        Object metadataBuilderContributorSetting = buildTimeSettings
                .get(EntityManagerFactoryBuilderImpl.METADATA_BUILDER_CONTRIBUTOR);

        if (metadataBuilderContributorSetting == null) {
            return;
        }

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

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

    @SuppressWarnings("unchecked")
    private  T loadSettingInstance(String settingName, Object settingValue, Class clazz) {
        T instance = null;
        Class instanceClass = null;

        if (clazz.isAssignableFrom(settingValue.getClass())) {
            instance = (T) settingValue;
        } else if (settingValue instanceof Class) {
            instanceClass = (Class) 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) 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.getConstructor().newInstance();
            } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
                throw new IllegalArgumentException(
                        "The " + clazz.getSimpleName() + " class [" + instanceClass + "] could not be instantiated!",
                        e);
            }
        }

        return instance;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy