/*
* 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.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.IntegrationConfigUtils;
import org.springframework.integration.handler.MessageHandlerChain;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* Parser for the <chain> element.
*
* @author Mark Fisher
* @author Iwein Fuld
* @author Oleg Zhurakousky
* @author Artem Bilan
* @author Gunnar Hillert
*/
public class ChainParser extends AbstractConsumerEndpointParser {
/**
* {@link BeanDefinition} attribute used to pass down the current bean id for nested chains, allowing full
* qualification of 'named' handlers within nested chains.
*
*/
private static final String SI_CHAIN_NESTED_ID_ATTRIBUTE = "SI.ChainParser.NestedId.Prefix";
private final Log logger = LogFactory.getLog(this.getClass());
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MessageHandlerChain.class);
if (!StringUtils.hasText(element.getAttribute(ID_ATTRIBUTE))) {
this.logger.info("It is useful to provide an explicit 'id' attribute on 'chain' elements " +
"to simplify the identification of child elements in logs etc.");
}
String chainHandlerId = this.resolveId(element, builder.getRawBeanDefinition(), parserContext);
List<BeanMetadataElement> handlerList = new ManagedList<BeanMetadataElement>();
Set<String> handlerBeanNameSet = new HashSet<String>();
NodeList children = element.getChildNodes();
int childOrder = 0;
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE && !"poller".equals(child.getLocalName())) {
BeanMetadataElement childBeanMetadata = this.parseChild(chainHandlerId, (Element) child, childOrder++,
parserContext, builder.getBeanDefinition());
if (childBeanMetadata instanceof RuntimeBeanReference) {
String handlerBeanName = ((RuntimeBeanReference) childBeanMetadata).getBeanName();
if (!handlerBeanNameSet.add(handlerBeanName)) {
parserContext.getReaderContext().error("A bean definition is already registered for " +
"beanName: '" + handlerBeanName + "' within the current <chain>.",
element);
return null;
}
}
if ("gateway".equals(child.getLocalName())) {
BeanDefinitionBuilder gwBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationConfigUtils.BASE_PACKAGE + ".gateway.RequestReplyMessageHandlerAdapter");
gwBuilder.addConstructorArgValue(childBeanMetadata);
handlerList.add(gwBuilder.getBeanDefinition());
}
else {
handlerList.add(childBeanMetadata);
}
}
}
builder.addPropertyValue("handlers", handlerList);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout");
return builder;
}
@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException {
String id = super.resolveId(element, definition, parserContext);
BeanDefinition containingBeanDefinition = parserContext.getContainingBeanDefinition();
if (containingBeanDefinition != null) {
String nestedChainIdPrefix = (String) containingBeanDefinition.getAttribute(SI_CHAIN_NESTED_ID_ATTRIBUTE);
if (StringUtils.hasText(nestedChainIdPrefix)) {
id = nestedChainIdPrefix + "$child." + id;
}
}
definition.setAttribute(SI_CHAIN_NESTED_ID_ATTRIBUTE, id);
return id;
}
private BeanMetadataElement parseChild(String chainHandlerId, Element element, int order, ParserContext parserContext,
BeanDefinition parentDefinition) {
BeanDefinitionHolder holder = null;
String id = element.getAttribute(ID_ATTRIBUTE);
boolean hasId = StringUtils.hasText(id);
String handlerComponentName = chainHandlerId + "$child" + (hasId ? "." + id : "#" + order);
if ("bean".equals(element.getLocalName())) {
holder = parserContext.getDelegate().parseBeanDefinitionElement(element, parentDefinition);
}
else {
this.validateChild(element, parserContext);
BeanDefinition beanDefinition = parserContext.getDelegate().parseCustomElement(element, parentDefinition);
if (beanDefinition == null) {
parserContext.getReaderContext().error("child BeanDefinition must not be null", element);
return null;
}
else {
holder = new BeanDefinitionHolder(beanDefinition, handlerComponentName + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX);
}
}
holder.getBeanDefinition().getPropertyValues().add("componentName", handlerComponentName);
if (hasId) {
BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry());
return new RuntimeBeanReference(holder.getBeanName());
}
return holder;
}
private void validateChild(Element element, ParserContext parserContext) {
final Object source = parserContext.extractSource(element);
final String order = element.getAttribute(IntegrationNamespaceUtils.ORDER);
if (StringUtils.hasText(order)) {
parserContext.getReaderContext().error(IntegrationNamespaceUtils.createElementDescription(element) + " must not define " +
"an 'order' attribute when used within a chain.", source);
}
final List<Element> pollerChildElements = DomUtils.getChildElementsByTagName(element, "poller");
if (!pollerChildElements.isEmpty()) {
parserContext.getReaderContext().error(IntegrationNamespaceUtils.createElementDescription(element) + " must not define " +
"a 'poller' sub-element when used within a chain.", source);
}
}
}