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

org.cache2k.xmlConfiguration.CacheConfigurationProviderImpl Maven / Gradle / Ivy

package org.cache2k.xmlConfiguration;

/*
 * #%L
 * cache2k XML configuration
 * %%
 * Copyright (C) 2000 - 2016 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);
    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"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy