package org.ehcache.xml.multi;
import org.ehcache.config.Configuration;
import org.ehcache.xml.XmlConfiguration;
import org.ehcache.xml.exceptions.XmlConfigurationException;
import org.ehcache.xml.multi.model.Configurations;
import org.ehcache.xml.multi.model.ObjectFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
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.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.Collections.unmodifiableSet;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static org.ehcache.xml.ConfigurationParser.discoverSchema;
import static org.ehcache.xml.ConfigurationParser.documentBuilder;
import static org.ehcache.xml.ConfigurationParser.documentToText;
import static org.ehcache.xml.ConfigurationParser.urlToText;
import static org.ehcache.xml.XmlConfiguration.CORE_SCHEMA_URL;
public class XmlMultiConfiguration {
private static final URL MULTI_SCHEMA_URL = XmlMultiConfiguration.class.getResource("/ehcache-multi.xsd");
private static final QName MULTI_SCHEMA_ROOT_NAME = new QName(
Configurations.class.getPackage().getAnnotation(XmlSchema.class).namespace(),
Configurations.class.getAnnotation(XmlRootElement.class).name());
private final Map<String, Config> configurations;
private final Document document;
private final String renderedDocument;
@SuppressWarnings("unchecked")
private XmlMultiConfiguration(URL url, BiFunction<String, Document, XmlConfiguration> configParser) throws XmlConfigurationException {
try {
Schema schema = discoverSchema(new StreamSource(CORE_SCHEMA_URL.openStream()), new StreamSource(MULTI_SCHEMA_URL.openStream()));
DocumentBuilder domBuilder = documentBuilder(schema);
this.document = domBuilder.parse(url.toExternalForm());
this.renderedDocument = urlToText(url, document.getInputEncoding());
Element rootElement = document.getDocumentElement();
QName rootName = new QName(rootElement.getNamespaceURI(), rootElement.getLocalName());
if (!MULTI_SCHEMA_ROOT_NAME.equals(rootName)) {
throw new XmlConfigurationException("Expecting " + MULTI_SCHEMA_ROOT_NAME + " element; found " + rootName);
}
JAXBContext jaxbContext = JAXBContext.newInstance(Configurations.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Configurations value = unmarshaller.unmarshal(rootElement, Configurations.class).getValue();
this.configurations = value.getConfiguration().stream().collect(toMap(Configurations.Configuration::getIdentity, c -> {
List<Object> configOrVariant = c.getConfigOrVariant();
if (configOrVariant.size() == 1 && configOrVariant.get(0) instanceof Node) {
Document configDoc = domBuilder.newDocument();
configDoc.appendChild(configDoc.importNode((Element) configOrVariant.get(0), true));
return new SingleConfig(configParser.apply(c.getIdentity(), configDoc));
} else {
return new VariantConfig(configOrVariant.stream()
.map(e -> (JAXBElement<Configurations.Configuration.Variant>) e)
.map(JAXBElement::getValue)
.collect(toMap(Configurations.Configuration.Variant::getType, v -> {
Document configDoc = domBuilder.newDocument();
configDoc.appendChild(configDoc.importNode(v.getConfig(), true));
return configParser.apply(c.getIdentity(), configDoc);
})));
}
}));
} catch (ParserConfigurationException | SAXException | IOException | JAXBException e) {
throw new XmlConfigurationException(e);
}
}
private XmlMultiConfiguration(Map<String, Config> configurations) {
try {
Schema schema = discoverSchema(new StreamSource(CORE_SCHEMA_URL.openStream()), new StreamSource(MULTI_SCHEMA_URL.openStream()));
this.configurations = configurations;
ObjectFactory objectFactory = new ObjectFactory();
Configurations jaxb = objectFactory.createConfigurations().withConfiguration(configurations.entrySet().stream().map(
entry -> objectFactory.createConfigurationsConfiguration().withIdentity(entry.getKey()).withConfigOrVariant(entry.getValue().unparse(objectFactory))
).collect(toList()));
JAXBContext jaxbContext = JAXBContext.newInstance(Configurations.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setSchema(schema);
this.document = documentBuilder(schema).newDocument();
marshaller.marshal(jaxb, document);
this.renderedDocument = documentToText(document);
} catch (JAXBException | IOException | TransformerException | ParserConfigurationException | SAXException e) {
throw new XmlConfigurationException(e);
}
}
public Configuration configuration(String identity) throws IllegalArgumentException {
Config variants = configurations.get(identity);
if (variants == null) {
return null;
} else {
return variants.configuration();
}
}
public Configuration configuration(String identity, String variant) {
Config config = configurations.get(identity);
if (config == null) {
return null;
} else {
return config.configuration(variant);
}
}
public Set<String> variants(String identity) throws IllegalArgumentException {
Config config = configurations.get(identity);
if (config == null) {
throw new IllegalArgumentException("Identity " + identity + " does not exist.");
} else {
return config.variants();
}
}
public Set<String> identities() {
return unmodifiableSet(configurations.keySet());
}
public Document asDocument() {
return document;
}
public String asRenderedDocument() {
return renderedDocument;
}
@Override
public String toString() {
return asRenderedDocument();
}
private static Element unparseEhcacheConfiguration(Configuration config) {
if (config instanceof XmlConfiguration) {
return ((XmlConfiguration) config).asDocument().getDocumentElement();
} else {
return new XmlConfiguration(config).asDocument().getDocumentElement();
}
}
private interface Config {
Configuration configuration() throws IllegalStateException;
Configuration configuration(String variant);
Collection<Object> unparse(ObjectFactory factory);
Set<String> variants();
}
private static class SingleConfig implements Config {
private final Configuration config;
private SingleConfig(Configuration config) {
this.config = config;
}
@Override
public Configuration configuration() {
return config;
}
@Override
public Configuration configuration(String variant) {
return configuration();
}
@Override
public Collection<Object> unparse(ObjectFactory factory) {
return singleton(unparseEhcacheConfiguration(config));
}
@Override
public Set<String> variants() {
return emptySet();
}
}
private static class VariantConfig implements Config {
private final Map<String, Configuration> configs;
private VariantConfig(Map<String, Configuration> configs) {
this.configs = configs;
}
@Override
public Configuration configuration() {
switch (configs.size()) {
case 0:
return null;
case 1:
return configs.values().iterator().next();
default:
throw new IllegalStateException("Please choose a variant: " + configs.keySet());
}
}
@Override
public Configuration configuration(String variant) {
Configuration configuration = configs.get(variant);
if (configuration == null) {
throw new IllegalArgumentException("Please choose a valid variant: " + configs.keySet());
} else {
return configuration;
}
}
@Override
public Collection<Object> unparse(ObjectFactory factory) {
return configs.entrySet().stream()
.map(v -> factory.createConfigurationsConfigurationVariant().withType(v.getKey())
.withConfig(unparseEhcacheConfiguration(v.getValue())))
.map(factory::createConfigurationsConfigurationVariant)
.collect(toList());
}
@Override
public Set<String> variants() {
return unmodifiableSet(configs.keySet());
}
}
public static Builder from(URL xml) {
return from(new XmlMultiConfiguration(xml, (identity, dom) -> new XmlConfiguration(dom)));
}
public static Builder from(URL xml, ClassLoader classLoader) {
return from(new XmlMultiConfiguration(xml, (identity, dom) -> new XmlConfiguration(dom, classLoader)));
}
public static Builder from(XmlMultiConfiguration config) {
return new Builder() {
@Override
public Builder withManager(String identity, Configuration configuration) {
Map<String, Config> configurations = new HashMap<>(config.configurations);
configurations.put(identity, new SingleConfig(configuration));
return from(new XmlMultiConfiguration(configurations));
}
@Override
public Builder withoutManager(String identity) {
Map<String, Config> configurations = config.configurations;
configurations.remove(identity);
return from(new XmlMultiConfiguration(configurations));
}
@Override
public Variant withManager(String identity) {
Map<String, Configuration> variants = new HashMap<>();
Config current = config.configurations.get(identity);
if (current instanceof VariantConfig) {
variants.putAll(((VariantConfig) current).configs);
} else if (current != null) {
throw new IllegalStateException("Existing non-variant configuration cannot be replaced - it must be removed first.");
}
return new Variant() {
@Override
public Variant withoutVariant(String variant) {
variants.remove(variant);
return this;
}
@Override
public Variant variant(String variant, Configuration configuration) {
variants.put(variant, configuration);
return this;
}
@Override
public Builder withoutManager(String identity) {
return from(build()).withoutManager(identity);
}
@Override
public Builder withManager(String identity, Configuration configuration) {
return from(build()).withManager(identity, configuration);
}
@Override
public Variant withManager(String identity) {
return from(build()).withManager(identity);
}
@Override
public XmlMultiConfiguration build() {
Map<String, Config> configurations = new HashMap<>(config.configurations);
configurations.put(identity, new VariantConfig(variants));
return new XmlMultiConfiguration(configurations);
}
};
}
@Override
public XmlMultiConfiguration build() {
return config;
}
};
}
public static Builder fromNothing() {
return from(new XmlMultiConfiguration(emptyMap()));
}
public interface Builder {
Builder withoutManager(String identity);
Builder withManager(String identity, Configuration configuration);
default Builder withManager(String identity, org.ehcache.config.Builder<? extends Configuration> builder) {
return withManager(identity, builder.build());
}
Variant withManager(String identity);
XmlMultiConfiguration build();
}
public interface Variant extends Builder {
Variant withoutVariant(String variant);
Variant variant(String variant, Configuration configuration);
default Variant variant(String variant, org.ehcache.config.Builder<? extends Configuration> builder) {
return variant(variant, builder.build());
}
}
}