/*
* 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.clustered.client.internal.config.xml;
import org.ehcache.clustered.client.config.ClusteredStoreConfiguration;
import org.ehcache.clustered.client.config.ClusteringServiceConfiguration;
import org.ehcache.clustered.client.config.TimeoutDuration;
import org.ehcache.clustered.client.internal.store.ClusteredStore;
import org.ehcache.clustered.client.service.ClusteringService;
import org.ehcache.clustered.common.Consistency;
import org.ehcache.clustered.common.ServerSideConfiguration;
import org.ehcache.clustered.common.ServerSideConfiguration.Pool;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.spi.service.ServiceConfiguration;
import org.ehcache.spi.service.ServiceCreationConfiguration;
import org.ehcache.xml.CacheManagerServiceConfigurationParser;
import org.ehcache.xml.CacheServiceConfigurationParser;
import org.ehcache.xml.exceptions.XmlConfigurationException;
import org.ehcache.xml.model.TimeType;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import static org.ehcache.clustered.client.internal.config.xml.ClusteredCacheConstants.*;
import static org.ehcache.xml.XmlModel.convertToJavaTimeUnit;
/**
* Provides parsing support for the {@code <service>} elements representing a {@link ClusteringService ClusteringService}.
*
* @see ClusteredCacheConstants#XSD
*/
public class ClusteringServiceConfigurationParser implements CacheManagerServiceConfigurationParser<ClusteringService>,
CacheServiceConfigurationParser<ClusteredStore.Provider> {
public static final String CLUSTERED_STORE_ELEMENT_NAME = "clustered-store";
public static final String CONSISTENCY_ATTRIBUTE_NAME = "consistency";
@Override
public Source getXmlSchema() throws IOException {
return new StreamSource(XML_SCHEMA.openStream());
}
@Override
public URI getNamespace() {
return NAMESPACE;
}
@Override
public ServiceConfiguration<ClusteredStore.Provider> parseServiceConfiguration(Element fragment) {
if (CLUSTERED_STORE_ELEMENT_NAME.equals(fragment.getLocalName())) {
if (fragment.hasAttribute(CONSISTENCY_ATTRIBUTE_NAME)) {
return new ClusteredStoreConfiguration(Consistency.valueOf(fragment.getAttribute("consistency").toUpperCase()));
} else {
return new ClusteredStoreConfiguration();
}
}
throw new XmlConfigurationException(String.format("XML configuration element <%s> in <%s> is not supported",
fragment.getTagName(), (fragment.getParentNode() == null ? "null" : fragment.getParentNode().getLocalName())));
}
/**
* Complete interpretation of the top-level elements defined in <code>{@value ClusteredCacheConstants#XSD}</code>.
* This method is called only for those elements from the namespace set by {@link ClusteredCacheConstants#NAMESPACE}.
* <p>
* This method presumes the element presented is valid according to the XSD.
*
* @param fragment the XML fragment to process
*
* @return a {@link org.ehcache.clustered.client.config.ClusteringServiceConfiguration ClusteringServiceConfiguration}
*/
@Override
public ServiceCreationConfiguration<ClusteringService> parseServiceCreationConfiguration(final Element fragment) {
if ("cluster".equals(fragment.getLocalName())) {
ServerSideConfig serverConfig = null;
URI connectionUri = null;
TimeoutDuration getTimeout = null;
final NodeList childNodes = fragment.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
final Node item = childNodes.item(i);
if (Node.ELEMENT_NODE == item.getNodeType()) {
if ("connection".equals(item.getLocalName())) {
/*
* <connection> is a required element in the XSD
*/
final Attr urlAttribute = ((Element)item).getAttributeNode("url");
final String urlValue = urlAttribute.getValue();
try {
connectionUri = new URI(urlValue);
} catch (URISyntaxException e) {
throw new XmlConfigurationException(
String.format("Value of %s attribute on XML configuration element <%s> in <%s> is not a valid URI - '%s'",
urlAttribute.getName(), item.getNodeName(), fragment.getTagName(), connectionUri), e);
}
} else if ("read-timeout".equals(item.getLocalName())) {
/*
* <read-timeout> is an optional element
*/
getTimeout = processGetTimeout(fragment, item);
} else if ("server-side-config".equals(item.getLocalName())) {
/*
* <server-side-config> is an optional element
*/
serverConfig = processServerSideConfig(item);
}
}
}
try {
if (serverConfig == null) {
if (getTimeout == null) {
return new ClusteringServiceConfiguration(connectionUri);
} else {
return new ClusteringServiceConfiguration(connectionUri, getTimeout);
}
} else {
ServerSideConfiguration serverSideConfiguration;
if (serverConfig.defaultServerResource == null) {
serverSideConfiguration = new ServerSideConfiguration(serverConfig.pools);
} else {
serverSideConfiguration = new ServerSideConfiguration(serverConfig.defaultServerResource, serverConfig.pools);
}
if (getTimeout == null) {
return new ClusteringServiceConfiguration(connectionUri, serverConfig.autoCreate, serverSideConfiguration);
} else {
return new ClusteringServiceConfiguration(
connectionUri, getTimeout, serverConfig.autoCreate, serverSideConfiguration);
}
}
} catch (IllegalArgumentException e) {
throw new XmlConfigurationException(e);
}
}
throw new XmlConfigurationException(String.format("XML configuration element <%s> in <%s> is not supported",
fragment.getTagName(), (fragment.getParentNode() == null ? "null" : fragment.getParentNode().getLocalName())));
}
private TimeoutDuration processGetTimeout(Element parentElement, Node timeoutNode) {
TimeoutDuration getTimeout;
try {
// <read-timeout> is a direct subtype of ehcache:time-type; use JAXB to interpret it
JAXBContext context = JAXBContext.newInstance(TimeType.class.getPackage().getName());
Unmarshaller unmarshaller = context.createUnmarshaller();
JAXBElement<TimeType> jaxbElement = unmarshaller.unmarshal(timeoutNode, TimeType.class);
TimeType timeType = jaxbElement.getValue();
BigInteger amount = timeType.getValue();
if (amount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
throw new XmlConfigurationException(
String.format("Value of XML configuration element <%s> in <%s> exceeds allowed value - %s",
timeoutNode.getNodeName(), parentElement.getTagName(), amount));
}
getTimeout = TimeoutDuration.of(amount.longValue(), convertToJavaTimeUnit(timeType.getUnit()));
} catch (JAXBException e) {
throw new XmlConfigurationException(e);
}
return getTimeout;
}
private ServerSideConfig processServerSideConfig(Node serverSideConfigElement) {
ServerSideConfig serverSideConfig = new ServerSideConfig();
serverSideConfig.autoCreate = Boolean.parseBoolean(((Element) serverSideConfigElement).getAttribute("auto-create"));
final NodeList serverSideNodes = serverSideConfigElement.getChildNodes();
for (int i = 0; i < serverSideNodes.getLength(); i++) {
final Node item = serverSideNodes.item(i);
if (Node.ELEMENT_NODE == item.getNodeType()) {
String nodeLocalName = item.getLocalName();
if ("default-resource".equals(nodeLocalName)) {
serverSideConfig.defaultServerResource = ((Element)item).getAttribute("from");
} else if ("shared-pool".equals(nodeLocalName)) {
Element sharedPoolElement = (Element)item;
String poolName = sharedPoolElement.getAttribute("name"); // required
Attr fromAttr = sharedPoolElement.getAttributeNode("from"); // optional
String fromResource = (fromAttr == null ? null : fromAttr.getValue());
Attr unitAttr = sharedPoolElement.getAttributeNode("unit"); // optional - default 'B'
String unit = (unitAttr == null ? "B" : unitAttr.getValue());
MemoryUnit memoryUnit = MemoryUnit.valueOf(unit.toUpperCase(Locale.ENGLISH));
String quantityValue = sharedPoolElement.getFirstChild().getNodeValue();
long quantity;
try {
quantity = Long.parseLong(quantityValue);
} catch (NumberFormatException e) {
throw new XmlConfigurationException("Magnitude of value specified for <shared-pool name=\""
+ poolName + "\"> is too large");
}
Pool poolDefinition;
if (fromResource == null) {
poolDefinition = new Pool(memoryUnit.toBytes(quantity));
} else {
poolDefinition = new Pool(memoryUnit.toBytes(quantity), fromResource);
}
if (serverSideConfig.pools.put(poolName, poolDefinition) != null) {
throw new XmlConfigurationException("Duplicate definition for <shared-pool name=\"" + poolName + "\">");
}
}
}
}
return serverSideConfig;
}
private static final class ServerSideConfig {
private boolean autoCreate = false;
private String defaultServerResource = null;
private final Map<String, Pool> pools = new HashMap<String, Pool>();
}
}