/*
* Copyright 2002-2015 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.Collection;
import java.util.List;
import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
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.support.ManagedSet;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.ConsumerEndpointFactoryBean;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* Base class parser for elements that create Message Endpoints.
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
*/
public abstract class AbstractConsumerEndpointParser extends AbstractBeanDefinitionParser {
protected static final String REF_ATTRIBUTE = "ref";
protected static final String METHOD_ATTRIBUTE = "method";
protected static final String EXPRESSION_ATTRIBUTE = "expression";
@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext)
throws BeanDefinitionStoreException {
String id = element.getAttribute(ID_ATTRIBUTE);
if (!StringUtils.hasText(id)) {
id = element.getAttribute("name");
}
if (!StringUtils.hasText(id)) {
id = BeanDefinitionReaderUtils.generateBeanName(definition, parserContext.getRegistry(), parserContext.isNested());
}
return id;
}
/**
* Parse the MessageHandler.
*
* @param element The element.
* @param parserContext The parser context.
* @return The bean definition builder.
*/
protected abstract BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext);
protected String getInputChannelAttributeName() {
return "input-channel";
}
@Override
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder handlerBuilder = this.parseHandler(element, parserContext);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(handlerBuilder, element, "output-channel");
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "order");
Element txElement = DomUtils.getChildElementByTagName(element, "transactional");
Element adviceChainElement = DomUtils.getChildElementByTagName(element,
IntegrationNamespaceUtils.REQUEST_HANDLER_ADVICE_CHAIN);
@SuppressWarnings("rawtypes")
ManagedList adviceChain = IntegrationNamespaceUtils.configureAdviceChain(adviceChainElement, txElement, true,
handlerBuilder.getRawBeanDefinition(), parserContext);
if (!CollectionUtils.isEmpty(adviceChain)) {
handlerBuilder.addPropertyValue("adviceChain", adviceChain);
}
AbstractBeanDefinition handlerBeanDefinition = handlerBuilder.getBeanDefinition();
String inputChannelAttributeName = this.getInputChannelAttributeName();
boolean hasInputChannelAttribute = element.hasAttribute(inputChannelAttributeName);
if (parserContext.isNested()) {
String elementDescription = IntegrationNamespaceUtils.createElementDescription(element);
if (hasInputChannelAttribute) {
parserContext.getReaderContext().error("The '" + inputChannelAttributeName
+ "' attribute isn't allowed for a nested (e.g. inside a <chain/>) endpoint element: "
+ elementDescription + ".", element);
}
if (!replyChannelInChainAllowed(element)) {
if (StringUtils.hasText(element.getAttribute("reply-channel"))) {
parserContext.getReaderContext().error("The 'reply-channel' attribute isn't"
+ " allowed for a nested (e.g. inside a <chain/>) outbound gateway element: "
+ elementDescription + ".", element);
}
}
return handlerBeanDefinition;
}
else {
if (!hasInputChannelAttribute) {
String elementDescription = IntegrationNamespaceUtils.createElementDescription(element);
parserContext.getReaderContext().error("The '" + inputChannelAttributeName
+ "' attribute is required for the top-level endpoint element: "
+ elementDescription + ".", element);
}
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class);
if (!CollectionUtils.isEmpty(adviceChain)) {
builder.addPropertyValue("adviceChain", adviceChain);
}
String handlerBeanName = BeanDefinitionReaderUtils.generateBeanName(handlerBeanDefinition, parserContext.getRegistry());
String[] handlerAlias = IntegrationNamespaceUtils.generateAlias(element);
parserContext.registerBeanComponent(new BeanComponentDefinition(handlerBeanDefinition, handlerBeanName, handlerAlias));
builder.addPropertyReference("handler", handlerBeanName);
String inputChannelName = element.getAttribute(inputChannelAttributeName);
if (!parserContext.getRegistry().containsBeanDefinition(inputChannelName)) {
if (parserContext.getRegistry().containsBeanDefinition(IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME)) {
BeanDefinition channelRegistry = parserContext.getRegistry().
getBeanDefinition(IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME);
ConstructorArgumentValues caValues = channelRegistry.getConstructorArgumentValues();
ValueHolder vh = caValues.getArgumentValue(0, Collection.class);
if (vh == null) { //although it should never happen if it does we can fix it
caValues.addIndexedArgumentValue(0, new ManagedSet<String>());
}
@SuppressWarnings("unchecked")
Collection<String> channelCandidateNames = (Collection<String>) caValues.getArgumentValue(0, Collection.class).getValue();
channelCandidateNames.add(inputChannelName);
}
else {
parserContext.getReaderContext().error("Failed to locate '" +
IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME + "'", parserContext.getRegistry());
}
}
IntegrationNamespaceUtils.checkAndConfigureFixedSubscriberChannel(element, parserContext, inputChannelName,
handlerBeanName);
builder.addPropertyValue("inputChannelName", inputChannelName);
List<Element> pollerElementList = DomUtils.getChildElementsByTagName(element, "poller");
if (!CollectionUtils.isEmpty(pollerElementList)) {
if (pollerElementList.size() != 1) {
parserContext.getReaderContext().error(
"at most one poller element may be configured for an endpoint", element);
}
IntegrationNamespaceUtils.configurePollerMetadata(pollerElementList.get(0), builder, parserContext);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.AUTO_STARTUP);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.PHASE);
String role = element.getAttribute(IntegrationNamespaceUtils.ROLE);
if (StringUtils.hasText(role)) {
if (!StringUtils.hasText(element.getAttribute(ID_ATTRIBUTE))) {
parserContext.getReaderContext().error("When using 'role', 'id' is required", element);
}
IntegrationNamespaceUtils.putLifecycleInRole(role, element.getAttribute(ID_ATTRIBUTE), parserContext);
}
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
String beanName = this.resolveId(element, beanDefinition, parserContext);
parserContext.registerBeanComponent(new BeanComponentDefinition(beanDefinition, beanName));
return null;
}
/**
* Override to allow 'reply-channel' within a chain, for components where it
* makes sense (e.g. enricher). Default is false for outbound gateways, else true.
* @param element the element.
* @return true to allow a reply channel attribute within a chain.
* @since 4.3
*/
protected boolean replyChannelInChainAllowed(Element element) {
String localName = element.getLocalName();
return localName == null || !localName.contains("outbound-gateway");
}
}