/* * 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.jms.config; import org.w3c.dom.Element; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.config.BeanDefinition; 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.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.jms.ChannelPublishingJmsMessageListener; import org.springframework.integration.jms.JmsMessageDrivenEndpoint; import org.springframework.jms.listener.DefaultMessageListenerContainer; import org.springframework.util.StringUtils; /** * Parser for the <message-driven-channel-adapter> element and the * <inbound-gateway> element of the 'jms' namespace. * * @author Mark Fisher * @author Michael Bannister * @author Gary Russell * @author Artem Bilan */ public class JmsMessageDrivenEndpointParser extends AbstractSingleBeanDefinitionParser { private static final String DEFAULT_REPLY_DESTINATION_ATTRIB = "default-reply-destination"; private static final String DEFAULT_REPLY_QUEUE_NAME_ATTRIB = "default-reply-queue-name"; private static final String DEFAULT_REPLY_TOPIC_NAME_ATTRIB = "default-reply-topic-name"; private static final String REPLY_TIME_TO_LIVE = "reply-time-to-live"; private static final String REPLY_PRIORITY = "reply-priority"; private static final String REPLY_DELIVERY_PERSISTENT = "reply-delivery-persistent"; private static final String EXPLICIT_QOS_ENABLED_FOR_REPLIES = "explicit-qos-enabled-for-replies"; private static String[] containerAttributes = new String[] { JmsParserUtils.CONNECTION_FACTORY_PROPERTY, JmsParserUtils.DESTINATION_ATTRIBUTE, JmsParserUtils.DESTINATION_NAME_ATTRIBUTE, "destination-resolver", "transaction-manager", "concurrent-consumers", "max-concurrent-consumers", "acknowledge", "max-messages-per-task", "selector", "receive-timeout", "recovery-interval", "idle-consumer-limit", "idle-task-execution-limit", "cache-level", "subscription-durable", "subscription-shared", "subscription-name", "client-id", "task-executor" }; private final boolean expectReply; public JmsMessageDrivenEndpointParser(boolean expectReply) { this.expectReply = expectReply; } @Override protected String getBeanClassName(Element element) { return JmsMessageDrivenEndpoint.class.getName(); } @Override protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException { String id = super.resolveId(element, definition, parserContext); if (!this.expectReply && !element.hasAttribute("channel")) { // the created channel will get the 'id', so the adapter's bean name includes a suffix id = id + ".adapter"; } if (!StringUtils.hasText(id)) { id = BeanDefinitionReaderUtils.generateBeanName(definition, parserContext.getRegistry()); } return id; } @Override protected boolean shouldGenerateIdAsFallback() { return true; } @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { String containerBeanName = this.parseMessageListenerContainer(element, parserContext, builder.getRawBeanDefinition()); String listenerBeanName = this.parseMessageListener(element, parserContext, builder.getRawBeanDefinition()); builder.addConstructorArgReference(containerBeanName); builder.addConstructorArgReference(listenerBeanName); builder.addConstructorArgValue(hasExternalContainer(element)); 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); } IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "acknowledge", "sessionAcknowledgeMode"); } private String parseMessageListenerContainer(Element element, ParserContext parserContext, BeanDefinition adapterBeanDefinition) { String containerClass = element.getAttribute("container-class"); if (hasExternalContainer(element)) { if (StringUtils.hasText(containerClass)) { parserContext.getReaderContext().error("Cannot have both 'container' and 'container-class'", element); } for (String containerAttribute : containerAttributes) { if (element.hasAttribute(containerAttribute)) { parserContext.getReaderContext().error("The '" + containerAttribute + "' attribute should not be provided when specifying a 'container' reference.", element); } } return element.getAttribute("container"); } // otherwise, we build a DefaultMessageListenerContainer instance BeanDefinitionBuilder builder; if (StringUtils.hasText(containerClass)) { builder = BeanDefinitionBuilder.genericBeanDefinition(containerClass); } else { builder = BeanDefinitionBuilder.genericBeanDefinition(DefaultMessageListenerContainer.class); } String destinationAttribute = this.expectReply ? "request-destination" : "destination"; String destinationNameAttribute = this.expectReply ? "request-destination-name" : "destination-name"; String pubSubDomainAttribute = this.expectReply ? "request-pub-sub-domain" : "pub-sub-domain"; String destination = element.getAttribute(destinationAttribute); String destinationName = element.getAttribute(destinationNameAttribute); boolean hasDestination = StringUtils.hasText(destination); boolean hasDestinationName = StringUtils.hasText(destinationName); if (hasDestination == hasDestinationName) { parserContext.getReaderContext().error( "Exactly one of '" + destinationAttribute + "' or '" + destinationNameAttribute + "' is required.", element); } builder.addPropertyReference(JmsParserUtils.CONNECTION_FACTORY_PROPERTY, JmsParserUtils.determineConnectionFactoryBeanName(element, parserContext)); if (hasDestination) { builder.addPropertyReference("destination", destination); } else { builder.addPropertyValue("destinationName", destinationName); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, pubSubDomainAttribute, "pubSubDomain"); } IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "destination-resolver"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "transaction-manager"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "task-executor"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "selector", "messageSelector"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "concurrent-consumers"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "max-concurrent-consumers"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "max-messages-per-task"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "receive-timeout"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "recovery-interval"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "idle-consumer-limit"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "idle-task-execution-limit"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "cache-level"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "subscription-durable"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "subscription-shared"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "subscription-name"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "client-id"); String beanName = adapterBeanNameRoot(element, parserContext, adapterBeanDefinition) + ".container"; parserContext.getRegistry().registerBeanDefinition(beanName, builder.getBeanDefinition()); return beanName; } private boolean hasExternalContainer(Element element) { return element.hasAttribute("container"); } private String parseMessageListener(Element element, ParserContext parserContext, BeanDefinition adapterBeanDefinition) { BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(ChannelPublishingJmsMessageListener.class); builder.addPropertyValue("expectReply", this.expectReply); if (this.expectReply) { IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "request-channel"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-timeout"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-request-payload"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-reply-payload"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "correlation-key"); int defaults = 0; if (StringUtils.hasText(element.getAttribute(DEFAULT_REPLY_DESTINATION_ATTRIB))) { defaults++; } if (StringUtils.hasText(element.getAttribute(DEFAULT_REPLY_QUEUE_NAME_ATTRIB))) { defaults++; } if (StringUtils.hasText(element.getAttribute(DEFAULT_REPLY_TOPIC_NAME_ATTRIB))) { defaults++; } if (defaults > 1) { parserContext.getReaderContext().error("At most one of '" + DEFAULT_REPLY_DESTINATION_ATTRIB + "', '" + DEFAULT_REPLY_QUEUE_NAME_ATTRIB + "', or '" + DEFAULT_REPLY_TOPIC_NAME_ATTRIB + "' may be provided.", element); } IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, DEFAULT_REPLY_DESTINATION_ATTRIB); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, DEFAULT_REPLY_QUEUE_NAME_ATTRIB); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, DEFAULT_REPLY_TOPIC_NAME_ATTRIB); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "destination-resolver"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, REPLY_TIME_TO_LIVE); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, REPLY_PRIORITY); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, REPLY_DELIVERY_PERSISTENT); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, EXPLICIT_QOS_ENABLED_FOR_REPLIES); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel"); } else { String channelName = element.getAttribute("channel"); if (!StringUtils.hasText(channelName)) { channelName = IntegrationNamespaceUtils.createDirectChannel(element, parserContext); } builder.addPropertyReference("requestChannel", channelName); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout", "requestTimeout"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-payload", "extractRequestPayload"); } IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converter"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "header-mapper"); String alias = adapterBeanNameRoot(element, parserContext, adapterBeanDefinition) + ".listener"; BeanDefinition beanDefinition = builder.getBeanDefinition(); String beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, parserContext.getRegistry()); BeanComponentDefinition component = new BeanComponentDefinition(beanDefinition, beanName, new String[] { alias }); parserContext.registerBeanComponent(component); return beanName; } private String adapterBeanNameRoot(Element element, ParserContext parserContext, BeanDefinition adapterBeanDefinition) { String beanName = element.getAttribute(ID_ATTRIBUTE); if (!StringUtils.hasText(beanName)) { beanName = BeanDefinitionReaderUtils.generateBeanName(adapterBeanDefinition, parserContext.getRegistry()); } return beanName; } }