com.tangosol.net.ScopedCacheFactoryBuilder Maven / Gradle / Ivy
Show all versions of coherence Show documentation
/*
* Copyright (c) 2000, 2023, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl.
*/
package com.tangosol.net;
import com.tangosol.config.expression.ParameterResolver;
import com.tangosol.internal.net.ScopedUriScopeResolver;
import com.tangosol.net.ExtensibleConfigurableCacheFactory.Dependencies;
import com.tangosol.run.xml.XmlElement;
import com.tangosol.run.xml.XmlHelper;
import com.tangosol.util.ClassHelper;
import com.tangosol.util.CopyOnWriteMap;
import com.tangosol.util.LiteMap;
import com.tangosol.util.Resources;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import static com.tangosol.util.Base.ensureClassLoader;
import static com.tangosol.util.Base.ensureRuntimeException;
import static com.tangosol.util.Base.getOriginalException;
/**
* Implementation of {@link CacheFactoryBuilder} that manages multiple
* instances of {@link ConfigurableCacheFactory}. This implementation
* supports isolation of cache configurations via the following mechanisms:
*
* - It parses the cache configuration file for the {@code
}
* attribute. If this element exists, this attribute will be set
* on the CCF instance.
* - The scope name can be explicitly passed to the {@link #instantiateFactory}
* method.
*
*
* The scope name may be used by the {@link ConfigurableCacheFactory} instance
* as a service name prefix.
*
*
* @author pp 2010.01.20
*
* @since Coherence 3.7
*/
public class ScopedCacheFactoryBuilder
implements CacheFactoryBuilder
{
// ----- constructors -------------------------------------------------
/**
* Default constructor; reads scope resolver configuration from
* operational configuration file (tangosol-coherence.xml).
*/
public ScopedCacheFactoryBuilder()
{
this(null);
}
/**
* Constructor to provide a custom scope resolver.
*
* @param resolver scope resolver
*/
public ScopedCacheFactoryBuilder(ScopeResolver resolver)
{
f_scopeResolver = resolver == null ? instantiateScopeResolver() : resolver;
}
// ----- accessors ----------------------------------------------------
/**
* Obtain the scope resolver for this builder.
*
* @return scope resolver
*/
public ScopeResolver getScopeResolver()
{
return f_scopeResolver;
}
// ----- CacheFactoryBuilder interface --------------------------------
/**
* {@inheritDoc}
*/
public ConfigurableCacheFactory getConfigurableCacheFactory(ClassLoader loader)
{
return getFactory(URI_DEFAULT, loader, null);
}
/**
* {@inheritDoc}
*/
public ConfigurableCacheFactory getConfigurableCacheFactory(String sConfigURI, ClassLoader loader)
{
return getFactory(sConfigURI, loader, null);
}
/**
* {@inheritDoc}
*/
public ConfigurableCacheFactory getConfigurableCacheFactory(String sConfigURI,
ClassLoader loader,
ParameterResolver resolver)
{
return getFactory(sConfigURI, loader, resolver);
}
/**
* {@inheritDoc}
*/
public void setCacheConfiguration(ClassLoader loader, XmlElement xmlConfig)
{
setCacheConfiguration(URI_DEFAULT, loader, xmlConfig);
}
/**
* {@inheritDoc}
*/
public synchronized void setCacheConfiguration(String sConfigURI, ClassLoader loader, XmlElement xmlConfig)
{
loader = ensureClassLoader(loader);
Map mapCCF = m_mapByLoader.get(loader);
ConfigurableCacheFactory ccf = mapCCF == null ? null : mapCCF.get(sConfigURI);
if (ccf != null)
{
release(ccf);
}
URL url = resolveURL(resolveURI(sConfigURI), loader);
setXmlConfig(loader, url, xmlConfig);
}
/**
* {@inheritDoc}
*/
public ConfigurableCacheFactory setConfigurableCacheFactory(ConfigurableCacheFactory ccf,
String sConfigURI, ClassLoader loader, boolean fReplace)
{
loader = ensureClassLoader(loader);
Map mapCCF = m_mapByLoader.get(loader);
ConfigurableCacheFactory ccfOld = mapCCF == null ? null : mapCCF.get(sConfigURI);
if (ccfOld != null)
{
if (!fReplace)
{
return ccfOld;
}
release(ccfOld);
}
mapCCF = ensureConfigCCFMap(loader);
mapCCF.put(sConfigURI, ccf);
return ccfOld;
}
/**
* {@inheritDoc}
*/
public synchronized void releaseAll(ClassLoader loader)
{
Map map = m_mapByLoader.get(ensureClassLoader(loader));
if (map != null)
{
List list = new ArrayList<>(map.values());
list.forEach(this::release);
list.clear();
}
m_mapByLoader.remove(ensureClassLoader(loader));
}
/**
* {@inheritDoc}
*/
public synchronized void release(ConfigurableCacheFactory factory)
{
// track ClassLoaders that no longer have any associated factories;
// since the iterator for mapByLoader is read only, these ClassLoaders
// must be removed via the Map itself (the "front door")
Set setLoader = new HashSet<>();
Map> mapByLoader = m_mapByLoader;
for (Map.Entry> entry : mapByLoader.entrySet())
{
Map mapCCF = entry.getValue();
mapCCF.values().removeIf(factory::equals);
if (mapCCF.isEmpty())
{
// this ClassLoader no longer has any associated factories; track for removal
setLoader.add(entry.getKey());
}
}
Map> mapConfigByLoader = m_mapConfigByLoader;
mapByLoader.keySet().removeAll(setLoader);
mapConfigByLoader.keySet().removeAll(setLoader);
}
// ----- helper methods -----------------------------------------------
/**
* Instantiate the default {@link ScopeResolver}.
*
* If the the {@code scope-resolver} element of the {@code cache-factory-builder}
* element of the operational configuration has been specified this will be used
* to determine the {@link ScopeResolver} implementation to use otherwise the
* {@link ScopeResolver#INSTANCE NullImplementation} resolver will be
* used.
*
* @return the default {@link ScopeResolver}
*/
protected ScopeResolver instantiateScopeResolver()
{
ScopeResolver scopeResolver = null;
XmlElement xmlConfig = CacheFactory.getCacheFactoryBuilderConfig();
XmlElement xmlResolver = xmlConfig.getElement("scope-resolver");
if (xmlResolver != null)
{
try
{
scopeResolver = (ScopeResolver) XmlHelper.createInstance(
xmlResolver, ScopedCacheFactoryBuilder.class.getClassLoader(), null);
}
catch (Exception e)
{
throw ensureRuntimeException(e, "Could not create scope resolver");
}
}
if (scopeResolver == null)
{
scopeResolver = new ScopedUriScopeResolver();
}
return scopeResolver;
}
/**
* Helper method to return a {@link ConfigurableCacheFactory} instance for the
* specified URI and class loader.
*
* @param sConfigURI the configuration URI to return a {@link ConfigurableCacheFactory} for
* @param loader the loader to return a CCF for
*
* @return a {@link ConfigurableCacheFactory} instance
*/
protected ConfigurableCacheFactory getFactory(final String sConfigURI,
final ClassLoader loader,
final ParameterResolver resolver)
{
return System.getSecurityManager() == null
? getFactoryInternal(sConfigURI, loader, resolver)
: AccessController.doPrivileged((PrivilegedAction)
() -> getFactoryInternal(sConfigURI, loader, resolver));
}
/**
* Implementation of {@link #getFactory(String, ClassLoader, ParameterResolver)}.
*/
private ConfigurableCacheFactory getFactoryInternal(String sConfigURI,
ClassLoader loader,
ParameterResolver resolver)
{
ClassLoader loaderSearch = loader = ensureClassLoader(loader);
// most likely code path: retrieve existing factory from provided
// ClassLoader or its parents; create if it doesn't exist
// Note: Returning a CCF associated with the parent's class loader
// may disallow loading classes bound to the given class loader;
// however this constraint is introduced to accommodate for the EAR /
// WAR / GAR use case
Map mapCCF;
do
{
mapCCF = m_mapByLoader.get(loaderSearch);
}
while ((mapCCF == null || !mapCCF.containsKey(sConfigURI))
&& (loaderSearch = loaderSearch.getParent()) != null);
ConfigurableCacheFactory ccf = mapCCF == null ? null : mapCCF.get(sConfigURI);
if (ccf == null)
{
synchronized (this)
{
mapCCF = ensureConfigCCFMap(loader);
ccf = mapCCF.get(sConfigURI);
if (ccf == null)
{
ccf = buildFactory(sConfigURI, loader, resolver);
}
mapCCF.put(sConfigURI, ccf);
}
}
return ccf;
}
/**
* Ensure that a map from URI to ConfigurableCacheFactory for the specified
* loader exists (creating it if necessary).
*
* @param loader the class loader to which the map corresponds
*
* @return a map from URI to ConfigurableCacheFactory
*/
protected synchronized Map ensureConfigCCFMap(ClassLoader loader)
{
Map> mapByLoader = m_mapByLoader;
Map mapCCF = mapByLoader.get(loader);
if (mapCCF == null)
{
mapCCF = new LiteMap<>();
mapByLoader.put(loader, mapCCF);
}
return mapCCF;
}
/**
* Ensure that a map from URL to ConfigurableCacheFactory for the specified
* loader exists (creating it if necessary).
*
* @param loader the class loader to which the map corresponds
*
* @return a Map from URL to ConfigurableCacheFactory
*/
protected synchronized Map ensureConfigMap(ClassLoader loader)
{
Map> mapConfigByLoader = m_mapConfigByLoader;
return mapConfigByLoader.computeIfAbsent(loader, k -> new HashMap<>());
}
/**
* Return the {@link XmlElement XML config} relating to the provided
* ClassLoader and URL, or null.
*
* @param loader the ClassLoader the XML was registered with
* @param url the URL the XML was registered with
*
* @return the XML config relating to the provided ClassLoader and URL,
* or null
*/
protected XmlElement getXmlConfig(ClassLoader loader, URL url)
{
Map mapXml = ensureConfigMap(loader);
try
{
return mapXml.get(url.toURI());
}
catch (URISyntaxException ignored) {}
return null;
}
/**
* Register the provided {@link XmlElement XML config} with the ClassLoader
* and URL.
*
* @param loader the ClassLoader the XML is to be registered with
* @param url the URL the XML is to be registered with
* @param xml the XML config to register
*/
protected void setXmlConfig(ClassLoader loader, URL url, XmlElement xml)
{
Map mapXml = ensureConfigMap(loader);
try
{
mapXml.put(url.toURI(), xml);
}
catch (URISyntaxException ignored) {}
}
/**
* Load the XML configuration from the specified URI.
*
* @param sConfigURI the configuration URI; must not be null
* @param loader class loader to use
*
* @return the XML configuration, or null if the config could not be loaded
*/
@SuppressWarnings({"ConstantValue", "AssignmentToCatchBlockParameter"})
protected synchronized XmlElement loadConfigFromURI(String sConfigURI, ClassLoader loader)
{
URL url = resolveURL(sConfigURI, loader);
XmlElement xmlConfig = getXmlConfig(loader, url);
if (xmlConfig == null)
{
try
{
xmlConfig = XmlHelper.loadResource(url, "cache configuration", loader);
setXmlConfig(loader, url, xmlConfig);
}
catch (Exception e)
{
if (e instanceof RuntimeException)
{
Throwable eOrig = getOriginalException((RuntimeException) e);
if (eOrig instanceof IOException)
{
StringBuilder sb = new StringBuilder("Could not load cache configuration resource " + url);
Throwable cause = eOrig.getCause();
if (cause != null)
{
sb.append(", Cause:").append(cause.getMessage());
}
e = new IOException(sb.toString());
}
}
throw ensureRuntimeException(e);
}
}
return xmlConfig;
}
/**
* Return the XML configuration used for the construction of a {@link ConfigurableCacheFactory}.
*
* @return the {@link XmlElement} that contains construction configuration
*/
protected XmlElement getConfigurableCacheFactoryConfig()
{
return CacheFactory.getConfigurableCacheFactoryConfig();
}
/**
* Construct and configure a {@link ConfigurableCacheFactory} for the specified
* cache config URI and {@link ClassLoader}.
*
* @param sConfigURI the URI to the cache configuration
* @param loader the {@link ClassLoader} associated with the factory
*
* @return a ConfigurableCacheFactory for the specified XML configuration
*/
protected ConfigurableCacheFactory buildFactory(String sConfigURI, ClassLoader loader)
{
return buildFactory(sConfigURI, loader, null);
}
/**
* Construct and configure a {@link ConfigurableCacheFactory} for the specified
* cache config URI and {@link ClassLoader}.
*
* @param sConfigURI the URI to the cache configuration
* @param loader the {@link ClassLoader} associated with the factory
* @param paramResolver an optional {@link ParameterResolver} to use to resolve configuration parameters
*
* @return a ConfigurableCacheFactory for the specified XML configuration
*/
protected ConfigurableCacheFactory buildFactory(String sConfigURI,
ClassLoader loader,
ParameterResolver paramResolver)
{
ScopeResolver resolver = getScopeResolver();
String sDescopedURI = resolver == null ? sConfigURI : resolver.resolveURI(sConfigURI);
String sResolvedURI = resolveURI(sDescopedURI);
XmlElement xmlConfig = loadConfigFromURI(sResolvedURI, loader);
String sScope = resolver == null ? null : resolver.resolveScopeName(sConfigURI, loader, null);
if (sScope != null && !Coherence.DEFAULT_SCOPE.equals(sScope) && !resolver.useScopeInConfig())
{
XmlElement xmlDefaults = xmlConfig.getElement("defaults");
if (xmlDefaults != null)
{
XmlElement xmlScope = xmlDefaults.getElement("scope-name");
if (xmlScope != null)
{
xmlScope.setString(sScope);
}
}
}
return instantiateFactory(loader, xmlConfig,
getConfigurableCacheFactoryConfig(), null, sScope, paramResolver);
}
/**
* Create a new instance of {@link ConfigurableCacheFactory} based on a given
* {@link ClassLoader} and cache configuration XML.
*
* @param loader the {@link ClassLoader} used to instantiate the {@link ConfigurableCacheFactory}
* @param xmlConfig the {@link XmlElement} containing the cache configuration
* @param xmlFactory the {@link XmlElement} containing the factory definition
* @param sPofConfigURI the POF configuration URI
* @param sScopeName an optional scope name
*
* @return the {@link ConfigurableCacheFactory} created
*/
@SuppressWarnings("deprecation")
protected ConfigurableCacheFactory instantiateFactory(ClassLoader loader, XmlElement xmlConfig, XmlElement xmlFactory,
String sPofConfigURI, String sScopeName, ParameterResolver resolver)
{
// temporarily allow user to select ECCF via a property
String sClass = xmlFactory.getSafeElement("class-name").getString();
try
{
if (sClass.equals(ExtensibleConfigurableCacheFactory.class.getName()))
{
Dependencies dependencies = ExtensibleConfigurableCacheFactory.DependenciesHelper.
newInstance(xmlConfig, loader, sPofConfigURI, sScopeName, null, resolver);
ExtensibleConfigurableCacheFactory eccf = new ExtensibleConfigurableCacheFactory(dependencies);
eccf.setConfigClassLoader(loader);
return eccf;
}
else if (sClass.equals(DefaultConfigurableCacheFactory.class.getName()))
{
DefaultConfigurableCacheFactory dccf = new DefaultConfigurableCacheFactory(xmlConfig);
dccf.setConfigClassLoader(loader);
sScopeName = xmlConfig.getSafeElement("scope-name").getString(sScopeName);
if (sScopeName != null && sScopeName.length() > 0)
{
dccf.setScopeName(sScopeName);
}
return dccf;
}
else
{
ConfigurableCacheFactory ccf = (ConfigurableCacheFactory)
XmlHelper.createInstance(xmlFactory, loader, null);
Method methSetConfig = ClassHelper.findMethod(ccf.getClass(), "setConfig",
new Class[]{XmlElement.class}, false);
if (methSetConfig != null)
{
ClassHelper.invoke(ccf, "setConfig", new Object[]{xmlConfig});
}
return ccf;
}
}
catch (Exception e)
{
throw ensureRuntimeException(e, "Failed to instantiate a class from the xmlConfiguration "
+ xmlConfig);
}
}
/**
* Resolve the URI that identifies the cache configuration. The URI provided
* may be a normal URL or Resource, or it may be a "special" default URI that
* is used when a specific cache configuration file is not indicated (for
* example, if the user requests a factory via {@link CacheFactory#getConfigurableCacheFactory()}.
* If the "default" URI is requested, the URI is resolved to the default
* cache configuration name indicated in the operational configuration file;
* otherwise the provided URI is returned.
*
* @param sConfigURI the passed in URI
*
* @return the resolved URI
*
* @see #URI_DEFAULT
*/
protected String resolveURI(String sConfigURI)
{
if (sConfigURI.equals(URI_DEFAULT))
{
// by convention, the "default URI" is the first parameter
// passed to DCCF per the operational configuration
// (typically "coherence-cache-config.xml")
XmlElement xmlFactory = CacheFactory.getConfigurableCacheFactoryConfig();
Object [] aoParam = XmlHelper.parseInitParams(xmlFactory.getSafeElement("init-params"));
return aoParam.length > 0 && aoParam[0] instanceof String
? (String) aoParam[0] : ExtensibleConfigurableCacheFactory.FILE_CFG_CACHE;
}
else
{
return sConfigURI;
}
}
/**
* Resolve the URL based on the provided configuration URI. The resolution
* consists of locating the URI as a resource or a file and the creation
* of a corresponding URL. If the URI cannot be located, a "placeholder"
* file URL will be created.
*
* @param sConfigURI the configuration URI to make a URL out of
* @param loader the {@link ClassLoader} to use
*
* @return a {@link URL} for the resource
*/
protected URL resolveURL(String sConfigURI, ClassLoader loader)
{
URL url = Resources.findFileOrResourceOrDefault(sConfigURI, loader);
if (url == null)
{
// if the URL does not exist, we will create a file URL so that
// we can associate a cache configuration with a URI that doesn't
// exist as a file or resource
try
{
url = new URL((sConfigURI.contains(":") ? "" : "file://")
+ sConfigURI);
}
catch (MalformedURLException e)
{
throw ensureRuntimeException(e, "The configuration URI contains illegal characters for a URL " +
sConfigURI);
}
}
return url;
}
// ----- constants and data members -----------------------------------
/**
* Scope resolver used to resolve scope name upon CCF construction.
*/
protected final ScopeResolver f_scopeResolver;
/**
* Mapping used to associate class loaders with the cache factories that are
* configured on them. The map is (weakly) keyed by class loader instances
* and holds a maps of URI to ConfigurableCacheFactory as a values
* (e.g. Map<ClassLoader, Map<URI, ConfigurableCacheFactory>>).
*/
protected Map> m_mapByLoader =
new CopyOnWriteMap<>(WeakHashMap.class);
/**
* Mapping used to associate class loaders with specific configuration elements.
* The map is (weakly) keyed by class loader instances and holds a map
* of URL to XmlElement as values.
*/
protected Map> m_mapConfigByLoader =
new CopyOnWriteMap<>(WeakHashMap.class);
}