/*
* Copyright 2002-2016 the original author or authors.
*
* 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.springframework.integration.config.xml;
import java.util.Arrays;
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.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.expression.DynamicExpression;
import org.springframework.integration.transformer.HeaderEnricher;
import org.springframework.integration.transformer.support.ExpressionEvaluatingHeaderValueMessageProcessor;
import org.springframework.integration.transformer.support.MessageProcessingHeaderValueMessageProcessor;
import org.springframework.integration.transformer.support.RoutingSlipHeaderValueMessageProcessor;
import org.springframework.integration.transformer.support.StaticHeaderValueMessageProcessor;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* Base support class for 'header-enricher' parsers.
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
* @author Gary Russell
* @since 2.0
*/
public abstract class HeaderEnricherParserSupport extends AbstractTransformerParser {
private final Map<String, String> elementToNameMap = new HashMap<String, String>();
private final Map<String, Class<?>> elementToTypeMap = new HashMap<String, Class<?>>();
private final static Map<String, String[][]> cannedHeaderElementExpressions = new HashMap<String, String[][]>();
static {
cannedHeaderElementExpressions.put("header-channels-to-string", new String[][] {
{"replyChannel", "@" + IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME
+ ".channelToChannelName(headers.replyChannel, ####)" },
{"errorChannel", "@" + IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME
+ ".channelToChannelName(headers.errorChannel, ####)" },
});
}
@Override
protected final String getTransformerClassName() {
return HeaderEnricher.class.getName();
}
protected final void addElementToHeaderMapping(String elementName, String headerName) {
this.addElementToHeaderMapping(elementName, headerName, null);
}
protected final void addElementToHeaderMapping(String elementName, String headerName, Class<?> headerType) {
this.elementToNameMap.put(elementName, headerName);
if (headerType != null) {
this.elementToTypeMap.put(elementName, headerType);
}
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
protected void parseTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
ManagedMap headers = new ManagedMap();
this.processHeaders(element, headers, parserContext);
builder.addConstructorArgValue(headers);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "default-overwrite");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "should-skip-nulls");
this.postProcessHeaderEnricher(builder, element, parserContext);
}
protected void processHeaders(Element element, ManagedMap<String, Object> headers, ParserContext parserContext) {
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
String headerName = null;
Element headerElement = (Element) node;
String elementName = node.getLocalName();
Class<?> headerType = null;
String expression = null;
String overwrite = headerElement.getAttribute("overwrite");
if ("header".equals(elementName)) {
headerName = headerElement.getAttribute(NAME_ATTRIBUTE);
}
else {
headerName = this.elementToNameMap.get(elementName);
headerType = this.elementToTypeMap.get(elementName);
if (headerType != null && StringUtils.hasText(headerElement.getAttribute("type"))) {
parserContext.getReaderContext().error("The " + elementName
+ " header does not accept a 'type' attribute. The required type is ["
+ headerType.getName() + "]", element);
}
}
if (headerType == null) {
String headerTypeName = headerElement.getAttribute("type");
if (StringUtils.hasText(headerTypeName)) {
ClassLoader classLoader = parserContext.getReaderContext().getBeanClassLoader();
if (classLoader == null) {
classLoader = getClass().getClassLoader();
}
try {
headerType = ClassUtils.forName(headerTypeName, classLoader);
}
catch (Exception e) {
parserContext.getReaderContext().error("unable to resolve type [" +
headerTypeName + "] for header '" + headerName + "'", element, e);
}
}
}
if (headerName == null) {
String ttlExpression = headerElement.getAttribute("time-to-live-expression");
if (cannedHeaderElementExpressions.containsKey(elementName)) {
for (int j = 0; j < cannedHeaderElementExpressions.get(elementName).length; j++) {
headerName = cannedHeaderElementExpressions.get(elementName)[j][0];
expression = cannedHeaderElementExpressions.get(elementName)[j][1];
if (StringUtils.hasText(ttlExpression)) {
expression = expression.replace("####", ttlExpression);
}
else {
expression = expression.replace(", ####", "");
}
overwrite = "true";
this.addHeader(element, headers, parserContext, headerName, headerElement, headerType,
expression, overwrite);
}
}
}
else {
this.addHeader(element, headers, parserContext, headerName, headerElement, headerType, null,
overwrite);
}
}
}
}
private void addHeader(Element element, ManagedMap<String, Object> headers, ParserContext parserContext,
String headerName, Element headerElement, Class<?> headerType, String expression, String overwrite) {
String value = headerElement.getAttribute("value");
String ref = headerElement.getAttribute(REF_ATTRIBUTE);
String method = headerElement.getAttribute(METHOD_ATTRIBUTE);
if (expression == null) {
expression = headerElement.getAttribute(EXPRESSION_ATTRIBUTE);
}
Element beanElement = null;
Element scriptElement = null;
Element expressionElement = null;
List<Element> subElements = DomUtils.getChildElements(headerElement);
if (!subElements.isEmpty()) {
Element subElement = subElements.get(0);
String subElementLocalName = subElement.getLocalName();
if ("bean".equals(subElementLocalName)) {
beanElement = subElement;
}
else if ("script".equals(subElementLocalName)) {
scriptElement = subElement;
}
else if ("expression".equals(subElementLocalName)) {
expressionElement = subElement;
}
if (beanElement == null && scriptElement == null && expressionElement == null) {
parserContext.getReaderContext()
.error("Only 'bean', 'script' or 'expression' can be defined as a sub-element", element);
}
}
if (StringUtils.hasText(expression) && expressionElement != null) {
parserContext.getReaderContext()
.error("The 'expression' attribute and sub-element are mutually exclusive", element);
}
boolean isValue = StringUtils.hasText(value);
boolean isRef = StringUtils.hasText(ref);
boolean hasMethod = StringUtils.hasText(method);
boolean isExpression = StringUtils.hasText(expression) || expressionElement != null;
boolean isScript = scriptElement != null;
BeanDefinition innerComponentDefinition = null;
if (beanElement != null) {
innerComponentDefinition = parserContext.getDelegate()
.parseBeanDefinitionElement(beanElement)
.getBeanDefinition();
}
else if (isScript) {
innerComponentDefinition = parserContext.getDelegate().parseCustomElement(scriptElement);
}
boolean isCustomBean = innerComponentDefinition != null;
if (hasMethod && isScript) {
parserContext.getReaderContext()
.error("The 'method' attribute cannot be used when a 'script' sub-element is defined", element);
}
if (isValue == (isRef ^ (isExpression ^ isCustomBean))) {
parserContext.getReaderContext().error(
"Exactly one of the 'ref', 'value', 'expression' or inner bean is required.", element);
}
BeanDefinitionBuilder valueProcessorBuilder = null;
if (isValue) {
if (hasMethod) {
parserContext.getReaderContext().error(
"The 'method' attribute cannot be used with the 'value' attribute.", element);
}
if (IntegrationMessageHeaderAccessor.ROUTING_SLIP.equals(headerName)) {
List<String> routingSlipPath = new ManagedList<String>();
routingSlipPath.addAll(Arrays.asList(StringUtils.tokenizeToStringArray(value, ";")));
valueProcessorBuilder =
BeanDefinitionBuilder.genericBeanDefinition(RoutingSlipHeaderValueMessageProcessor.class)
.addConstructorArgValue(routingSlipPath);
}
else {
Object headerValue = (headerType != null) ?
new TypedStringValue(value, headerType) : value;
valueProcessorBuilder =
BeanDefinitionBuilder.genericBeanDefinition(StaticHeaderValueMessageProcessor.class)
.addConstructorArgValue(headerValue);
}
}
else if (isExpression) {
if (hasMethod) {
parserContext.getReaderContext().error(
"The 'method' attribute cannot be used with the 'expression' attribute.", element);
}
valueProcessorBuilder =
BeanDefinitionBuilder.genericBeanDefinition(ExpressionEvaluatingHeaderValueMessageProcessor.class);
if (expressionElement != null) {
BeanDefinitionBuilder dynamicExpressionBuilder =
BeanDefinitionBuilder.genericBeanDefinition(DynamicExpression.class);
dynamicExpressionBuilder.addConstructorArgValue(expressionElement.getAttribute("key"));
dynamicExpressionBuilder.addConstructorArgReference(expressionElement.getAttribute("source"));
valueProcessorBuilder.addConstructorArgValue(dynamicExpressionBuilder.getBeanDefinition());
}
else {
valueProcessorBuilder.addConstructorArgValue(expression);
}
valueProcessorBuilder.addConstructorArgValue(headerType);
}
else if (isCustomBean) {
if (StringUtils.hasText(headerElement.getAttribute("type"))) {
parserContext.getReaderContext().error(
"The 'type' attribute cannot be used with an inner bean.", element);
}
if (hasMethod || isScript) {
valueProcessorBuilder =
BeanDefinitionBuilder.genericBeanDefinition(MessageProcessingHeaderValueMessageProcessor.class)
.addConstructorArgValue(innerComponentDefinition);
if (hasMethod) {
valueProcessorBuilder.addConstructorArgValue(method);
}
}
else {
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(StaticHeaderValueMessageProcessor.class);
valueProcessorBuilder.addConstructorArgValue(innerComponentDefinition);
}
}
else {
if (StringUtils.hasText(headerElement.getAttribute("type"))) {
parserContext.getReaderContext().error(
"The 'type' attribute cannot be used with the 'ref' attribute.", element);
}
if (hasMethod) {
valueProcessorBuilder =
BeanDefinitionBuilder.genericBeanDefinition(MessageProcessingHeaderValueMessageProcessor.class)
.addConstructorArgReference(ref)
.addConstructorArgValue(method);
}
else {
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(StaticHeaderValueMessageProcessor.class)
.addConstructorArgReference(ref);
}
}
if (StringUtils.hasText(overwrite)) {
valueProcessorBuilder.addPropertyValue("overwrite", overwrite);
}
headers.put(headerName, valueProcessorBuilder.getBeanDefinition());
}
/**
* Subclasses may override this method to provide any additional processing.
* @param builder The builder.
* @param element The element.
* @param parserContext The parser context.
*/
protected void postProcessHeaderEnricher(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
}
}