Skip to content

Instantly share code, notes, and snippets.

@nlwillia
Last active December 17, 2015 11:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nlwillia/5602964 to your computer and use it in GitHub Desktop.
Save nlwillia/5602964 to your computer and use it in GitHub Desktop.
https://jira.springsource.org/browse/INT-3023 This is an example of how the Chain namespace parser could be extended to support embedding an inbound-channel-adapter at the top of the chain and inferring a direct channel between the adapter and the chain. The details are in the added parseInboundChannelAdapters method.
/*
* Copyright 2002-2012 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.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
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.ParserContext;
import org.springframework.integration.handler.MessageHandlerChain;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* 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 {
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MessageHandlerChain.class);
parseInboundChannelAdapters(element, builder, parserContext);
ManagedList<BeanMetadataElement> handlerList = new ManagedList<BeanMetadataElement>();
NodeList children = element.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE && !"poller".equals(child.getLocalName())) {
BeanDefinitionHolder holder = this.parseChild((Element) child, parserContext, builder.getBeanDefinition());
if ("gateway".equals(child.getLocalName())){
BeanDefinitionBuilder gwBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".gateway.RequestReplyMessageHandlerAdapter");
gwBuilder.addConstructorArgValue(holder);
handlerList.add(gwBuilder.getBeanDefinition());
}
else {
handlerList.add(holder);
}
}
}
builder.addPropertyValue("handlers", handlerList);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "phase");
return builder;
}
/** Parses, connects and removes any inbound-channel-adapter elements declared at the top of the chain. */
private void parseInboundChannelAdapters(Element chainElement, BeanDefinitionBuilder builder, ParserContext parserContext) {
/*
* If an inbound-channel-adapter appears inside a chain as the first element, the implication is that the adapter is the starting point
* for the processing sequence grouped by the chain. A channel between the adapter and the chain is still required, but if the adapter's
* "channel" attribute and the chain's "input-channel" attribute are omitted, the intent of a direct channel can be inferred and the
* link created automatically. This eliminates the need for the developer to maintain a throwaway channel name just to connect the adapter
* to the chain.
*
* This implementation traverses the DOM to detect, establish and enforce this relationship. After completion, an anonymous channel name
* will be configured and the adapter elements will be removed from the chain Element so that chain processing can continue normally.
*/
NodeList children = chainElement.getChildNodes();
boolean hasNonAdapter = false; // Have we found something other than an inbound-adapter in the node list yet?
String channelId = null; // anonymous channel between an inbound adapter and the chain
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
Element childElement = (Element) child;
// We don't attempt to handle bean-style definitions because there is no common base class or marker interface for inbound adapters.
if (!childElement.getLocalName().endsWith("inbound-channel-adapter")) {
hasNonAdapter = true; // don't allow any more inbound adapters
} else if (hasNonAdapter) {
parserContext.getReaderContext().error(IntegrationNamespaceUtils.createElementDescription(childElement) + " must occur at the beginning of the chain.", parserContext.extractSource(childElement));
} else {
// The inbound adapter should not declare a channel.
String adapterChannelId = childElement.getAttribute("channel");
if (StringUtils.hasText(adapterChannelId)) {
parserContext.getReaderContext().error(IntegrationNamespaceUtils.createElementDescription(childElement) + " should not declare channel '" + adapterChannelId + "' when used inside a chain.", parserContext.extractSource(childElement));
}
// The first inbound adapter found initializes an anonymous channel to connect the chain.
if (channelId == null) {
// The chain should not declare an input-channel either.
String chainChannelId = chainElement.getAttribute(getInputChannelAttributeName());
if (StringUtils.hasText(chainChannelId)) {
parserContext.getReaderContext().error(IntegrationNamespaceUtils.createElementDescription(chainElement) + " should not declare " + getInputChannelAttributeName() + " '" + chainChannelId + "' when it contains an inbound-channel-adapter.", parserContext.extractSource(chainElement));
}
// Copied from AbstractConsumerEndpointParser#parseInternal
if (parserContext.getRegistry().containsBeanDefinition(ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME)){
BeanDefinition channelRegistry = parserContext.getRegistry().getBeanDefinition(ChannelInitializer.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();
// Need to come up with a unique name.
String chainId = resolveDeclaredId(chainElement);
if (chainId != null) {
channelId = chainId + "$anonymousInputChannel";
} else {
int counter = 0;
do {
channelId = "anonymousChain" + counter++ + "$anonymousInputChannel"; // remote risk that this could obscure a later explicit channel name
} while (channelCandidateNames.contains(channelId));
}
channelCandidateNames.add(channelId); // Automatic channel creation
} else {
parserContext.getReaderContext().error("Failed to locate '" + ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME + "'", parserContext.getRegistry());
}
chainElement.setAttribute(getInputChannelAttributeName(), channelId); // Chain will need this to validate.
}
childElement.setAttribute("channel", channelId); // Adapter will need this to validate.
// Parse and validate the adapter like any other child.
BeanDefinitionHolder adapterBeanHolder = this.parseChild(childElement, parserContext, builder.getBeanDefinition());
String adapterBeanName = resolveDeclaredId(childElement);
if (adapterBeanName == null) {
adapterBeanName = adapterBeanHolder.getBeanName();
}
// But, an inbound adapter is not a MessageHandler, so we'll need to register it separately...
parserContext.getRegistry().registerBeanDefinition(adapterBeanName, adapterBeanHolder.getBeanDefinition());
// ...and remove it so the regular chain parsing doesn't try to append it.
chainElement.removeChild(childElement);
i--;
}
}
}
private String resolveDeclaredId(Element element) {
String id = element.getAttribute(ID_ATTRIBUTE);
if (!StringUtils.hasText(id)) {
id = element.getAttribute(NAME_ATTRIBUTE);
if (!StringUtils.hasText(id)) {
id = null;
}
}
return id;
}
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);
}
}
private BeanDefinitionHolder parseChild(Element element, ParserContext parserContext, BeanDefinition parentDefinition) {
BeanDefinitionHolder holder = null;
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);
}
else {
String beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, parserContext.getRegistry(), true);
holder = new BeanDefinitionHolder(beanDefinition, beanName);
}
}
return holder;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment