/*******************************************************************************
* Copyright (c) 2006-2010 eBay Inc. All Rights Reserved.
* 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
*******************************************************************************/
package org.ebayopensource.turmeric.runtime.common.impl.internal.config;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.ebayopensource.turmeric.runtime.common.exceptions.ErrorDataFactory;
import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceCreationException;
import org.ebayopensource.turmeric.runtime.common.pipeline.TransportOptions;
import org.ebayopensource.turmeric.runtime.errorlibrary.ErrorConstants;
// Copy non-null source data into the destination holder.
// The very first time the ServiceInvokerOptions is initialized,
public class MessageProcessorConfigMapper {
public static void map(String filename,
NodeList customSerializers,
String errorClass,
String errorDataProviderClass,
Element pipelineConfig,
NodeList protocolProcessors,
NodeList transports,
Element dataBindingConfig,
MessageProcessorConfigHolder dst) throws ServiceCreationException {
dst.setConfigFilename(filename);
if (errorClass != null && errorClass.length() > 0) {
dst.setErrorMappingClass(errorClass);
}
if (errorDataProviderClass != null && errorDataProviderClass.length() > 0) {
dst.setErrorDataProviderClass(errorDataProviderClass);
}
mapCustomSerializers(filename, customSerializers, dst);
mapPipelineConfig(filename, pipelineConfig, dst);
mapProtocolProcessor(filename, protocolProcessors, dst);
mapTransports(filename, transports, dst);
mapDataBindingConfig(filename, dataBindingConfig, dst);
}
// Walk the outer list of all bindings (each of which has a list of
// custom serializer/deserializer entries).
// For each such binding, get the map associated with its binding name.
// Then, take the serializer list for that particular binding, and
// add item by item into the map.
// The result is an outer hashmap keyed by binding names, each entry giving
// an inner hashmap keyed by XML type name.
// The group data is stored into this map, and the instance data
// is stored, effectively allowing the instance data to add to/replace
// any entries based on binding name and XML type name.
private static void mapCustomSerializers(String filename, NodeList customSerializers, MessageProcessorConfigHolder dstConfig) throws ServiceCreationException {
if (customSerializers == null) {
return;
}
for (int i = 0; i < customSerializers.getLength(); i++) {
Element customSerializer = (Element) customSerializers.item(i);
mapOneCustomSerializer(filename, customSerializer, dstConfig);
}
}
private static void mapOneCustomSerializer(String filename, Element customSerializer, MessageProcessorConfigHolder dstConfig) throws ServiceCreationException {
String bindingName = customSerializer.getAttribute("binding");
if (bindingName == null) {
DomParseUtils.throwError(filename, "No binding name specified for custom-serializers entry");
}
Map<String, CustomSerializerConfig> serializerMap = dstConfig.getCustomSerializerMap(bindingName);
Map<String, TypeConverterConfig> typeConverterMap = dstConfig.getTypeConverterMap(bindingName);
NodeList customSerializerDeserializers = DomParseUtils.getImmediateChildrenByTagName(customSerializer, "custom-serializer-deserializer");
for (int i = 0; i < customSerializerDeserializers.getLength(); i++) {
Element customSerializerDeserializer = (Element) customSerializerDeserializers.item(i);
CustomSerializerConfig config = new CustomSerializerConfig();
// No merging going on here, straight create with null or with data
String javaTypeName = DomParseUtils.getElementText(filename, customSerializerDeserializer, "java-type-name");
config.setJavaTypeName(javaTypeName);
String serClassName = DomParseUtils.getElementText(filename, customSerializerDeserializer, "serializer-class-name");
config.setSerializerClassName(serClassName);
String deserClassName = DomParseUtils.getElementText(filename, customSerializerDeserializer, "deserializer-class-name");
config.setDeserializerClassName(deserClassName);
String xmlTypeName = DomParseUtils.getElementText(filename, customSerializerDeserializer, "xml-type-name");
config.setXmlTypeName(xmlTypeName);
serializerMap.put(javaTypeName, config);
}
NodeList typeConverters = DomParseUtils.getImmediateChildrenByTagName(customSerializer, "type-converter");
for (int i = 0; i < typeConverters.getLength(); i++) {
Element typeConverter = (Element) typeConverters.item(i);
TypeConverterConfig config = new TypeConverterConfig();
String boundJavaTypeName = DomParseUtils.getElementText(filename, typeConverter, "bound-java-type-name");
config.setBoundJavaTypeName(boundJavaTypeName);
String valueJavaTypeName = DomParseUtils.getElementText(filename, typeConverter, "value-java-type-name");
config.setValueJavaTypeName(valueJavaTypeName);
String typeConverterClassName = DomParseUtils.getElementText(filename, typeConverter, "type-converter-class-name");
config.setTypeConverterClassName(typeConverterClassName);
String xmlTypeName = DomParseUtils.getElementText(filename, typeConverter, "xml-type-name");
config.setXmlTypeName(xmlTypeName);
typeConverterMap.put(boundJavaTypeName, config);
}
}
private static void mapPipelineConfig(String filename, Element pipelineConfig, MessageProcessorConfigHolder mpConfig) throws ServiceCreationException {
if (pipelineConfig == null) {
return;
}
String value = getPipelineClassName(filename, pipelineConfig, "request-pipeline");
if (value != null) {
mpConfig.setRequestPipelineClassName(value);
}
value = getPipelineClassName(filename, pipelineConfig, "response-pipeline");
if (value != null) {
mpConfig.setResponsePipelineClassName(value);
}
value = getPipelineClassName(filename, pipelineConfig, "request-dispatcher");
if (value != null) {
mpConfig.setRequestDispatcherClassName(value);
}
value = getPipelineClassName(filename, pipelineConfig, "response-dispatcher");
if (value != null) {
mpConfig.setResponseDispatcherClassName(value);
}
NodeList loggingHandlers = DomParseUtils.getImmediateChildrenByTagName(pipelineConfig, "logging-handler");
if (loggingHandlers != null && loggingHandlers.getLength() > 0) {
List<FrameworkHandlerConfig> outHandlers = mpConfig.getLoggingHandlers();
for (int i = 0; i < loggingHandlers.getLength(); i++) {
Element loggingHandler = (Element) loggingHandlers.item(i);
String classname = DomParseUtils.getElementText(filename, loggingHandler, "class-name");
OptionList options = DomParseUtils.getOptionList(filename, loggingHandler, "options");
FrameworkHandlerConfig outHandler = new FrameworkHandlerConfig();
outHandler.setClassName(classname);
HashMap<String,String> outOptions = outHandler.getOptions();
DomParseUtils.storeNVListToHashMap(filename, options, outOptions);
outHandlers.add(outHandler);
}
}
PipelineTreeConfig requestHandlerTree = parsePipelineTree(filename, pipelineConfig, "request-handlers");
PipelineTreeConfig mergedTree;
if (requestHandlerTree != null) {
mergedTree = combinePipelines(mpConfig.getConfigFilename(), requestHandlerTree, mpConfig.getRequestPipelineTree(), "request-handlers");
validateTree(filename, "request-handlers", mergedTree);
mpConfig.setRequestPipelineTree(mergedTree);
}
PipelineTreeConfig responseHandlerTree = parsePipelineTree(filename, pipelineConfig, "response-handlers");
if (responseHandlerTree != null) {
mergedTree = combinePipelines(mpConfig.getConfigFilename(), responseHandlerTree, mpConfig.getResponsePipelineTree(), "response-handlers");
validateTree(filename, "response-handlers", mergedTree);
mpConfig.setResponsePipelineTree(mergedTree);
}
}
private static void validateTree(String filename, String handlerSection, PipelineTreeConfig pipelineTree) throws ServiceCreationException {
List elements = pipelineTree.getHandlerOrChain();
for (Object element : elements) {
if (element instanceof HandlerConfig) {
HandlerConfig handler = (HandlerConfig)element;
validateHandler(filename, handlerSection, handler);
} else if (element instanceof ChainConfig) {
ChainConfig chain = (ChainConfig) element;
for (HandlerConfig handler : chain.getHandler()) {
validateHandler(filename, handlerSection, handler);
}
}
}
}
private static void validateHandler(String filename, String handlerSection, HandlerConfig handler) throws ServiceCreationException {
String classname = handler.getClassName();
if (classname == null || classname.length() == 0) {
throwError(filename, "handler '" + handler.getName() + "' is missing classname in the pipeline section: " + handlerSection);
}
}
private static PipelineTreeConfig parsePipelineTree(String filename, Element pipelineConfig, String name) throws ServiceCreationException {
Element handlerTree = (Element) DomParseUtils.getSingleNode(filename, pipelineConfig, name);
if (handlerTree == null) {
return null;
}
PipelineTreeConfig result = new PipelineTreeConfig();
List<Object> outElements = result.getHandlerOrChain();
NodeList inElements = handlerTree.getChildNodes();
for (int i = 0; i < inElements.getLength(); i++) {
Node element = inElements.item(i);
if (element.getNodeName().equals("handler")) {
HandlerConfig handler = mapHandlerConfig(filename, (Element) element);
outElements.add(handler);
} else if (element.getNodeName().equals("chain")) {
ChainConfig chain = mapChainConfig(filename, (Element) element);
outElements.add(chain);
}
}
return result;
}
private static ChainConfig mapChainConfig(String filename, Element inChain) throws ServiceCreationException {
ChainConfig outChain = new ChainConfig();
String value = DomParseUtils.getRequiredAttribute(filename, inChain, "name");
outChain.setName(value);
value = inChain.getAttribute("presence");
outChain.setPresence(mapPresence(filename, value));
List<HandlerConfig> outHandlerList = outChain.getHandler();
NodeList inHandlerList = DomParseUtils.getImmediateChildrenByTagName(inChain, "handler");
for (int i = 0; i < inHandlerList.getLength(); i++) {
Element inHandler = (Element) inHandlerList.item(i);
HandlerConfig outHandler = mapHandlerConfig(filename, inHandler);
outHandlerList.add(outHandler);
}
return outChain;
}
private static HandlerConfig mapHandlerConfig(String filename, Element inHandler) throws ServiceCreationException {
HandlerConfig outHandler = new HandlerConfig();
String value = DomParseUtils.getElementText(filename, inHandler, "class-name");
outHandler.setClassName(value);
value = DomParseUtils.getRequiredAttribute(filename, inHandler, "name");
outHandler.setName(value);
value = inHandler.getAttribute("presence");
outHandler.setPresence(mapPresence(filename, value));
value = inHandler.getAttribute("continue-on-error");
if (value != null && value.length() > 0) {
outHandler.setContinueOnError(Boolean.valueOf(value));
}
value = inHandler.getAttribute("run-on-error");
if (value != null && value.length() > 0) {
outHandler.setRunOnError(Boolean.valueOf(value));
}
OptionList options = DomParseUtils.getOptionList(filename, inHandler, "options");
outHandler.setOptions(options);
return outHandler;
}
private static PresenceConfig mapPresence(String filename, String value) throws ServiceCreationException {
if (value.equals("")) {
return null;
}
try {
return PresenceConfig.fromValue(value);
} catch (IllegalArgumentException e) {
DomParseUtils.throwError(filename, "Invalid handler presence value: " + value);
}
return null;
}
private static String getPipelineClassName(String filename, Element pipelineConfig, String name) throws ServiceCreationException {
Element pipelineClassConfig = (Element) DomParseUtils.getSingleNode(filename, pipelineConfig, name);
if (pipelineClassConfig == null) {
return null;
}
return DomParseUtils.getElementText(filename, pipelineClassConfig, "class-name");
}
private static PipelineTreeConfig combinePipelines(String filename,
PipelineTreeConfig overridePipelineTree,
PipelineTreeConfig basePipelineTree,
String elementName) throws ServiceCreationException {
PipelineTreeConfig outTree = new PipelineTreeConfig();
List<Object> outList = outTree.getHandlerOrChain();
List baseElements = null;
if (basePipelineTree != null) {
baseElements = basePipelineTree.getHandlerOrChain();
}
// If we don't have anything to start with, just return the "override" data as the "combined" tree.
if (baseElements == null) {
outTree = overridePipelineTree;
validateTree(filename, outTree, elementName);
return outTree;
}
// Start with a new list, with copies of handlers and references to chains.
for (Object baseElement : baseElements) {
if (baseElement instanceof HandlerConfig) {
outList.add(ConfigUtils.copyHandler((HandlerConfig) baseElement));
} else if (baseElement instanceof ChainConfig) {
outList.add(baseElement);
}
}
List overrideElements = null;
if (overridePipelineTree != null) {
overrideElements = overridePipelineTree.getHandlerOrChain();
}
if (overrideElements != null) {
for (Object overrideElement : overrideElements) {
if (overrideElement instanceof HandlerConfig) {
replaceHandler(filename, outList, (HandlerConfig)overrideElement);
} else if (overrideElement instanceof ChainConfig) {
replaceChain(filename, outList, (ChainConfig) overrideElement);
}
}
}
return outTree;
}
private static void validateTree(String filename,
PipelineTreeConfig tree,
String elementName) throws ServiceCreationException {
List elements = tree.getHandlerOrChain();
for (Object element : elements) {
if (element instanceof HandlerConfig) {
HandlerConfig handlerConfig = (HandlerConfig) element;
if (isRemoved(handlerConfig.getPresence())) {
throwError(filename, "Pipeline '" + elementName + "'is not overriding anything, so handler '" + handlerConfig.getName() + "' cannot use 'removed' keyword");
}
} else if (element instanceof ChainConfig) {
ChainConfig chainConfig = (ChainConfig) element;
if (isRemoved(chainConfig.getPresence())) {
throwError(filename, "Pipeline '" + elementName + "'is not overriding anything, so chain '" + chainConfig.getName() + "' cannot use 'removed' keyword");
}
}
}
}
private static boolean replaceChain(String filename, List<Object> outList, ChainConfig overrideChain) throws ServiceCreationException {
int ix = findByName(outList, overrideChain.getName());
if (ix < 0) {
throwError(filename, "Can't find chain: " + overrideChain.getName());
return false;
}
Object baseElement = outList.get(ix);
if (! (baseElement instanceof ChainConfig)) {
throwError(filename, "Can't override handler with a chain: " + overrideChain.getName());
}
ChainConfig baseChain = (ChainConfig) baseElement;
if (isMandatory(baseChain.getPresence())) {
throwError(filename, "Can't override mandatory chain: " + overrideChain.getName());
}
// Is this a "remove"?
if (isRemoved(overrideChain.getPresence())) {
outList.remove(ix);
return true;
}
ChainConfig newChain = ConfigUtils.copyChain(overrideChain);
outList.set(ix, newChain);
return true;
}
private static boolean replaceHandler (String filename, List<Object> outList, HandlerConfig overrideHandler) throws ServiceCreationException {
int ix = findByName(outList, overrideHandler.getName());
if (ix < 0) {
throwError(filename, "Handler '" + overrideHandler.getName() + "' is not allowed because it is not specified in the global configuration pipeline");
return false;
}
Object baseElement = outList.get(ix);
if (! (baseElement instanceof HandlerConfig)) {
throwError(filename, "Can't override chain with a handler: " + overrideHandler.getName());
return false;
}
HandlerConfig baseHandler = (HandlerConfig) baseElement;
if (isMandatory(baseHandler.getPresence())) {
throwError(filename, "Can't override mandatory handler: " + overrideHandler.getName());
return false;
}
// Is this a "remove"?
if (isRemoved(overrideHandler.getPresence())) {
outList.remove(ix);
return true;
}
// Replace base handler data with any declared override handler data
ConfigUtils.copyMutableHandlerData(baseHandler, overrideHandler);
return true;
}
private static boolean isMandatory(PresenceConfig p) {
return (p != null && p.equals(PresenceConfig.MANDATORY));
}
private static boolean isRemoved(PresenceConfig p) {
return (p != null && p.equals(PresenceConfig.REMOVED));
}
private static int findByName(List<Object> baseElements, String name) {
for (int ix = 0; ix < baseElements.size(); ix++) {
Object baseElement = baseElements.get(ix);
if (baseElement instanceof HandlerConfig) {
HandlerConfig baseHandler = (HandlerConfig) baseElement;
if (baseHandler.getName().equals(name))
return ix;
} else if (baseElement instanceof ChainConfig) {
ChainConfig baseChain = (ChainConfig) baseElement;
if (baseChain.getName().equals(name))
return ix;
}
}
return -1;
}
// Keeping a list instead of a hashmap because order could be significant in the indicator matching
private static void mapProtocolProcessor(String filename, NodeList protocolProcessors, MessageProcessorConfigHolder dstConfig) throws ServiceCreationException {
if (protocolProcessors == null) {
return;
}
List<ProtocolProcessorConfig> dstPPs = dstConfig.getProtocolProcessors();
for (int i = 0; i < protocolProcessors.getLength(); i++) {
Element inPP = (Element) protocolProcessors.item(i);
ProtocolProcessorConfig outPP = mapProtocolProcessor(filename, inPP);
int ix = findPPByName(dstPPs, outPP.getName());
if (ix < 0) {
// New entry - add it
dstPPs.add(outPP);
} else {
dstPPs.set(ix, outPP);
}
}
}
private static ProtocolProcessorConfig mapProtocolProcessor(String filename, Element inPP) throws ServiceCreationException {
ProtocolProcessorConfig outPP = new ProtocolProcessorConfig();
String name = inPP.getAttribute("name");
outPP.setName(name);
String version = inPP.getAttribute("version");
outPP.setVersion(version);
String classname = DomParseUtils.getElementText(filename, inPP, "class-name");
outPP.setClassName(classname);
Element inIndicator = (Element) DomParseUtils.getSingleNode(filename, inPP, "indicator");
FeatureIndicatorConfig outIndicator = mapIndicator(filename, inIndicator);
outPP.setIndicator(outIndicator);
return outPP;
}
private static FeatureIndicatorConfig mapIndicator(String filename, Element inIndicator) throws ServiceCreationException {
// This is a choice, exactly one will appear in the XML. Modeled as two properties on FeatureIndicatorConfig
// of which one will be null and the other present.
FeatureIndicatorConfig indicator = new FeatureIndicatorConfig();
if (inIndicator == null) {
throwError(filename, "missing element 'indicator'");
}
String urlPattern = DomParseUtils.getElementText(filename, inIndicator, "URL-pattern");
if (urlPattern != null) {
indicator.setURLPattern(urlPattern);
}
Element inNV = (Element) DomParseUtils.getSingleNode(filename, inIndicator, "transport-header");
if (inNV != null) {
NameValue outNV = new NameValue();
String name = inNV.getAttribute("name");
String value = DomParseUtils.getText(inNV);
outNV.setName(name);
outNV.setValue(value);
indicator.setTransportHeader(outNV);
}
if (urlPattern == null && inNV == null) {
throwError(filename, "indicator must contain either URL-pattern or transport-header");
}
return indicator;
}
private static void mapTransports(String filename, NodeList transports, MessageProcessorConfigHolder dstConfig) throws ServiceCreationException {
if (transports == null) {
return;
}
TransportOptions outDefaultOptions = null;
Map<String, String> dstTransportClasses = dstConfig.getTransportClasses();
Map<String, TransportOptions> dstTransportOptions = dstConfig.getTransportOptions();
Map<String, Map<String, String>> dstTransportHeaderOptions = dstConfig.getTransportHeaderOptions();
for (int i = 0; i < transports.getLength(); i++) {
Element transport = (Element) transports.item(i);
String name = transport.getAttribute("name");
String classname = DomParseUtils.getElementText(filename, transport, "class-name");
dstTransportClasses.put(name.toUpperCase(), classname); // converting to uppercase
Element inDefaultOptions = (Element) DomParseUtils.getSingleNode(filename, transport, "default-options");
if (inDefaultOptions != null) {
outDefaultOptions = DomParseUtils.mapTransportOptions(filename, inDefaultOptions);
} else {
outDefaultOptions = new TransportOptions();
}
outDefaultOptions.setHttpTransportClassName(classname); // set the className field with the 'class-name' element value
dstTransportOptions.put(name.toUpperCase(), outDefaultOptions);
Map<String, String> outHeaderOptions = dstTransportHeaderOptions.get(name.toUpperCase());
if (outHeaderOptions == null) {
outHeaderOptions = new HashMap<String, String>();
dstTransportHeaderOptions.put(name.toUpperCase(), outHeaderOptions);
}
OptionList inHeaderOptions = DomParseUtils.getOptionList(filename, transport, "header-options");
if (inHeaderOptions != null) {
DomParseUtils.storeNVListToHashMap(filename, inHeaderOptions, outHeaderOptions);
}
}
}
private static int findPPByName(List<ProtocolProcessorConfig> elements, String name) {
if (elements == null) {
return -1;
}
for (int ix = 0; ix < elements.size(); ix++) {
ProtocolProcessorConfig element = elements.get(ix);
if (element.getName().equals(name))
return ix;
}
return -1;
}
private static void mapDataBindingConfig(String filename, Element dataBindingConfig, MessageProcessorConfigHolder dstConfig) throws ServiceCreationException {
if (dataBindingConfig == null) {
return;
}
Map<String, SerializerConfig> outDataBindings = dstConfig.getDataBindings();
NodeList inDataBindings = DomParseUtils.getImmediateChildrenByTagName(dataBindingConfig, "data-binding");
for (int i = 0; i < inDataBindings.getLength(); i++) {
Element inDataBinding = (Element) inDataBindings.item(i);
SerializerConfig outDataBinding = mapDataBinding(filename, inDataBinding);
outDataBindings.put(outDataBinding.getName(), outDataBinding);
}
}
private static SerializerConfig mapDataBinding(String filename, Element inDataBinding) throws ServiceCreationException {
SerializerConfig outDataBinding = new SerializerConfig();
String name = DomParseUtils.getRequiredAttribute(filename, inDataBinding, "name");
outDataBinding.setName(name);
String mimeType = DomParseUtils.getElementText(filename, inDataBinding, "mime-type", true);
outDataBinding.setMimeType(mimeType);
String serClass = DomParseUtils.getElementText(filename, inDataBinding, "serializer-factory-class-name");
outDataBinding.setSerializerFactoryClassName(serClass);
String deserClass = DomParseUtils.getElementText(filename, inDataBinding, "deserializer-factory-class-name");
outDataBinding.setDeserializerFactoryClassName(deserClass);
OptionList options = DomParseUtils.getOptionList(filename, inDataBinding, "options");
HashMap<String,String> outOptions = outDataBinding.getOptions();
DomParseUtils.storeNVListToHashMap(filename, options, outOptions);
return outDataBinding;
}
// TODO - many of the errors thrown in this way should be individual exceptions so the text can
// be localized better.
private static void throwError(String filename, String cause) throws ServiceCreationException {
throw new ServiceCreationException(ErrorDataFactory.createErrorData(ErrorConstants.CFG_VALIDATION_ERROR,
ErrorConstants.ERRORDOMAIN, new Object[] {filename, cause}));
}
}