Last active
December 17, 2015 11:29
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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