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

org.apereo.cas.config.Ehcache3TicketRegistryConfiguration Maven / Gradle / Ivy

package org.apereo.cas.config;

import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.ticket.Ticket;
import org.apereo.cas.ticket.TicketCatalog;
import org.apereo.cas.ticket.TicketDefinition;
import org.apereo.cas.ticket.registry.EhCache3TicketRegistry;
import org.apereo.cas.ticket.registry.TicketRegistry;
import org.apereo.cas.util.CoreTicketUtils;
import org.apereo.cas.util.model.Capacity;

import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.ehcache.clustered.client.config.builders.ClusteredResourcePoolBuilder;
import org.ehcache.clustered.client.config.builders.ClusteredStoreConfigurationBuilder;
import org.ehcache.clustered.client.config.builders.ClusteringServiceConfigurationBuilder;
import org.ehcache.clustered.client.config.builders.TimeoutsBuilder;
import org.ehcache.clustered.common.Consistency;
import org.ehcache.config.CacheConfiguration;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheEventListenerConfigurationBuilder;
import org.ehcache.config.builders.ExpiryPolicyBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.core.config.DefaultConfiguration;
import org.ehcache.event.CacheEvent;
import org.ehcache.event.CacheEventListener;
import org.ehcache.event.EventType;
import org.ehcache.impl.config.persistence.DefaultPersistenceConfiguration;
import org.ehcache.jsr107.Eh107Configuration;
import org.ehcache.jsr107.EhcacheCachingProvider;
import org.ehcache.jsr107.config.ConfigurationElementState;
import org.ehcache.jsr107.config.Jsr107Configuration;
import org.ehcache.spi.service.ServiceCreationConfiguration;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.jcache.JCacheCacheManager;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ScopedProxyMode;

import javax.cache.CacheManager;
import javax.cache.Caching;
import java.io.File;
import java.net.URI;
import java.time.Duration;
import java.util.HashMap;

/**
 * This is {@link Ehcache3TicketRegistryConfiguration}.
 *
 * @author Hal Deadman
 * @since 6.2.0
 */
@Configuration(value = "Ehcache3TicketRegistryConfiguration", proxyBeanMethods = false)
@EnableConfigurationProperties(CasConfigurationProperties.class)
@ConditionalOnProperty(prefix = "cas.ticket.registry.ehcache3", name = "enabled", havingValue = "true", matchIfMissing = true)
@Slf4j
public class Ehcache3TicketRegistryConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "ehcache3CacheManagerConfiguration")
    public ServiceCreationConfiguration ehcache3CacheManagerConfiguration(final CasConfigurationProperties casProperties) {
        val ehcacheProperties = casProperties.getTicket().getRegistry().getEhcache3();
        val terracottaClusterUri = ehcacheProperties.getTerracottaClusterUri();
        if (StringUtils.isNotBlank(terracottaClusterUri)) {
            val resourcePoolCapacity = Capacity.parse(ehcacheProperties.getResourcePoolSize());
            val clusterConfigBuilder = ClusteringServiceConfigurationBuilder.cluster(URI.create(terracottaClusterUri)).timeouts(
                    TimeoutsBuilder.timeouts().connection(Duration.ofSeconds(ehcacheProperties.getClusterConnectionTimeout()))
                        .read(Duration.ofSeconds(ehcacheProperties.getClusterReadWriteTimeout()))
                        .write(Duration.ofSeconds(ehcacheProperties.getClusterReadWriteTimeout())).build())
                .autoCreate(s -> s.defaultServerResource(ehcacheProperties.getDefaultServerResource())
                    .resourcePool(ehcacheProperties.getResourcePoolName(),
                        resourcePoolCapacity.getSize().longValue(), MemoryUnit.valueOf(resourcePoolCapacity.getUnitOfMeasure().name())));
            return clusterConfigBuilder.build();
        }
        val rootDirectory = ehcacheProperties.getRootDirectory();
        val rootDirectoryFile = new File(rootDirectory);
        if (!rootDirectoryFile.exists()) {
            LOGGER.debug("Creating folder for ehcache ticket registry disk cache [{}]", rootDirectory);
            val mkdirResult = rootDirectoryFile.mkdirs();
            if (!mkdirResult) {
                LOGGER.warn("Unable to create folder for ehcache ticket registry disk cache [{}]", rootDirectory);
            }
        }
        return new DefaultPersistenceConfiguration(rootDirectoryFile);
    }

    @Bean
    @ConditionalOnMissingBean(name = "ehcache3TicketCacheManager")
    public CacheManager ehcache3TicketCacheManager(
        @Qualifier("ehcache3CacheManagerConfiguration")
        final ServiceCreationConfiguration ehcache3CacheManagerConfiguration, final CasConfigurationProperties casProperties) {
        val ehcacheProperties = casProperties.getTicket().getRegistry().getEhcache3();
        val ehcacheProvider = (EhcacheCachingProvider) Caching.getCachingProvider(EhcacheCachingProvider.class.getName());
        val statisticsAllEnabled = ehcacheProperties.isEnableStatistics() ? ConfigurationElementState.ENABLED : ConfigurationElementState.DISABLED;
        val managementAllAllEnabled = ehcacheProperties.isEnableManagement() ? ConfigurationElementState.ENABLED : ConfigurationElementState.DISABLED;
        val jsr107Config = new Jsr107Configuration(null, new HashMap<>(), false, managementAllAllEnabled, statisticsAllEnabled);
        val configuration = new DefaultConfiguration(ehcacheProvider.getDefaultClassLoader(), ehcache3CacheManagerConfiguration, jsr107Config);
        return ehcacheProvider.getCacheManager(ehcacheProvider.getDefaultURI(), configuration);
    }

    /**
     * Create ticket registry bean with all necessary caches.
     * Using the spring ehcache wrapper bean so it can be initialized after the caches are built.
     *
     * @param ehcacheManager Spring EhCache manager bean, wraps EhCache manager and is used for cache actuator endpoint.
     * @param ticketCatalog  Ticket Catalog
     * @param casProperties  the cas properties
     * @return Ticket Registry
     */
    @Bean
    @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
    public TicketRegistry ticketRegistry(
        @Qualifier("ehcache3TicketCacheManager")
        final CacheManager ehcacheManager,
        @Qualifier("ticketCatalog")
        final TicketCatalog ticketCatalog, final CasConfigurationProperties casProperties) {
        val ehcacheProperties = casProperties.getTicket().getRegistry().getEhcache3();
        val crypto = ehcacheProperties.getCrypto();
        val definitions = ticketCatalog.findAll();
        definitions.forEach(t -> {
            val cacheName = t.getProperties().getStorageName();
            if (ehcacheManager.getCache(cacheName, String.class, Ticket.class) == null) {
                val ehcacheConfiguration = buildCacheConfiguration(t, casProperties);
                ehcacheManager.createCache(cacheName, Eh107Configuration.fromEhcacheCacheConfiguration(ehcacheConfiguration));
            }
        });
        return new EhCache3TicketRegistry(ticketCatalog, ehcacheManager, CoreTicketUtils.newTicketRegistryCipherExecutor(crypto, "ehcache3"));
    }

    /**
     * This bean is used by the spring boot cache actuator which spring boot admin can use to clear caches.
     * Actuator needs to be exposed in order for this bean to be used.
     *
     * @param ehcache3TicketCacheManager JSR107 wrapper of EhCache cache manager to be wrapped by spring cache manager.
     * @return Spring EhCacheCacheManager that wraps EhCache JSR107 CacheManager
     */
    @Bean
    public JCacheCacheManager ehCacheJCacheCacheManager(
        @Qualifier("ehcache3TicketCacheManager")
        final CacheManager ehcache3TicketCacheManager) {
        return new JCacheCacheManager(ehcache3TicketCacheManager);
    }

    private static class CasCacheEventListener implements CacheEventListener {

        @Override
        public void onEvent(final CacheEvent event) {
            LOGGER.trace("Event Type: [{}], Ticket Id: [{}]", event.getType().name(), event.getKey());
        }
    }

    private CacheConfiguration buildCacheConfiguration(final TicketDefinition ticketDefinition,
                                                                       final CasConfigurationProperties casProperties) {
        val ehcacheProperties = casProperties.getTicket().getRegistry().getEhcache3();
        val terracottaClusterUri = ehcacheProperties.getTerracottaClusterUri();
        val cacheEventListenerConfiguration =
            CacheEventListenerConfigurationBuilder.newEventListenerConfiguration(new CasCacheEventListener(),
                EventType.CREATED, EventType.UPDATED, EventType.EXPIRED, EventType.REMOVED,
                EventType.EVICTED).ordered().asynchronous();
        val storageTimeout = ticketDefinition.getProperties().getStorageTimeout();
        val expiryPolicy = ehcacheProperties.isEternal()
            ? ExpiryPolicyBuilder.noExpiration() : ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(storageTimeout));
        var resourcePools = ResourcePoolsBuilder.heap(ehcacheProperties.getMaxElementsInMemory());
        if (StringUtils.isNotBlank(terracottaClusterUri)) {
            resourcePools = resourcePools.with(ClusteredResourcePoolBuilder.clusteredShared(ehcacheProperties.getResourcePoolName()));
        }
        val resourcePoolCapacity = Capacity.parse(ehcacheProperties.getResourcePoolSize());
        resourcePools = resourcePools.offheap(resourcePoolCapacity.getSize().longValue(), MemoryUnit.valueOf(resourcePoolCapacity.getUnitOfMeasure().name()));
        if (StringUtils.isBlank(terracottaClusterUri)) {
            val perCacheCapacity = Capacity.parse(ehcacheProperties.getPerCacheSizeOnDisk());
            val persistOnDisk = ehcacheProperties.isPersistOnDisk();
            resourcePools = resourcePools.disk(perCacheCapacity.getSize().longValue(), MemoryUnit.valueOf(perCacheCapacity.getUnitOfMeasure().name()), persistOnDisk);
        }
        var cacheConfigBuilder =
            CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Ticket.class, resourcePools)
                .withExpiry(expiryPolicy)
                .withService(cacheEventListenerConfiguration);
        if (StringUtils.isNotBlank(terracottaClusterUri)) {
            cacheConfigBuilder = cacheConfigBuilder.withService(ClusteredStoreConfigurationBuilder.withConsistency(
                Consistency.valueOf(ehcacheProperties.getClusteredCacheConsistency().name())));
        }
        return cacheConfigBuilder.build();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy