org.cache2k.xmlConfiguration.CacheConfigurationProviderImpl Maven / Gradle / Ivy
Show all versions of cache2k-xml-configuration Show documentation
package org.cache2k.xmlConfiguration;
/*
* #%L
* cache2k XML configuration
* %%
* Copyright (C) 2000 - 2017 headissue GmbH, Munich
* %%
* 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.
* #L%
*/
import org.cache2k.CacheException;
import org.cache2k.CacheManager;
import org.cache2k.configuration.Cache2kConfiguration;
import org.cache2k.configuration.ConfigurationSection;
import org.cache2k.configuration.ConfigurationWithSections;
import org.cache2k.configuration.CustomizationSupplierByClassName;
import org.cache2k.configuration.SingletonConfigurationSection;
import org.cache2k.core.spi.CacheConfigurationProvider;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* Hooks into cache2k and provides the additional configuration data.
*
* @author Jens Wilke
*/
public class CacheConfigurationProviderImpl implements CacheConfigurationProvider {
private static final String DEFAULT_CONFIGURATION_FILE = "cache2k.xml";
private static final Map version1SectionTypes = new HashMap() {
{
put("jcache", "org.cache2k.jcache.JCacheConfiguration");
put("byClassName", CustomizationSupplierByClassName.class.getName());
}
};
private PropertyParser propertyParser = new StandardPropertyParser();
private TokenizerFactory tokenizerFactory = new FlexibleXmlTokenizerFactory();
private volatile Map, BeanPropertyMutator> type2mutator = new HashMap, BeanPropertyMutator>();
private volatile ConfigurationContext defaultManagerContext = null;
private volatile Map manager2defaultConfig = new HashMap();
@Override
public String getDefaultManagerName(ClassLoader cl) {
synchronized (this) {
defaultManagerContext = createContext(cl, null, DEFAULT_CONFIGURATION_FILE);
}
return defaultManagerContext.getDefaultManagerName();
}
@Override
public Cache2kConfiguration getDefaultConfiguration(final CacheManager mgr) {
Cache2kConfiguration cfg = getManagerContext(mgr).getDefaultManagerConfiguration();
return copyViaSerialization(mgr, cfg);
}
@Override
public void augmentConfiguration(final CacheManager mgr, final Cache2kConfiguration cfg) {
ConfigurationContext ctx = getManagerContext(mgr);
if (!ctx.isConfigurationPresent()) {
return;
}
final String _cacheName = cfg.getName();
if (_cacheName == null) {
if (ctx.isIgnoreAnonymousCache()) {
return;
}
throw new ConfigurationException(
"Cache name missing, cannot apply XML configuration. " +
"Consider parameter: ignoreAnonymousCache");
}
ParsedConfiguration _parsedTop = readManagerConfigurationWithExceptionHandling(mgr.getClassLoader(), getFileName(mgr));
ParsedConfiguration _parsedCache = null;
ParsedConfiguration _section = extractCachesSection(_parsedTop);
if (_section != null) { _parsedCache = _section.getSection(_cacheName); }
if (_parsedCache == null) {
if (ctx.isIgnoreMissingCacheConfiguration()) {
return;
}
String _exceptionText =
"Configuration for cache '" + _cacheName + "' is missing. " +
"Consider parameter: ignoreMissingCacheConfiguration";
throw new ConfigurationException(_exceptionText, _parsedTop.getSource());
}
apply(ctx, _parsedCache, cfg);
cfg.setExternalConfigurationPresent(true);
}
private static String getFileName(CacheManager mgr) {
if (mgr.isDefaultManager()) {
return DEFAULT_CONFIGURATION_FILE;
}
return "cache2k-" + mgr.getName() + ".xml";
}
private ParsedConfiguration readManagerConfiguration(ClassLoader cl, final String _fileName) throws Exception {
InputStream is = cl.getResourceAsStream(_fileName);
if (is == null) {
return null;
}
ConfigurationTokenizer tkn = tokenizerFactory.createTokenizer(_fileName, is, null);
ParsedConfiguration cfg = ConfigurationParser.parse(tkn);
is.close();
VariableExpander _expander = new StandardVariableExpander();
_expander.expand(cfg);
return cfg;
}
private ParsedConfiguration extractCachesSection(ParsedConfiguration pc) {
ParsedConfiguration _cachesSection = pc.getSection("caches");
if (_cachesSection == null) {
return null;
}
return _cachesSection;
}
private void checkCacheConfigurationOnStartup(final ConfigurationContext ctx, final ParsedConfiguration pc) {
ParsedConfiguration _cachesSection = pc.getSection("caches");
if (_cachesSection == null) {
return;
}
for (ParsedConfiguration _cacheConfig : _cachesSection.getSections()) {
apply(ctx, _cacheConfig, new Cache2kConfiguration());
}
}
/**
* Hold the cache default configuration of a manager in a hash table. This is reused for all caches of
* one manager.
*/
private ConfigurationContext getManagerContext(final CacheManager mgr) {
ConfigurationContext ctx = manager2defaultConfig.get(mgr);
if (ctx != null) {
return ctx;
}
synchronized (this) {
if (mgr.isDefaultManager() && defaultManagerContext != null) {
ctx = defaultManagerContext;
} else {
ctx = createContext(mgr.getClassLoader(), mgr.getName(), getFileName(mgr));
}
Map m2 =
new HashMap(manager2defaultConfig);
m2.put(mgr, ctx);
manager2defaultConfig = m2;
return ctx;
}
}
private ConfigurationContext createContext(ClassLoader cl, String _managerName, String _fileName_fileName) {
ParsedConfiguration pc = readManagerConfigurationWithExceptionHandling(cl, _fileName_fileName);
ConfigurationContext ctx = new ConfigurationContext();
ctx.setClassLoader(cl);
Cache2kConfiguration _defaultConfiguration = new Cache2kConfiguration();
ctx.setDefaultManagerConfiguration(_defaultConfiguration);
ctx.setDefaultManagerName(_managerName);
if (pc != null) {
ctx.setTemplates(extractTemplates(pc));
apply(ctx, pc, ctx);
if (ctx.getVersion() != null && ctx.getVersion().startsWith("1.")) {
ctx.setPredefinedSectionTypes(version1SectionTypes);
}
applyDefaultConfigurationIfPresent(ctx, pc, _defaultConfiguration);
ctx.setConfigurationPresent(true);
if (!ctx.isSkipCheckOnStartup()) {
checkCacheConfigurationOnStartup(ctx, pc);
}
}
return ctx;
}
private ParsedConfiguration readManagerConfigurationWithExceptionHandling(final ClassLoader cl, final String _fileName) {
ParsedConfiguration pc;
try {
pc = readManagerConfiguration(cl, _fileName);
} catch (CacheException ex) {
throw ex;
} catch (Exception ex) {
throw new ConfigurationException("Reading configuration for manager from '" + _fileName + "'", ex);
}
return pc;
}
private void applyDefaultConfigurationIfPresent(final ConfigurationContext ctx,
final ParsedConfiguration _pc,
final Cache2kConfiguration _defaultConfiguration) {
ParsedConfiguration _defaults = _pc.getSection("defaults");
if (_defaults != null) {
_defaults = _defaults.getSection("cache");
}
if (_defaults != null) {
apply(ctx, _defaults, _defaultConfiguration);
}
}
private static T copyViaSerialization(CacheManager mgr, T obj) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
oos.flush();
ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
return (T) new ObjectInputStream(bin).readObject();
} catch (Exception ex) {
throw new ConfigurationException("Copying default cache configuration for manager '" + mgr.getName() + "'", ex);
}
}
/** Set properties in configuration bean based on the parsed configuration. Called by unit test. */
void apply(final ConfigurationContext ctx, final ParsedConfiguration _parsedCfg, final Object cfg) {
ParsedConfiguration _templates = ctx.getTemplates();
ConfigurationTokenizer.Property _include = _parsedCfg.getPropertyMap().get("include");
if (_include != null) {
for (String _template : _include.getValue().split(",")) {
ParsedConfiguration c2 = null;
if (_templates != null) { c2 = _templates.getSection(_template); }
if (c2 == null) {
throw new ConfigurationException("Template not found \'" + _template + "\'", _include);
}
apply(ctx, c2, cfg);
}
}
applyPropertyValues(_parsedCfg, cfg);
if (!(cfg instanceof ConfigurationWithSections)) {
return;
}
ConfigurationWithSections _configurationWithSections = (ConfigurationWithSections) cfg;
for(ParsedConfiguration _parsedSection : _parsedCfg.getSections()) {
String _sectionType = ctx.getPredefinedSectionTypes().get(_parsedSection.getName());
if (_sectionType == null) {
_sectionType = _parsedSection.getType();
}
if (_sectionType == null) {
throw new ConfigurationException("type missing or unknown", _parsedSection);
}
Class> _type;
try {
_type = Class.forName(_sectionType);
} catch (ClassNotFoundException ex) {
throw new ConfigurationException(
"class not found '" + _sectionType + "'", _parsedSection);
}
if (!handleSection(ctx, _type, _configurationWithSections, _parsedSection)
&& !handleBean(ctx, _type, cfg, _parsedSection)
&& !handleCollection(ctx, _type, cfg, _parsedSection)) {
throw new ConfigurationException("Unknown property '" + _parsedSection.getContainer() + "'", _parsedSection);
}
}
}
/**
* Create the bean, apply configuration to it and set it.
*
* @return true, if applied, false if not a property
*/
private boolean handleBean(
final ConfigurationContext ctx,
final Class> _type,
final Object cfg,
final ParsedConfiguration _parsedCfg) {
String _containerName = _parsedCfg.getContainer();
BeanPropertyMutator m = provideMutator(cfg.getClass());
Class> _targetType = m.getType(_containerName);
if (_targetType == null) {
return false;
}
if (!_targetType.isAssignableFrom(_type)) {
throw new ConfigurationException("Type mismatch, expected: '" + _targetType.getName() + "'", _parsedCfg);
}
Object _bean = createBeanAndApplyConfiguration(ctx, _type, _parsedCfg);
mutateAndCatch(cfg, m, _containerName, _bean, _parsedCfg, _bean);
return true;
}
/**
* Get appropriate collection e.g. {@link Cache2kConfiguration#getListeners()} create the bean
* and add it to the collection.
*
* @return True, if applied, false if there is no getter for a collection.
*/
private boolean handleCollection(
final ConfigurationContext ctx,
final Class> _type,
final Object cfg,
final ParsedConfiguration _parsedCfg) {
String _containerName = _parsedCfg.getContainer();
Method m;
try {
m = cfg.getClass().getMethod(constructGetterName(_containerName));
} catch (NoSuchMethodException ex) {
return false;
}
if (!Collection.class.isAssignableFrom(m.getReturnType())) {
return false;
}
Collection c;
try {
c = (Collection) m.invoke(cfg);
} catch (Exception ex) {
throw new ConfigurationException("Cannot access collection for '" + _containerName + "' " + ex, _parsedCfg);
}
Object _bean = createBeanAndApplyConfiguration(ctx, _type, _parsedCfg);
try {
c.add(_bean);
} catch (IllegalArgumentException ex) {
throw new ConfigurationException("Rejected add '" + _containerName + "': " + ex.getMessage(), _parsedCfg);
}
return true;
}
private static String constructGetterName(final String _containerName) {
return "get" + Character.toUpperCase(_containerName.charAt(0)) + _containerName.substring(1);
}
private Object createBeanAndApplyConfiguration(
final ConfigurationContext ctx,
final Class> _type,
final ParsedConfiguration _parsedCfg) {
Object _bean;
try {
_bean = _type.newInstance();
} catch (Exception ex) {
throw new ConfigurationException("Cannot instantiate bean: " + ex, _parsedCfg);
}
apply(ctx, _parsedCfg, _bean);
return _bean;
}
/**
* Create a new configuration section or reuse an existing section, if it is a singleton.
*
* No support for writing on existing sections, which means it is not possible to define a non singleton
* in the defaults section and then override values later in the cache specific configuration.
* This is not needed for version 1.0. If we want to add it later, it is best to use the name to select the
* correct section.
*
* @return true if applied, false if not a section.
*/
private boolean handleSection(
final ConfigurationContext ctx,
final Class> _type,
final ConfigurationWithSections cfg,
final ParsedConfiguration sc) {
String _containerName = sc.getContainer();
if (!"sections".equals(_containerName)) {
return false;
}
ConfigurationSection _sectionBean = cfg.getSections().getSection((Class) _type);
if (_sectionBean == null || !(_sectionBean instanceof SingletonConfigurationSection)) {
try {
_sectionBean = (ConfigurationSection) _type.newInstance();
} catch (Exception ex) {
throw new ConfigurationException("Cannot instantiate section class: " + ex, sc);
}
cfg.getSections().add(_sectionBean);
}
apply(ctx, sc, _sectionBean);
return true;
}
private void applyPropertyValues(final ParsedConfiguration cfg, final Object _bean) {
BeanPropertyMutator m = provideMutator(_bean.getClass());
for (ConfigurationTokenizer.Property p : cfg.getPropertyMap().values()) {
Class> _propertyType = m.getType(p.getName());
if (_propertyType == null) {
if ("include".equals(p.getName()) ||
"name".equals(p.getName()) ||
"type".equals(p.getName())) {
continue;
}
throw new ConfigurationException("Unknown property '" + p.getName() + "'", p);
}
Object obj;
try {
obj = propertyParser.parse(_propertyType, p.getValue());
} catch (Exception ex) {
if (ex instanceof IllegalArgumentException) {
throw new ConfigurationException("Value '" + p.getValue() + "' rejected: " + ex.getMessage(), p);
}
throw new ConfigurationException("Cannot parse property: " + ex, p);
}
mutateAndCatch(_bean, m, p, obj);
}
}
private void mutateAndCatch(
final Object cfg,
final BeanPropertyMutator m,
final ConfigurationTokenizer.Property p,
final Object obj) {
mutateAndCatch(cfg, m, p.getName(), p.getValue(), p, obj);
}
private void mutateAndCatch(
final Object cfg,
final BeanPropertyMutator m,
final String _name,
final Object _valueForExceptionText,
final SourceLocation loc,
final Object obj) {
try {
m.mutate(cfg, _name, obj);
} catch (InvocationTargetException ex) {
Throwable t = ex.getTargetException();
if (t instanceof IllegalArgumentException) {
throw new ConfigurationException("Value '" + _valueForExceptionText + "' rejected: " + t.getMessage(), loc);
}
throw new ConfigurationException("Setting property: " + ex, loc);
} catch (Exception ex) {
throw new ConfigurationException("Setting property: " + ex, loc);
}
}
private BeanPropertyMutator provideMutator(Class> _type) {
BeanPropertyMutator m = type2mutator.get(_type);
if (m == null) {
synchronized (this) {
m = new BeanPropertyMutator(_type);
Map, BeanPropertyMutator> m2 = new HashMap, BeanPropertyMutator>(type2mutator);
m2.put(_type, m);
type2mutator = m2;
}
}
return m;
}
private ParsedConfiguration extractTemplates(final ParsedConfiguration _pc) {
return _pc.getSection("templates");
}
}