org.ehcache.xml.XmlConfiguration Maven / Gradle / Ivy
Show all versions of ehcache Show documentation
/*
* Copyright Terracotta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ehcache.xml;
import org.ehcache.config.CacheConfiguration;
import org.ehcache.config.Configuration;
import org.ehcache.config.ResourcePools;
import org.ehcache.config.Builder;
import org.ehcache.config.FluentConfigurationBuilder;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.core.util.ClassLoading;
import org.ehcache.spi.service.ServiceCreationConfiguration;
import org.ehcache.xml.exceptions.XmlConfigurationException;
import org.w3c.dom.Document;
import java.lang.reflect.Array;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import static java.lang.Class.forName;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap;
import static java.util.Objects.requireNonNull;
import static org.ehcache.config.builders.ConfigurationBuilder.newConfigurationBuilder;
import static org.ehcache.xml.ConfigurationParser.documentToText;
import static org.ehcache.xml.XmlConfiguration.PrettyClassFormat.when;
/**
* Exposes {@link org.ehcache.config.Configuration} and {@link CacheConfigurationBuilder} expressed
* in a XML file that obeys the core Ehcache schema.
*
* Instances of this class are not thread-safe.
*/
public class XmlConfiguration implements Configuration {
public static final URL CORE_SCHEMA_URL = XmlConfiguration.class.getResource("/ehcache-core.xsd");
private final URL source;
private final Document document;
private final String renderedDocument;
private final Configuration configuration;
private final Map cacheClassLoaders;
private final Map templates;
/**
* Constructs an instance of XmlConfiguration mapping to the XML file located at {@code url}.
*
* The default ClassLoader will first try to use the thread context class loader, followed by the ClassLoader that
* loaded the Ehcache classes.
*
* @param url URL pointing to the XML file's location
*
* @throws XmlConfigurationException if anything went wrong parsing the XML
*/
public XmlConfiguration(URL url)
throws XmlConfigurationException {
this(url, ClassLoading.getDefaultClassLoader());
}
/**
* Constructs an instance of XmlConfiguration mapping to the XML file located at {@code url} and using the provided
* {@code classLoader} to load user types (e.g. key and value Class instances).
*
* @param url URL pointing to the XML file's location
* @param classLoader ClassLoader to use to load user types.
*
* @throws XmlConfigurationException if anything went wrong parsing the XML
*/
public XmlConfiguration(URL url, final ClassLoader classLoader)
throws XmlConfigurationException {
this(url, classLoader, Collections.emptyMap());
}
/**
* Constructs an instance of XmlConfiguration mapping to the XML file located at {@code url} and using the provided
* {@code classLoader} to load user types (e.g. key and value Class instances). The {@code cacheClassLoaders} will
* let you specify a different {@link java.lang.ClassLoader} to use for each {@link org.ehcache.Cache} managed by
* the {@link org.ehcache.CacheManager} configured using this {@link org.ehcache.xml.XmlConfiguration}. Caches with
* aliases that do not appear in the map will use {@code classLoader} as a default.
*
* @param url URL pointing to the XML file's location
* @param classLoader ClassLoader to use to load user types.
* @param cacheClassLoaders the map with mappings between cache names and the corresponding class loaders
*
* @throws XmlConfigurationException if anything went wrong parsing the XML
*/
public XmlConfiguration(URL url, final ClassLoader classLoader, final Map cacheClassLoaders)
throws XmlConfigurationException {
this.source = requireNonNull(url, "The url can not be null");
requireNonNull(classLoader, "The classLoader can not be null");
this.cacheClassLoaders = requireNonNull(cacheClassLoaders, "The cacheClassLoaders map can not be null");
try {
ConfigurationParser parser = new ConfigurationParser();
this.document = parser.uriToDocument(source.toURI());
ConfigurationParser.XmlConfigurationWrapper configWrapper = parser.documentToConfig(document, classLoader, cacheClassLoaders);
this.configuration = configWrapper.getConfiguration();
this.templates = configWrapper.getTemplates();
this.renderedDocument = ConfigurationParser.urlToText(url, document.getInputEncoding());
} catch (XmlConfigurationException e) {
throw e;
} catch (Exception e) {
throw new XmlConfigurationException("Error parsing XML configuration at " + url, e);
}
}
/**
* Constructs an instance of XmlConfiguration from the given XML DOM.
*
* The default ClassLoader will first try to use the thread context class loader, followed by the ClassLoader that
* loaded the Ehcache classes.
*
* @param xml XML Document Object Model
*
* @throws XmlConfigurationException if anything went wrong parsing the XML
*/
public XmlConfiguration(Document xml) throws XmlConfigurationException {
this(xml, ClassLoading.getDefaultClassLoader());
}
/**
* Constructs an instance of XmlConfiguration from the given XML DOM and using the provided {@code classLoader} to
* load user types (e.g. key and value Class instances).
*
* @param xml XML Document Object Model
* @param classLoader ClassLoader to use to load user types.
*
* @throws XmlConfigurationException if anything went wrong parsing the XML
*/
public XmlConfiguration(Document xml, ClassLoader classLoader) throws XmlConfigurationException {
this(xml, classLoader, emptyMap());
}
/**
* Constructs an instance of XmlConfiguration from the given XML DOM and using the provided {@code classLoader} to
* load user types (e.g. key and value Class instances). The {@code cacheClassLoaders} will let you specify a
* different {@link java.lang.ClassLoader} to use for each {@link org.ehcache.Cache} managed by the
* {@link org.ehcache.CacheManager} configured using this {@link org.ehcache.xml.XmlConfiguration}. Caches with
* aliases that do not appear in the map will use {@code classLoader} as a default.
*
* @param xml XML Document Object Model
* @param classLoader ClassLoader to use to load user types.
* @param cacheClassLoaders the map with mappings between cache names and the corresponding class loaders
*
* @throws XmlConfigurationException if anything went wrong parsing the XML
*/
public XmlConfiguration(Document xml, ClassLoader classLoader, Map cacheClassLoaders) throws XmlConfigurationException {
requireNonNull(xml, "The source-element cannot be null");
requireNonNull(classLoader, "The classLoader can not be null");
this.cacheClassLoaders = requireNonNull(cacheClassLoaders, "The cacheClassLoaders map can not be null");
this.source = null;
try {
ConfigurationParser parser = new ConfigurationParser();
this.document = xml;
ConfigurationParser.XmlConfigurationWrapper configWrapper = parser.documentToConfig(document, classLoader, cacheClassLoaders);
this.configuration = configWrapper.getConfiguration();
this.templates = configWrapper.getTemplates();
this.renderedDocument = documentToText(xml);
} catch (XmlConfigurationException e) {
throw e;
} catch (Exception e) {
throw new XmlConfigurationException("Error parsing XML configuration", e);
}
}
/**
* Constructs an instance of XmlConfiguration from an existing configuration object.
*
* @param configuration existing configuration
*
* @throws XmlConfigurationException if anything went wrong converting to XML
*/
public XmlConfiguration(Configuration configuration) throws XmlConfigurationException {
this.source = null;
this.cacheClassLoaders = emptyMap();
try {
ConfigurationParser parser = new ConfigurationParser();
this.configuration = configuration;
this.templates = emptyMap();
this.document = parser.configToDocument(configuration);
this.renderedDocument = documentToText(document);
} catch (XmlConfigurationException e) {
throw e;
} catch (Exception e) {
throw new XmlConfigurationException("Error unparsing configuration: " + configuration, e);
}
}
/**
* Return this configuration as an XML {@link org.w3c.dom.Document}.
*
* @return configuration XML DOM.
*/
public Document asDocument() {
return document;
}
/**
* Return this configuration as a rendered XML string.
*
* @return configuration XML string
*/
public String asRenderedDocument() {
return renderedDocument;
}
@Override
public String toString() {
return asRenderedDocument();
}
/**
* Exposes the URL where the XML file parsed or yet to be parsed was or will be sourced from.
* @return The URL provided at object instantiation
*/
public URL getURL() {
return source;
}
/**
* Creates a new {@link CacheConfigurationBuilder} seeded with the cache-template configuration
* by the given {@code name} in the parsed XML configuration.
*
* Note that this version does not specify resources, which are mandatory to create a
* {@link CacheConfigurationBuilder}. So if the template does not define resources, this will throw.
*
* @param name the unique name identifying the cache-template element in the XML
* @param keyType the type of keys for the {@link CacheConfigurationBuilder} to use, must
* match the {@code key-type} declared in the template if declared in XML
* @param valueType the type of values for the {@link CacheConfigurationBuilder} to use, must
* match the {@code value-type} declared in the template if declared in XML
* @param type of keys
* @param type of values
*
* @return the preconfigured {@link CacheConfigurationBuilder}
* or {@code null} if no cache-template for the provided {@code name}
*
* @throws IllegalStateException if the template does not configure resources.
* @throws IllegalArgumentException if {@code keyType} or {@code valueType} don't match the declared type(s) of the template
* @throws ClassNotFoundException if a {@link java.lang.Class} declared in the XML couldn't be found
* @throws InstantiationException if a user provided {@link java.lang.Class} couldn't get instantiated
* @throws IllegalAccessException if a method (including constructor) couldn't be invoked on a user provided type
*/
@SuppressWarnings("unchecked")
public CacheConfigurationBuilder newCacheConfigurationBuilderFromTemplate(final String name,
final Class keyType,
final Class valueType)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Template template = templates.get(name);
if (template == null) {
return null;
} else {
return template.builderFor(cacheClassLoaders.getOrDefault(name, getClassLoader()), keyType, valueType, null);
}
}
/**
* Creates a new {@link CacheConfigurationBuilder} seeded with the cache-template configuration
* by the given {@code name} in the parsed XML configuration.
*
* @param name the unique name identifying the cache-template element in the XML
* @param keyType the type of keys for the {@link CacheConfigurationBuilder} to use, must
* match the {@code key-type} declared in the template if declared in XML
* @param valueType the type of values for the {@link CacheConfigurationBuilder} to use, must
* match the {@code value-type} declared in the template if declared in XML
* @param resourcePools Resources definitions that will be used
* @param type of keys
* @param type of values
*
* @return the preconfigured {@link CacheConfigurationBuilder}
* or {@code null} if no cache-template for the provided {@code name}
*
* @throws IllegalArgumentException if {@code keyType} or {@code valueType} don't match the declared type(s) of the template
* @throws ClassNotFoundException if a {@link java.lang.Class} declared in the XML couldn't be found
* @throws InstantiationException if a user provided {@link java.lang.Class} couldn't get instantiated
* @throws IllegalAccessException if a method (including constructor) couldn't be invoked on a user provided type
*/
@SuppressWarnings("unchecked")
public CacheConfigurationBuilder newCacheConfigurationBuilderFromTemplate(final String name,
final Class keyType,
final Class valueType,
final ResourcePools resourcePools)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Template template = templates.get(name);
if (template == null) {
return null;
} else {
return template.builderFor(cacheClassLoaders.getOrDefault(name, getClassLoader()), keyType, valueType, requireNonNull(resourcePools));
}
}
/**
* Creates a new {@link CacheConfigurationBuilder} seeded with the cache-template configuration
* by the given {@code name} in the parsed XML configuration.
*
* @param name the unique name identifying the cache-template element in the XML
* @param keyType the type of keys for the {@link CacheConfigurationBuilder} to use, must
* match the {@code key-type} declared in the template if declared in XML
* @param valueType the type of values for the {@link CacheConfigurationBuilder} to use, must
* match the {@code value-type} declared in the template if declared in XML
* @param resourcePoolsBuilder Resources definitions that will be used
* @param type of keys
* @param type of values
*
* @return the preconfigured {@link CacheConfigurationBuilder}
* or {@code null} if no cache-template for the provided {@code name}
*
* @throws IllegalArgumentException if {@code keyType} or {@code valueType} don't match the declared type(s) of the template
* @throws ClassNotFoundException if a {@link java.lang.Class} declared in the XML couldn't be found
* @throws InstantiationException if a user provided {@link java.lang.Class} couldn't get instantiated
* @throws IllegalAccessException if a method (including constructor) couldn't be invoked on a user provided type
*/
@SuppressWarnings("unchecked")
public CacheConfigurationBuilder newCacheConfigurationBuilderFromTemplate(final String name,
final Class keyType,
final Class valueType,
final Builder extends ResourcePools> resourcePoolsBuilder)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return newCacheConfigurationBuilderFromTemplate(name, keyType, valueType, resourcePoolsBuilder.build());
}
@Override
public Map> getCacheConfigurations() {
return configuration.getCacheConfigurations();
}
@Override
public Collection> getServiceCreationConfigurations() {
return configuration.getServiceCreationConfigurations();
}
@Override
public ClassLoader getClassLoader() {
return configuration.getClassLoader();
}
@Override
public FluentConfigurationBuilder> derive() {
return newConfigurationBuilder(this);
}
public interface Template {
CacheConfigurationBuilder builderFor(ClassLoader classLoader, Class keyType, Class valueType, ResourcePools resourcePools) throws ClassNotFoundException, InstantiationException, IllegalAccessException;
}
public static Class> getClassForName(String name, ClassLoader classLoader) throws ClassNotFoundException {
String klazz = name.trim();
return PRETTY_FORMATS.stream().filter(p -> p.applies().test(klazz)).findFirst().map(PrettyClassFormat::lookup).orElseThrow(AssertionError::new).lookup(klazz, classLoader);
}
private static final List PRETTY_FORMATS = asList(
//Primitive Types
when("boolean"::equals).then((n, l) -> Boolean.TYPE),
when("byte"::equals).then((n, l) -> Byte.TYPE),
when("short"::equals).then((n, l) -> Short.TYPE),
when("int"::equals).then((n, l) -> Integer.TYPE),
when("long"::equals).then((n, l) -> Long.TYPE),
when("char"::equals).then((n, l) -> Character.TYPE),
when("float"::equals).then((n, l) -> Float.TYPE),
when("double"::equals).then((n, l) -> Double.TYPE),
//Java Language Array Syntax
when(n -> n.endsWith("[]")).then((n, l) -> {
String component = n.split("(\\[\\])+$", 2)[0];
int dimensions = (n.length() - component.length()) >> 1;
return Array.newInstance(getClassForName(component, l), new int[dimensions]).getClass();
}),
//Inner Classes
when(n -> n.contains(".")).then((n, l) -> {
try {
return forName(n, false, l);
} catch (ClassNotFoundException e) {
int innerSeperator = n.lastIndexOf(".");
if (innerSeperator == -1) {
throw e;
} else {
return forName(n.substring(0, innerSeperator) + "$" + n.substring(innerSeperator + 1), false, l);
}
}
}),
//Everything Else
when(n -> true).then((n, l) -> forName(n, false, l))
);
interface PrettyClassFormat {
static Builder when(Predicate predicate) {
return lookup -> new PrettyClassFormat() {
@Override
public Predicate applies() {
return predicate;
}
@Override
public Lookup lookup() {
return lookup;
}
};
}
Predicate applies();
Lookup lookup();
interface Builder {
PrettyClassFormat then(Lookup lookup);
}
}
private interface Lookup {
Class> lookup(String name, ClassLoader loader) throws ClassNotFoundException;
}
}