org.infinispan.hibernate.cache.v53.InfinispanRegionFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.infinispan.hibernate.cache.v53.InfinispanRegionFactory.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.infinispan.hibernate.cache.v53;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;

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.SessionFactoryOptions;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext;
import org.hibernate.cache.cfg.spi.DomainDataRegionConfig;
import org.hibernate.cache.internal.DefaultCacheKeysFactory;
import org.hibernate.cache.spi.AbstractRegionFactory;
import org.hibernate.cache.spi.CacheKeysFactory;
import org.hibernate.cache.spi.CacheTransactionSynchronization;
import org.hibernate.cache.spi.DomainDataRegion;
import org.hibernate.cache.spi.QueryResultsRegion;
import org.hibernate.cache.spi.RegionFactory;
import org.hibernate.cache.spi.SecondLevelCacheLogger;
import org.hibernate.cache.spi.TimestampsRegion;
import org.hibernate.cache.spi.access.AccessType;
import org.hibernate.cache.spi.support.RegionNameQualifier;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.service.ServiceRegistry;
import org.infinispan.AdvancedCache;
import org.infinispan.commands.module.ModuleCommandFactory;
import org.infinispan.commons.util.FileLookup;
import org.infinispan.commons.util.FileLookupFactory;
import org.infinispan.commons.util.Util;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
import org.infinispan.configuration.parsing.ParserRegistry;
import org.infinispan.factories.GlobalComponentRegistry;
import org.infinispan.hibernate.cache.commons.DataType;
import org.infinispan.hibernate.cache.commons.DefaultCacheManagerProvider;
import org.infinispan.hibernate.cache.spi.InfinispanProperties;
import org.infinispan.hibernate.cache.commons.TimeSource;
import org.infinispan.hibernate.cache.commons.InfinispanBaseRegion;
import org.infinispan.hibernate.cache.commons.util.CacheCommandFactory;
import org.infinispan.hibernate.cache.commons.util.Caches;
import org.infinispan.hibernate.cache.commons.util.InfinispanMessageLogger;
import org.infinispan.hibernate.cache.v53.impl.DomainDataRegionImpl;
import org.infinispan.hibernate.cache.v53.impl.QueryResultsRegionImpl;
import org.infinispan.hibernate.cache.v53.impl.ClusteredTimestampsRegionImpl;
import org.infinispan.hibernate.cache.v53.impl.Sync;
import org.infinispan.hibernate.cache.v53.impl.TimestampsRegionImpl;
import org.infinispan.hibernate.cache.spi.EmbeddedCacheManagerProvider;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.transaction.TransactionMode;

/**
 * A {@link RegionFactory} for <a href="http://www.jboss.org/infinispan">Infinispan</a>-backed cache
 * regions.
 *
 * @author Chris Bredesen
 * @author Galder Zamarreo
 * @since 3.5
 */
public class InfinispanRegionFactory implements RegionFactory, TimeSource, InfinispanProperties {
    private static final InfinispanMessageLogger log = InfinispanMessageLogger.Provider
            .getLog(InfinispanRegionFactory.class);

    /**
     * Defines custom mapping for regionName -> cacheName and also DataType.key -> cacheName
     * (for the case that you want to change the cache configuration for whole type)
     */
    protected final Map<String, String> baseConfigurations = new HashMap<>();
    /**
     * Defines configuration properties applied on top of configuration set in any file, by regionName or DataType.key
     */
    protected final Map<String, ConfigurationBuilder> configOverrides = new HashMap<>();

    private CacheKeysFactory cacheKeysFactory;
    private final Map<DataType, Configuration> dataTypeConfigurations = new HashMap<>();
    private EmbeddedCacheManager manager;

    private List<InfinispanBaseRegion> regions = new ArrayList<>();
    private SessionFactoryOptions settings;

    private Boolean globalStats;

    /**
     * Create a new instance using the default configuration.
     */
    public InfinispanRegionFactory() {
    }

    /**
     * Create a new instance using conifguration properties in <code>props</code>.
     *
     * @param props Environmental properties; currently unused.
     */
    @SuppressWarnings("UnusedParameters")
    public InfinispanRegionFactory(Properties props) {
        this();
    }

    @Override
    public DomainDataRegion buildDomainDataRegion(DomainDataRegionConfig regionConfig,
            DomainDataRegionBuildingContext buildingContext) {
        log.debugf("Building domain data region [%s] entities=%s collections=%s naturalIds=%s",
                regionConfig.getRegionName(), regionConfig.getEntityCaching(), regionConfig.getCollectionCaching(),
                regionConfig.getNaturalIdCaching());
        // TODO: data type is probably deprecated, but we need it for backwards-compatible configuration
        DataType dataType;
        int entities = regionConfig.getEntityCaching().size();
        int collections = regionConfig.getCollectionCaching().size();
        int naturalIds = regionConfig.getNaturalIdCaching().size();
        if (entities > 0 && collections == 0 && naturalIds == 0) {
            dataType = regionConfig.getEntityCaching().stream().allMatch(c -> !c.isMutable())
                    ? DataType.IMMUTABLE_ENTITY
                    : DataType.ENTITY;
        } else if (entities == 0 && collections > 0 && naturalIds == 0) {
            dataType = DataType.COLLECTION;
        } else if (entities == 0 && collections == 0 && naturalIds > 0) {
            dataType = DataType.NATURAL_ID;
        } else {
            // some mix, let's use entity
            dataType = DataType.ENTITY;
        }

        AdvancedCache cache = getCache(qualify(regionConfig.getRegionName()), regionConfig.getRegionName(),
                dataType, Collections.emptyList());
        DomainDataRegionImpl region = new DomainDataRegionImpl(cache, regionConfig, this, getCacheKeysFactory());
        startRegion(region);
        return region;
    }

    @Override
    public QueryResultsRegion buildQueryResultsRegion(String regionName, SessionFactoryImplementor sessionFactory) {
        log.debugf("Building query results cache region [%s]", regionName);

        List<String> legacyUnqualifiedNames = regionName
                .equals(RegionFactory.DEFAULT_QUERY_RESULTS_REGION_UNQUALIFIED_NAME)
                        ? AbstractRegionFactory.LEGACY_QUERY_RESULTS_REGION_UNQUALIFIED_NAMES
                        : Collections.emptyList();
        AdvancedCache cache = getCache(qualify(regionName), regionName, DataType.QUERY, legacyUnqualifiedNames);
        QueryResultsRegionImpl region = new QueryResultsRegionImpl(cache, regionName, this);
        startRegion(region);
        return region;
    }

    @Override
    public TimestampsRegion buildTimestampsRegion(String regionName, SessionFactoryImplementor sessionFactory) {
        log.debugf("Building timestamps cache region [%s]", regionName);

        List<String> legacyUnqualifiedNames = regionName
                .equals(RegionFactory.DEFAULT_UPDATE_TIMESTAMPS_REGION_UNQUALIFIED_NAME)
                        ? AbstractRegionFactory.LEGACY_UPDATE_TIMESTAMPS_REGION_UNQUALIFIED_NAMES
                        : Collections.emptyList();
        final AdvancedCache cache = getCache(qualify(regionName), regionName, DataType.TIMESTAMPS,
                legacyUnqualifiedNames);
        TimestampsRegionImpl region = createTimestampsRegion(cache, regionName);
        startRegion(region);
        return region;
    }

    protected TimestampsRegionImpl createTimestampsRegion(AdvancedCache cache, String regionName) {
        if (Caches.isClustered(cache)) {
            return new ClusteredTimestampsRegionImpl(cache, regionName, this);
        } else {
            return new TimestampsRegionImpl(cache, regionName, this);
        }
    }

    public Configuration getPendingPutsCacheConfiguration() {
        return dataTypeConfigurations.get(DataType.PENDING_PUTS);
    }

    protected CacheKeysFactory getCacheKeysFactory() {
        return cacheKeysFactory;
    }

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

    @Override
    public AccessType getDefaultAccessType() {
        return AccessType.TRANSACTIONAL;
    }

    @Override
    public String qualify(String regionName) {
        return RegionNameQualifier.INSTANCE.qualify(regionName, settings);
    }

    @Override
    public CacheTransactionSynchronization createTransactionContext(SharedSessionContractImplementor session) {
        return new Sync(this);
    }

    @Override
    public long nextTimestamp() {
        return System.currentTimeMillis();
    }

    public void setCacheManager(EmbeddedCacheManager manager) {
        this.manager = manager;
    }

    public EmbeddedCacheManager getCacheManager() {
        return manager;
    }

    @Override
    public void start(SessionFactoryOptions settings, Map configValues) throws CacheException {
        log.debug("Starting Infinispan region factory");

        // determine the CacheKeysFactory to use...
        this.cacheKeysFactory = determineCacheKeysFactory(settings, configValues);

        try {
            this.settings = settings;

            for (Object k : configValues.keySet()) {
                final String key = (String) k;
                int prefixLoc;
                if ((prefixLoc = key.indexOf(PREFIX)) != -1) {
                    parseProperty(prefixLoc, key, extractProperty(key, configValues));
                }
            }

            String globalStatsStr = extractProperty(INFINISPAN_GLOBAL_STATISTICS_PROP, configValues);
            if (globalStatsStr != null) {
                globalStats = Boolean.parseBoolean(globalStatsStr);
            }
            if (configValues.containsKey(INFINISPAN_USE_SYNCHRONIZATION_PROP)) {
                log.propertyUseSynchronizationDeprecated();
            }

            StandardServiceRegistry serviceRegistry = settings.getServiceRegistry();
            manager = createCacheManager(toProperties(configValues), serviceRegistry);
            defineDataTypeCacheConfigurations(serviceRegistry);
        } catch (CacheException ce) {
            throw ce;
        } catch (Throwable t) {
            throw log.unableToStart(t);
        }
    }

    private Properties toProperties(Map<Object, Object> configValues) {
        Properties properties = new Properties();
        for (Map.Entry<Object, Object> entry : configValues.entrySet()) {
            properties.put(entry.getKey(), entry.getValue());
        }
        return properties;
    }

    private CacheKeysFactory determineCacheKeysFactory(SessionFactoryOptions settings, Map properties) {
        return settings.getServiceRegistry().getService(StrategySelector.class).resolveDefaultableStrategy(
                CacheKeysFactory.class, properties.get(AvailableSettings.CACHE_KEYS_FACTORY),
                DefaultCacheKeysFactory.INSTANCE);
    }

    /* This method is overridden in WildFly, so the signature must not change. */
    protected EmbeddedCacheManager createCacheManager(Properties properties, ServiceRegistry serviceRegistry) {
        for (EmbeddedCacheManagerProvider provider : ServiceLoader.load(EmbeddedCacheManagerProvider.class,
                EmbeddedCacheManagerProvider.class.getClassLoader())) {
            EmbeddedCacheManager cacheManager = provider.getEmbeddedCacheManager(properties);
            if (cacheManager != null) {
                return cacheManager;
            }
        }
        return new DefaultCacheManagerProvider(serviceRegistry).getEmbeddedCacheManager(properties);
    }

    @Override
    public void stop() {
        log.debug("Stop region factory");
        stopCacheRegions();
        stopCacheManager();
    }

    protected void stopCacheRegions() {
        log.debug("Clear region references");
        getCacheCommandFactory().clearRegions(regions);
        // Ensure we cleanup any caches we created
        regions.forEach(region -> {
            region.destroy();
            manager.undefineConfiguration(region.getCache().getName());
        });
        regions.clear();
    }

    protected void stopCacheManager() {
        log.debug("Stop cache manager");
        manager.stop();
    }

    private ConfigurationBuilderHolder loadConfiguration(ServiceRegistry serviceRegistry, String configFile) {
        final FileLookup fileLookup = FileLookupFactory.newInstance();
        final ClassLoader infinispanClassLoader = InfinispanRegionFactory.class.getClassLoader();
        return serviceRegistry.getService(ClassLoaderService.class).workWithClassLoader(classLoader -> {
            InputStream is = null;
            try {
                is = fileLookup.lookupFile(configFile, classLoader);
                if (is == null) {
                    // when it's not a user-provided configuration file, it might be a default configuration file,
                    // and if that's included in [this] module might not be visible to the ClassLoaderService:
                    classLoader = infinispanClassLoader;
                    // This time use lookupFile*Strict* so to provide an exception if we can't find it yet:
                    is = FileLookupFactory.newInstance().lookupFileStrict(configFile, classLoader);
                }
                final ParserRegistry parserRegistry = new ParserRegistry(infinispanClassLoader);
                final ConfigurationBuilderHolder holder = parseWithOverridenClassLoader(parserRegistry, is,
                        infinispanClassLoader);

                return holder;
            } catch (IOException e) {
                throw log.unableToCreateCacheManager(e);
            } finally {
                Util.close(is);
            }
        });
    }

    private static ConfigurationBuilderHolder parseWithOverridenClassLoader(ParserRegistry configurationParser,
            InputStream is, ClassLoader infinispanClassLoader) {
        // Infinispan requires the context ClassLoader to have full visibility on all
        // its components and eventual extension points even *during* configuration parsing.
        final Thread currentThread = Thread.currentThread();
        final ClassLoader originalContextClassLoader = currentThread.getContextClassLoader();
        try {
            currentThread.setContextClassLoader(infinispanClassLoader);
            ConfigurationBuilderHolder builderHolder = configurationParser.parse(is);
            // Workaround Infinispan's ClassLoader strategies to bend to our will:
            builderHolder.getGlobalConfigurationBuilder().classLoader(infinispanClassLoader);
            return builderHolder;
        } finally {
            currentThread.setContextClassLoader(originalContextClassLoader);
        }
    }

    private void startRegion(InfinispanBaseRegion region) {
        regions.add(region);
        getCacheCommandFactory().addRegion(region);
    }

    private void parseProperty(int prefixLoc, String key, String value) {
        final ConfigurationBuilder builder;
        int suffixLoc;
        if ((suffixLoc = key.indexOf(CONFIG_SUFFIX)) != -1 && !key.equals(INFINISPAN_CONFIG_RESOURCE_PROP)) {
            String regionName = key.substring(prefixLoc + PREFIX.length(), suffixLoc);
            baseConfigurations.put(regionName, value);
        } else if (key.contains(DEPRECATED_STRATEGY_SUFFIX)) {
            log.ignoringDeprecatedProperty(DEPRECATED_STRATEGY_SUFFIX);
        } else if ((suffixLoc = key.indexOf(WAKE_UP_INTERVAL_SUFFIX)) != -1
                || (suffixLoc = key.indexOf(DEPRECATED_WAKE_UP_INTERVAL_SUFFIX)) != -1) {
            builder = getOrCreateConfig(prefixLoc, key, suffixLoc);
            builder.expiration().wakeUpInterval(Long.parseLong(value));
        } else if ((suffixLoc = key.indexOf(SIZE_SUFFIX)) != -1) {
            builder = getOrCreateConfig(prefixLoc, key, suffixLoc);
            builder.memory().size(Long.parseLong(value));
        } else if ((suffixLoc = key.indexOf(DEPRECATED_MAX_ENTRIES_SUFFIX)) != -1) {
            log.deprecatedProperty(DEPRECATED_MAX_ENTRIES_SUFFIX, SIZE_SUFFIX);
            builder = getOrCreateConfig(prefixLoc, key, suffixLoc);
            builder.memory().size(Long.parseLong(value));
        } else if ((suffixLoc = key.indexOf(LIFESPAN_SUFFIX)) != -1) {
            builder = getOrCreateConfig(prefixLoc, key, suffixLoc);
            builder.expiration().lifespan(Long.parseLong(value));
        } else if ((suffixLoc = key.indexOf(MAX_IDLE_SUFFIX)) != -1) {
            builder = getOrCreateConfig(prefixLoc, key, suffixLoc);
            builder.expiration().maxIdle(Long.parseLong(value));
        }
    }

    private String extractProperty(String key, Map properties) {
        final String value = ConfigurationHelper.extractPropertyValue(key, properties);
        log.debugf("Configuration override via property %s: %s", key, value);
        return value;
    }

    private ConfigurationBuilder getOrCreateConfig(int prefixLoc, String key, int suffixLoc) {
        final String name = key.substring(prefixLoc + PREFIX.length(), suffixLoc);
        return configOverrides.computeIfAbsent(name, Void -> new ConfigurationBuilder());
    }

    private void defineDataTypeCacheConfigurations(ServiceRegistry serviceRegistry) {
        String defaultResource = manager.getCacheManagerConfiguration().isClustered()
                ? DEF_INFINISPAN_CONFIG_RESOURCE
                : INFINISPAN_CONFIG_LOCAL_RESOURCE;
        ConfigurationBuilderHolder defaultConfiguration = DefaultCacheManagerProvider
                .loadConfiguration(serviceRegistry, defaultResource);
        for (DataType type : DataType.values()) {
            String cacheName = baseConfigurations.get(type.key);
            if (cacheName == null) {
                cacheName = type.defaultCacheName;
            }
            Configuration configuration = manager.getCacheConfiguration(cacheName);
            ConfigurationBuilder builder;
            if (configuration == null) {
                log.debugf("Cache configuration not found for %s", type);
                if (!cacheName.equals(type.defaultCacheName)) {
                    log.customConfigForTypeNotFound(cacheName, type.key);
                }
                builder = defaultConfiguration.getNamedConfigurationBuilders().get(type.defaultCacheName);
                if (builder == null) {
                    throw new IllegalStateException(
                            "Generic data types must have default configuration, none found for " + type);
                }
            } else {
                builder = new ConfigurationBuilder().read(configuration);
            }
            ConfigurationBuilder override = configOverrides.get(type.key);
            if (override != null) {
                builder.read(override.build(false));
            }
            builder.template(true);
            unsetTransactions(builder);
            dataTypeConfigurations.put(type, builder.build());
        }
    }

    protected AdvancedCache getCache(String cacheName, String unqualifiedRegionName, DataType type,
            Collection<String> legacyUnqualifiedNames) {
        if (!manager.cacheExists(cacheName)) {
            String templateCacheName = baseConfigurations.get(cacheName);
            Configuration configuration;
            ConfigurationBuilder builder = new ConfigurationBuilder();
            if (templateCacheName == null) {
                templateCacheName = baseConfigurations.get(unqualifiedRegionName);
                if (templateCacheName != null) {
                    log.usingUnqualifiedNameInConfiguration(unqualifiedRegionName, cacheName);
                }
            }
            if (templateCacheName != null) {
                configuration = manager.getCacheConfiguration(templateCacheName);
                if (configuration == null) {
                    log.customConfigForRegionNotFound(templateCacheName, cacheName, type.key);
                } else {
                    log.debugf("Region '%s' will use cache template '%s'", cacheName, templateCacheName);
                    builder.read(configuration);
                    unsetTransactions(builder);
                    // do not apply data type overrides to regions that set special cache configuration
                    if (templateCacheName.equals(cacheName)) {
                        // we'll define the configuration at the end of this method
                        manager.undefineConfiguration(cacheName);
                    }
                }
            } else {
                configuration = manager.getCacheConfiguration(cacheName);
                if (configuration != null) {
                    // While we could just use the defined configuration it's better to force user to include
                    // the configuration properties so that it's obvious from persistence.xml that this entity
                    // will get some special treatment.
                    log.regionNameMatchesCacheName(cacheName, cacheName, cacheName);
                    manager.undefineConfiguration(cacheName);
                }
                if (manager.getCacheConfiguration(unqualifiedRegionName) != null) {
                    log.configurationWithUnqualifiedName(unqualifiedRegionName, cacheName);
                }
            }
            // Before the very default configuration for the type try legacy configuration names
            for (String legacyUnqualified : legacyUnqualifiedNames) {
                configuration = manager.getCacheConfiguration(qualify(legacyUnqualified));
                if (configuration != null) {
                    SecondLevelCacheLogger.INSTANCE.usingLegacyCacheName(cacheName, qualify(legacyUnqualified));
                    break;
                }
                configuration = manager.getCacheConfiguration(legacyUnqualified);
                if (configuration != null) {
                    SecondLevelCacheLogger.INSTANCE.usingLegacyCacheName(cacheName, legacyUnqualified);
                    break;
                }
            }
            if (configuration == null) {
                configuration = dataTypeConfigurations.get(type);
                if (configuration == null) {
                    throw new IllegalStateException("Configuration not defined for type " + type.key);
                }
                builder.read(configuration);
                // overrides for data types are already applied, but we should check custom ones
            }
            ConfigurationBuilder override = configOverrides.get(cacheName);
            if (override != null) {
                log.debugf("Region '%s' has additional configuration set through properties.", cacheName);
                builder.read(override.build(false));
            }
            if (globalStats != null) {
                builder.jmxStatistics().enabled(globalStats).available(globalStats);
            }
            configuration = builder.build();
            type.validate(configuration);
            manager.defineConfiguration(cacheName, configuration);
        }
        final AdvancedCache cache = manager.getCache(cacheName).getAdvancedCache();
        // TODO: not sure if this is needed in recent Infinispan
        if (!cache.getStatus().allowInvocations()) {
            cache.start();
        }
        return cache;
    }

    private void unsetTransactions(ConfigurationBuilder builder) {
        if (builder.transaction().transactionMode().isTransactional()) {
            log.transactionalConfigurationIgnored();
            builder.transaction().transactionMode(TransactionMode.NON_TRANSACTIONAL).transactionManagerLookup(null);
        }
    }

    private CacheCommandFactory getCacheCommandFactory() {
        final GlobalComponentRegistry globalCr = manager.getGlobalComponentRegistry();

        final Map<Byte, ModuleCommandFactory> factories = globalCr
                .getComponent("org.infinispan.modules.command.factories");

        for (ModuleCommandFactory factory : factories.values()) {
            if (factory instanceof CacheCommandFactory) {
                return (CacheCommandFactory) factory;
            }
        }

        throw log.cannotInstallCommandFactory();
    }
}