diff --git a/dotCMS/src/enterprise/java/com/dotcms/enterprise/cache/provider/CacheProviderAPIImpl.java b/dotCMS/src/enterprise/java/com/dotcms/enterprise/cache/provider/CacheProviderAPIImpl.java index fc438771d775..6ae469dc0431 100644 --- a/dotCMS/src/enterprise/java/com/dotcms/enterprise/cache/provider/CacheProviderAPIImpl.java +++ b/dotCMS/src/enterprise/java/com/dotcms/enterprise/cache/provider/CacheProviderAPIImpl.java @@ -47,19 +47,25 @@ import com.dotcms.enterprise.LicenseUtil; import com.dotcms.enterprise.license.LicenseLevel; -import com.dotmarketing.business.cache.provider.CacheProviderStats; -import com.dotmarketing.business.cache.provider.caffine.CaffineCache; import com.dotmarketing.business.APILocator; import com.dotmarketing.business.DotStateException; import com.dotmarketing.business.cache.CacheOSGIService; import com.dotmarketing.business.cache.provider.CacheProvider; +import com.dotmarketing.business.cache.provider.CacheProviderStats; +import com.dotmarketing.business.cache.provider.caffine.CaffineCache; import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; -import com.dotmarketing.util.UtilMethods; -import com.dotmarketing.util.WebKeys; import com.google.common.collect.Lists; - -import java.util.*; +import io.vavr.control.Try; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -70,125 +76,21 @@ public class CacheProviderAPIImpl implements CacheProviderAPI, CacheOSGIService private static final String CACHE_POOL_DEFAULT_CHAIN = "cache.default.chain"; - private static String defaultProvidersNoLicense;//Default providers with not ee lincense - private static String defaultProviders;//Default providers with license - private final Map> configuredChainsPerRegion;//List of registered providers per region - private final Map providersClasses;//List of ALL the registered providers - private final Map defaultProvidersNoLicenseClasses;//List of the default registered providers + private final Map> configuredChainsPerRegion= new ConcurrentHashMap<>(); + private final List noLicenseProviders = List.of(CaffineCache.class.getCanonicalName()); - private boolean isLicenseInitialized = false; public CacheProviderAPIImpl () { - configuredChainsPerRegion = new ConcurrentHashMap<>(); - providersClasses = new ConcurrentHashMap<>(); - defaultProvidersNoLicenseClasses = new ConcurrentHashMap<>(); - - /* - This class stores in memory the initialized providers in order to reduce the performance impact as much as possible. - Keep in mind this class is heavily used!. - */ - - //Reading the default providers properties - defaultProvidersNoLicense = CaffineCache.class.getCanonicalName(); - defaultProviders = Config.getStringProperty(CACHE_POOL_DEFAULT_CHAIN, defaultProvidersNoLicense); - - //Lets try to find out what Providers we want to use - Iterator keysIterator = Config.subset("cache"); - while ( keysIterator.hasNext() ) { - - //Check if it is a region chain property and read the property - String propertyKey = keysIterator.next(); - if ( propertyKey.endsWith(".chain") ) { - - String poolChainClassNames = Config.getStringProperty("cache." + propertyKey, ""); - - //Now iterate each CacheProvider on this region - String[] chainArray = poolChainClassNames.split(","); - for ( String providerClassName : chainArray ) { - - providerClassName = providerClassName.trim(); - - //First lets search if we already instantiated this provider - if ( !providersClasses.containsKey(providerClassName) ) { - - try { - - CacheProvider cacheProviderInstance; - try { - //Create a new instance of the provider - cacheProviderInstance = getInstanceFor(providerClassName); - } catch ( ClassNotFoundException e ) { - Logger.error(this, "Unable to get the class reference for the CacheProvider [" + providerClassName + "].", e); - //Lets continue with the next given CacheProvider implementation - continue; - } - - //Add the new provider instance to the list for registered providers - providersClasses.put(providerClassName, cacheProviderInstance); - - } catch ( InstantiationException | IllegalAccessException e ) { - Logger.error(this, "Error instantiating CacheProvider [" + providerClassName + "].", e); - //Lets continue with the next given CacheProvider implementation - } - } - - } - } - - } - - /* - Get the list of default CacheProviders when no license is set, we do this for easy and quick access - */ - String[] chainArray = defaultProvidersNoLicense.split(","); - for ( String providerClassName : chainArray ) { - - providerClassName = providerClassName.trim(); - - CacheProvider cacheProviderInstance = null; - - //Search in the already saved Providers - if ( providersClasses.containsKey(providerClassName) ) { - cacheProviderInstance = providersClasses.get(providerClassName); - } else { - try { - //Create a new instance of the provider - cacheProviderInstance = getInstanceFor(providerClassName); - } catch ( Exception e ) { - Logger.error(this, "Unable to get the class reference for the CacheProvider [" + providerClassName + "].", e); - //Lets continue with the next given CacheProvider implementation - } - } - - if ( cacheProviderInstance != null ) { - //Now lets fill in the default CacheProviders list for easy access - defaultProvidersNoLicenseClasses.put(providerClassName, cacheProviderInstance); - } else { - Logger.error(this, "CacheProvider not found [" + providerClassName + "]."); - } - } - - //Registering this service to the OSGI context - registerBundleService(); } - /** - * Creates an instance of a given class name - * - * @param providerClassName - * @return - * @throws IllegalAccessException - * @throws InstantiationException - * @throws ClassNotFoundException - */ - private CacheProvider getInstanceFor ( String providerClassName ) throws IllegalAccessException, InstantiationException, ClassNotFoundException { - //Look for the provider class - Class providerClass = (Class) Class.forName(providerClassName); - //Create a new instance of the provider - return providerClass.newInstance(); + private Optional getInstanceFor ( String providerClassName ) { + return Try.of(() -> { + Class providerClass = (Class) Class.forName(providerClassName.trim()); + return providerClass.newInstance(); + }).onFailure(e -> Logger.error(this, "Error creating CacheProvider [" + providerClassName + "].", e)).toJavaOptional(); } /** @@ -196,32 +98,23 @@ private CacheProvider getInstanceFor ( String providerClassName ) throws Illegal * * @return */ - private Boolean isValidLicense () { - - if ( !isLicenseInitialized ) { - - /* - Validate if we can get use the LicenseUtil, the CacheLocator and CacheProviders - are one of the first elements to be created, using the LicenseUtil here on a clean install - can throw errors as the DB could not be even been loaded or a server id file could not be created - and we don't want to stop the execution here for those expected cases. - */ - String serverId = APILocator.getServerAPI().readServerId(); - if ( serverId == null ) { - //We can continue, probably a first start - Logger.debug(this, "Unable to get License level [server id is null]."); - return false; - } - - //We can finally call directly the LicenseUtil.getLevel() method, the cluster_server was finally created! - isLicenseInitialized = true; - } + private boolean isCommunity () { - if ( LicenseUtil.getLevel() >= LicenseLevel.STANDARD.level ) { + /* + Validate if we can get use the LicenseUtil, the CacheLocator and CacheProviders + are one of the first elements to be created, using the LicenseUtil here on a clean install + can throw errors as the DB could not be even been loaded or a server id file could not be created + and we don't want to stop the execution here for those expected cases. + */ + String serverId = APILocator.getServerAPI().readServerId(); + if ( serverId == null ) { + //We can continue, probably a first start + Logger.debug(this, "Unable to get License level [server id is null]."); return true; } - return false; + return LicenseUtil.getLevel() <= LicenseLevel.COMMUNITY.level ; + } /** @@ -232,15 +125,36 @@ private Boolean isValidLicense () { */ private List getAllProviders () { - //If no license return the default providers - if ( !isValidLicense() ) { - return new ArrayList<>(defaultProvidersNoLicenseClasses.values()); + Map providers = new HashMap<>(); + + for(List providerList : configuredChainsPerRegion.values()){ + providerList.forEach(p->providers.put(p.getClass().getCanonicalName(),p)); } + return new ArrayList<>(providers.values()); + - //Or else return the complete list of providers - return new ArrayList<>(providersClasses.values()); } + + + private List getProviderNamesPerRegion(String group){ + if ( isCommunity() || null == group ) { + return noLicenseProviders; + } + //Read from the properties the cache chain to use for this region, if nothing found the default chain will be used + String[] poolChainClassNames = Config.getStringArrayProperty("cache." + group.toLowerCase() + ".chain", + new String[0]); + + if(poolChainClassNames.length>0){ + return Arrays.asList(poolChainClassNames); + } + + return Arrays.asList(Config.getStringArrayProperty(CACHE_POOL_DEFAULT_CHAIN,new String[]{ CaffineCache.class.getCanonicalName()})); + + + } + + /** * Returns the list of CacheProviders to use for a given group, this class stores in * memory the initialized providers in order to impact as much as possible performance, keep in mind this class @@ -252,64 +166,26 @@ private List getAllProviders () { private List getProvidersForRegion ( String group ) { //The case is very simple here, no license no chance to modify any region chain - if ( !isValidLicense() ) { - return new ArrayList<>(defaultProvidersNoLicenseClasses.values()); - } - - List providersForRegion = new ArrayList<>(); - List poolChainClassNames; - - //First lets check if we already searched for this group in the past - if ( configuredChainsPerRegion.containsKey(group) ) { - poolChainClassNames = configuredChainsPerRegion.get(group); - } else { - - //Read from the properties the cache chain to use for this region, if nothing found the default chain will be used - String poolChainClassNamesValue = Config.getStringProperty("cache." + group + ".chain", defaultProviders); - poolChainClassNames = Arrays.asList(poolChainClassNamesValue.split(",")); - //Added to the map, we want to avoid extra validations and checks as much as we can - configuredChainsPerRegion.put(group, poolChainClassNames); - } - - //Now iterate for each requested CacheProvider - for ( String providerClassName : poolChainClassNames ) { - - CacheProvider cacheProviderInstance = null; - - //First lets search if we already requested this provider in the past - if ( providersClasses.containsKey(providerClassName) ) { - cacheProviderInstance = providersClasses.get(providerClassName); + return configuredChainsPerRegion.computeIfAbsent(group, k -> { + List providers = new ArrayList<>(); + for(String providerClassName : getProviderNamesPerRegion(group)){ + getInstanceFor(providerClassName).ifPresent(providers::add); } + return List.copyOf(initProviders(providers)); + }); - if ( cacheProviderInstance != null ) { - if ( !providersForRegion.contains(cacheProviderInstance) ) { - - try { - /* - Check if the Provider was not already initialized, if not is probably because - the user didn't had a license and now have it (so he is using more providers now), or had a license and he removed it - */ - if ( !cacheProviderInstance.isInitialized() ) { - cacheProviderInstance.init(); - } - - //Add to the list of providers to return - providersForRegion.add(cacheProviderInstance); - - } catch ( Exception e ) { - //On Error we don't want to stop the execution of the other providers - Logger.error(this.getClass(), "Error initializing CacheProvider [" + cacheProviderInstance.getName() + "].", e); - } - } - } else { - Logger.error(this, "CacheProvider not found [" + providerClassName + "]."); - } - } + } - return providersForRegion; + List initProviders(List cacheProviders) { + cacheProviders.forEach(provider -> {; + Try.run(provider::init).onFailure( + e -> Logger.error(this, "Error initializing CacheProvider [" + provider.getName() + "].", e)); + }); + return cacheProviders; } + /** * Determines whether all Cache Providers are distributed */ @@ -341,13 +217,7 @@ private boolean determineDistributed(List providers) { * Registers this CacheOSGIService to the OSGI Context, in order to be use for OSGI plugins */ public void registerBundleService () { - - if (System.getProperty(WebKeys.OSGI_ENABLED) != null ) { - //Register the CacheOSGIService - // BundleContext context = HostActivator.instance().getBundleContext(); - // Hashtable props = new Hashtable<>(); - // context.registerService(CacheOSGIService.class.getName(), this, props); - } + // not implemented } @@ -491,6 +361,10 @@ public void removeAll (boolean ignoreDistributed) { Logger.error(this.getClass(), "Error flushing CacheProvider [" + provider.getName() + "].", e); } } + configuredChainsPerRegion.clear(); + shutdownProviders(providers); + + } @Override @@ -522,9 +396,9 @@ public Set getGroups () { public List getStats () { //Getting the list of all the cache providers - List providers = getAllProviders(); + List ret = new ArrayList<>(); - for ( CacheProvider provider : providers ) { + for ( CacheProvider provider : getAllProviders() ) { try { CacheProviderStats providerStat = provider.getStats(); @@ -544,58 +418,15 @@ public List getStats () { @Override public void shutdown () { - //Getting the list of all the cache providers - Set providers = new HashSet<>(providersClasses.values()); - providers.addAll(defaultProvidersNoLicenseClasses.values()); + shutdownProviders(List.copyOf(getAllProviders())); + } + private void shutdownProviders(Collection providers) { for ( CacheProvider provider : providers ) { - - try { - if ( provider.isInitialized() ) { - provider.shutdown(); - } - } catch ( Exception e ) { - //On Error we don't want to stop the execution of the other providers - Logger.error(this.getClass(), "Error shutting down CacheProvider [" + provider.getName() + "].", e); - } + Try.run(provider::shutdown).onFailure(e -> Logger.error(this, "Error shutting down CacheProvider [" + provider.getName() + "].", e)); } } - /** - * Internal comparator class used to order stat results (getStats()) - */ - public class CacheComparator implements Comparator> { - - public int compare ( Map o1, Map o2 ) { - - if ( o1 == null && o2 != null ) return 1; - if ( o1 != null && o2 == null ) return -1; - if ( o1 == null && o2 == null ) return 0; - - String group1 = (String) o1.get("region"); - String group2 = (String) o2.get("region"); - - if ( !UtilMethods.isSet(group1) && !UtilMethods.isSet(group2) ) { - return 0; - } else if ( UtilMethods.isSet(group1) && !UtilMethods.isSet(group2) ) { - return -1; - } else if ( !UtilMethods.isSet(group1) && UtilMethods.isSet(group2) ) { - return 1; - } else if ( group1.equals(group2) ) { - return 0; - } else if ( group1.startsWith(WORKING_CACHE_PREFIX) && group2.startsWith(LIVE_CACHE_PREFIX) ) { - return 1; - } else if ( group1.startsWith(LIVE_CACHE_PREFIX) && group2.startsWith(WORKING_CACHE_PREFIX) ) { - return -1; - } else if ( !group1.startsWith(LIVE_CACHE_PREFIX) && !group1.startsWith(WORKING_CACHE_PREFIX) && (group2.startsWith(LIVE_CACHE_PREFIX) || group2.startsWith(WORKING_CACHE_PREFIX)) ) { - return -1; - } else if ( (group1.startsWith(LIVE_CACHE_PREFIX) || group1.startsWith(WORKING_CACHE_PREFIX)) && !group2.startsWith(LIVE_CACHE_PREFIX) && !group2.startsWith(WORKING_CACHE_PREFIX) ) { - return 1; - } else { // neither group1 nor group2 are live or working - return group1.compareToIgnoreCase(group2); - } - } - } -} \ No newline at end of file +} diff --git a/dotcms-integration/src/test/resources/dotmarketing-config.properties b/dotcms-integration/src/test/resources/dotmarketing-config.properties index 784625d984fc..fd49c4489495 100644 --- a/dotcms-integration/src/test/resources/dotmarketing-config.properties +++ b/dotcms-integration/src/test/resources/dotmarketing-config.properties @@ -515,8 +515,7 @@ asset.cache.control.max.days=30 cache.default.chain =com.dotmarketing.business.cache.provider.caffine.CaffineCache cache.contentletcache.chain =com.dotmarketing.business.cache.provider.caffine.CaffineCache,com.dotmarketing.business.cache.provider.h22.H22Cache cache.velocitycache.chain =com.dotmarketing.business.cache.provider.caffine.CaffineCache,com.dotmarketing.business.cache.provider.h22.H22Cache -cache.vanityurlcache.chain =com.dotmarketing.business.cache.provider.timedcache.TimedCacheProvider -cache.cachedvanityurlgroup.chain=com.dotmarketing.business.cache.provider.timedcache.TimedCacheProvider + CACHE_INVALIDATION_TRANSPORT_CLASS=com.dotmarketing.business.cache.transport.NullTransport @@ -881,4 +880,4 @@ DELETE_CONTENT_TYPE_ASYNC_WITH_JOB=false secrets.scripting.enabled=true FEATURE_FLAG_DUMMY_FALSE=false -FEATURE_FLAG_DUMMY_TRUE=true \ No newline at end of file +FEATURE_FLAG_DUMMY_TRUE=true