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

org.ehcache.xml.ConfigurationParser Maven / Gradle / Ivy

There is a newer version: 3.10.8
Show newest version
/*
 * 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.FluentConfigurationBuilder;
import org.ehcache.config.ResourcePools;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.core.util.ClassLoading;
import org.ehcache.xml.exceptions.XmlConfigurationException;
import org.ehcache.xml.model.BaseCacheType;
import org.ehcache.xml.model.CacheDefinition;
import org.ehcache.xml.model.CacheEntryType;
import org.ehcache.xml.model.CacheTemplate;
import org.ehcache.xml.model.CacheTemplateType;
import org.ehcache.xml.model.CacheType;
import org.ehcache.xml.model.ConfigType;
import org.ehcache.xml.model.ObjectFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.PrivilegedAction;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static java.security.AccessController.doPrivileged;
import static java.util.Arrays.asList;
import static java.util.Spliterators.spliterator;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toMap;
import static org.ehcache.config.builders.CacheConfigurationBuilder.newCacheConfigurationBuilder;
import static org.ehcache.config.builders.ConfigurationBuilder.newConfigurationBuilder;
import static org.ehcache.config.builders.ResourcePoolsBuilder.newResourcePoolsBuilder;
import static org.ehcache.core.util.ClassLoading.servicesOfType;
import static org.ehcache.xml.XmlConfiguration.CORE_SCHEMA_URL;
import static org.ehcache.xml.XmlConfiguration.getClassForName;

/**
 * Provides support for parsing a cache configuration expressed in XML.
 */
public class ConfigurationParser {

  private static final Pattern SYSPROP = Pattern.compile("\\$\\{([^}]+)\\}");
  private static final SchemaFactory XSD_SCHEMA_FACTORY = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
  private static Schema newSchema(Source... schemas) throws SAXException {
    synchronized (XSD_SCHEMA_FACTORY) {
      return XSD_SCHEMA_FACTORY.newSchema(schemas);
    }
  }
  private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();

  private static final QName CORE_SCHEMA_ROOT_NAME;
  static {
    ObjectFactory objectFactory = new ObjectFactory();
    CORE_SCHEMA_ROOT_NAME = objectFactory.createConfig(objectFactory.createConfigType()).getName();
  }

  static final CoreCacheConfigurationParser CORE_CACHE_CONFIGURATION_PARSER = new CoreCacheConfigurationParser();

  private final Schema schema;
  private final JAXBContext jaxbContext = JAXBContext.newInstance(ConfigType.class);
  private final DocumentBuilder documentBuilder;

  private final ServiceCreationConfigurationParser serviceCreationConfigurationParser;
  private final ServiceConfigurationParser serviceConfigurationParser;
  private final ResourceConfigurationParser resourceConfigurationParser;

  static String replaceProperties(String originalValue) {
    Matcher matcher = SYSPROP.matcher(originalValue);

    StringBuffer sb = new StringBuffer();
    while (matcher.find()) {
      final String property = matcher.group(1);
      final String value = doPrivileged((PrivilegedAction) () -> System.getProperty(property));
      if (value == null) {
        throw new IllegalStateException(String.format("Replacement for ${%s} not found!", property));
      }
      matcher.appendReplacement(sb, Matcher.quoteReplacement(value));
    }
    matcher.appendTail(sb);
    final String resolvedValue = sb.toString();
    return resolvedValue.equals(originalValue) ? null : resolvedValue;
  }

  @SuppressWarnings("unchecked")
  private static  Stream stream(Iterable iterable) {
    return StreamSupport.stream(spliterator((Iterator) iterable.iterator(), Long.MAX_VALUE, 0), false);
  }

  ConfigurationParser() throws IOException, SAXException, JAXBException, ParserConfigurationException {
    serviceCreationConfigurationParser = ConfigurationParser.>stream(
      servicesOfType(CacheManagerServiceConfigurationParser.class))
      .collect(collectingAndThen(toMap(CacheManagerServiceConfigurationParser::getServiceType, identity(),
        (a, b) -> a.getClass().isInstance(b) ? b : a), ServiceCreationConfigurationParser::new));

    serviceConfigurationParser = ConfigurationParser.>stream(
      servicesOfType(CacheServiceConfigurationParser.class))
      .collect(collectingAndThen(toMap(CacheServiceConfigurationParser::getServiceType, identity(),
        (a, b) -> a.getClass().isInstance(b) ? b : a), ServiceConfigurationParser::new));

    resourceConfigurationParser = stream(servicesOfType(CacheResourceConfigurationParser.class))
      .flatMap(p -> p.getResourceTypes().stream().map(t -> new AbstractMap.SimpleImmutableEntry<>(t, p)))
      .collect(collectingAndThen(toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a.getClass().isInstance(b) ? b : a),
        m -> new ResourceConfigurationParser(new HashSet<>(m.values()))));

    schema = discoverSchema(new StreamSource(CORE_SCHEMA_URL.openStream()));
    documentBuilder = documentBuilder(schema);
  }

   CacheConfigurationBuilder parseServiceConfigurations(CacheConfigurationBuilder cacheBuilder,
                                                                    ClassLoader cacheClassLoader, CacheTemplate cacheDefinition)
    throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    cacheBuilder = CORE_CACHE_CONFIGURATION_PARSER.parseConfiguration(cacheDefinition, cacheClassLoader, cacheBuilder);
    return serviceConfigurationParser.parseConfiguration(cacheDefinition, cacheClassLoader, cacheBuilder);
  }

  private static void substituteSystemProperties(Node node) {
    Stack nodeLists = new Stack<>();
    nodeLists.push(node.getChildNodes());
    while (!nodeLists.isEmpty()) {
      NodeList nodeList = nodeLists.pop();
      for (int i = 0; i < nodeList.getLength(); ++i) {
        Node currentNode = nodeList.item(i);
        if (currentNode.hasChildNodes()) {
          nodeLists.push(currentNode.getChildNodes());
        }
        final NamedNodeMap attributes = currentNode.getAttributes();
        if (attributes != null) {
          for (int j = 0; j < attributes.getLength(); ++j) {
            final Node attributeNode = attributes.item(j);
            final String newValue = replaceProperties(attributeNode.getNodeValue());
            if (newValue != null) {
              attributeNode.setNodeValue(newValue);
            }
          }
        }
        if (currentNode.getNodeType() == Node.TEXT_NODE) {
          final String newValue = replaceProperties(currentNode.getNodeValue());
          if (newValue != null) {
            currentNode.setNodeValue(newValue);
          }
        }
      }
    }
  }

  private static Iterable getCacheElements(ConfigType configType) {
    List cacheCfgs = new ArrayList<>();
    final List cacheOrCacheTemplate = configType.getCacheOrCacheTemplate();
    for (BaseCacheType baseCacheType : cacheOrCacheTemplate) {
      if(baseCacheType instanceof CacheType) {
        final CacheType cacheType = (CacheType)baseCacheType;

        final BaseCacheType[] sources;
        if(cacheType.getUsesTemplate() != null) {
          sources = new BaseCacheType[2];
          sources[0] = cacheType;
          sources[1] = (BaseCacheType) cacheType.getUsesTemplate();
        } else {
          sources = new BaseCacheType[1];
          sources[0] = cacheType;
        }

        cacheCfgs.add(new CacheDefinition(cacheType.getAlias(), sources));
      }
    }

    return Collections.unmodifiableList(cacheCfgs);
  }

  private Map getTemplates(ConfigType configType) {
    final Map templates = new HashMap<>();
    final List cacheOrCacheTemplate = configType.getCacheOrCacheTemplate();
    for (BaseCacheType baseCacheType : cacheOrCacheTemplate) {
      if (baseCacheType instanceof CacheTemplateType) {
        final CacheTemplate cacheTemplate = new CacheTemplate.Impl(((CacheTemplateType) baseCacheType));
        templates.put(cacheTemplate.id(), parseTemplate(cacheTemplate));
      }
    }
    return Collections.unmodifiableMap(templates);
  }

  private XmlConfiguration.Template parseTemplate(CacheTemplate template) {
    return new XmlConfiguration.Template() {
      @Override
      public  CacheConfigurationBuilder builderFor(ClassLoader classLoader, Class keyType, Class valueType, ResourcePools resources) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        checkTemplateTypeConsistency("key", classLoader, keyType, template);
        checkTemplateTypeConsistency("value", classLoader, valueType, template);

        if ((resources == null || resources.getResourceTypeSet().isEmpty()) && template.getHeap() == null && template.getResources().isEmpty()) {
          throw new IllegalStateException("Template defines no resources, and none were provided");
        }

        if (resources == null) {
          resources = resourceConfigurationParser.parseResourceConfiguration(template, newResourcePoolsBuilder());
        }

        return parseServiceConfigurations(newCacheConfigurationBuilder(keyType, valueType, resources), classLoader, template);
      }
    };
  }

  private static  void checkTemplateTypeConsistency(String type, ClassLoader classLoader, Class providedType, CacheTemplate template) throws ClassNotFoundException {
    Class templateType;
    if (type.equals("key")) {
      templateType = getClassForName(template.keyType(), classLoader);
    } else {
      templateType = getClassForName(template.valueType(), classLoader);
    }

    if(providedType == null || !templateType.isAssignableFrom(providedType)) {
      throw new IllegalArgumentException("CacheTemplate '" + template.id() + "' declares " + type + " type of " + templateType.getName() + ". Provided: " + providedType);
    }
  }

  public Document uriToDocument(URI uri) throws IOException, SAXException {
    return documentBuilder.parse(uri.toString());
  }

  public XmlConfigurationWrapper documentToConfig(Document document, ClassLoader classLoader, Map cacheClassLoaders) throws JAXBException, ClassNotFoundException, InstantiationException, IllegalAccessException {
    substituteSystemProperties(document);

    Element root = document.getDocumentElement();

    QName rootName = new QName(root.getNamespaceURI(), root.getLocalName());
    if (!CORE_SCHEMA_ROOT_NAME.equals(rootName)) {
      throw new XmlConfigurationException("Expecting " + CORE_SCHEMA_ROOT_NAME + " element; found " + rootName);
    }

    Class configTypeClass = ConfigType.class;
    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    ConfigType jaxbModel = unmarshaller.unmarshal(document, configTypeClass).getValue();

    FluentConfigurationBuilder managerBuilder = newConfigurationBuilder().withClassLoader(classLoader);
    managerBuilder = serviceCreationConfigurationParser.parseServiceCreationConfiguration(jaxbModel, classLoader, managerBuilder);

    for (CacheDefinition cacheDefinition : getCacheElements(jaxbModel)) {
      String alias = cacheDefinition.id();
      if(managerBuilder.getCache(alias) != null) {
        throw new XmlConfigurationException("Two caches defined with the same alias: " + alias);
      }

      ClassLoader cacheClassLoader = cacheClassLoaders.get(alias);
      boolean classLoaderConfigured = cacheClassLoader != null;

      if (cacheClassLoader == null) {
        if (classLoader != null) {
          cacheClassLoader = classLoader;
        } else {
          cacheClassLoader = ClassLoading.getDefaultClassLoader();
        }
      }

      Class keyType = getClassForName(cacheDefinition.keyType(), cacheClassLoader);
      Class valueType = getClassForName(cacheDefinition.valueType(), cacheClassLoader);

      ResourcePools resourcePools = resourceConfigurationParser.parseResourceConfiguration(cacheDefinition, newResourcePoolsBuilder());

      CacheConfigurationBuilder cacheBuilder = newCacheConfigurationBuilder(keyType, valueType, resourcePools);
      if (classLoaderConfigured) {
        cacheBuilder = cacheBuilder.withClassLoader(cacheClassLoader);
      }

      cacheBuilder = parseServiceConfigurations(cacheBuilder, cacheClassLoader, cacheDefinition);
      managerBuilder = managerBuilder.withCache(alias, cacheBuilder.build());
    }

    Map templates = getTemplates(jaxbModel);

    return new XmlConfigurationWrapper(managerBuilder.build(), templates);
  }

  public Document configToDocument(Configuration configuration) throws JAXBException {
    ConfigType configType = new ConfigType();

    serviceCreationConfigurationParser.unparseServiceCreationConfiguration(configuration, configType);

    for (Map.Entry> cacheConfigurationEntry : configuration.getCacheConfigurations().entrySet()) {
      CacheConfiguration cacheConfiguration = cacheConfigurationEntry.getValue();

      CacheType cacheType = new CacheType().withAlias(cacheConfigurationEntry.getKey())
        .withKeyType(new CacheEntryType().withValue(cacheConfiguration.getKeyType().getName()))
        .withValueType(new CacheEntryType().withValue(cacheConfiguration.getValueType().getName()));

      resourceConfigurationParser.unparseResourceConfiguration(cacheConfiguration.getResourcePools(), cacheType);

      CORE_CACHE_CONFIGURATION_PARSER.unparseConfiguration(cacheConfiguration, cacheType);
      serviceConfigurationParser.unparseServiceConfiguration(cacheConfiguration, cacheType);
      configType.withCacheOrCacheTemplate(cacheType);
    }

    JAXBElement root = new ObjectFactory().createConfig(configType);

    Marshaller marshaller = jaxbContext.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    marshaller.setSchema(schema);

    Document document = documentBuilder.newDocument();
    marshaller.marshal(root, document);
    return document;
  }

  public static class FatalErrorHandler implements ErrorHandler {

    @Override
    public void warning(SAXParseException exception) throws SAXException {
      throw exception;
    }

    @Override
    public void error(SAXParseException exception) throws SAXException {
      throw exception;
    }

    @Override
    public void fatalError(SAXParseException exception) throws SAXException {
      throw exception;
    }
  }

  public static class XmlConfigurationWrapper {
    private final Configuration configuration;
    private final Map templates;

    public XmlConfigurationWrapper(Configuration configuration, Map templates) {
      this.configuration = configuration;
      this.templates = templates;
    }

    public Configuration getConfiguration() {
      return configuration;
    }

    public Map getTemplates() {
      return templates;
    }
  }

  public static String documentToText(Document xml) throws IOException, TransformerException {
    try (StringWriter writer = new StringWriter()) {
      transformer().transform(new DOMSource(xml), new StreamResult(writer));
      return writer.toString();
    }
  }

  private static Transformer transformer() throws TransformerConfigurationException {
    Transformer transformer = TRANSFORMER_FACTORY.newTransformer();
    transformer.setOutputProperty(OutputKeys.METHOD, "xml");
    transformer.setOutputProperty(OutputKeys.ENCODING, StandardCharsets.UTF_8.name());
    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
    return transformer;
  }

  public static String urlToText(URL url, String encoding) throws IOException {
    Charset charset = encoding == null ? StandardCharsets.UTF_8 : Charset.forName(encoding);
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), charset))) {
      return reader.lines().collect(joining(System.lineSeparator()));
    }
  }

  public static DocumentBuilder documentBuilder(Schema schema) throws ParserConfigurationException {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(true);
    factory.setIgnoringComments(true);
    factory.setIgnoringElementContentWhitespace(true);
    factory.setSchema(schema);
    DocumentBuilder documentBuilder = factory.newDocumentBuilder();
    documentBuilder.setErrorHandler(new FatalErrorHandler());
    return documentBuilder;
  }

  public static Schema discoverSchema(Source ... fixedSources) throws SAXException, IOException {
    ArrayList schemaSources = new ArrayList<>(asList(fixedSources));
    for (CacheManagerServiceConfigurationParser p : servicesOfType(CacheManagerServiceConfigurationParser.class)) {
      schemaSources.add(p.getXmlSchema());
    }
    for (CacheServiceConfigurationParser p : servicesOfType(CacheServiceConfigurationParser.class)) {
      schemaSources.add(p.getXmlSchema());
    }
    for (CacheResourceConfigurationParser p : servicesOfType(CacheResourceConfigurationParser.class)) {
      schemaSources.add(p.getXmlSchema());
    }
    return newSchema(schemaSources.toArray(new Source[0]));
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy