/*
* 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.jms.config;
import javax.jms.Session;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractChannelParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.util.StringUtils;
/**
* Parser for the 'channel' and 'publish-subscribe-channel' elements of the
* Spring Integration JMS namespace.
*
* @author Mark Fisher
* @author Oleg Zhurakusky
* @author Gary Russell
* @since 2.0
*/
public class JmsChannelParser extends AbstractChannelParser {
private final static String CONTAINER_TYPE_ATTRIBUTE = "container-type";
private final static String CONTAINER_CLASS_ATTRIBUTE = "container-class";
private final static String ACKNOWLEDGE_ATTRIBUTE = "acknowledge";
@Override
protected BeanDefinitionBuilder buildBeanDefinition(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
JmsChannelFactoryBean.class);
String messageDriven = element.getAttribute("message-driven");
if (StringUtils.hasText(messageDriven)) {
builder.addConstructorArgValue(messageDriven);
}
builder.addPropertyReference(JmsParserUtils.CONNECTION_FACTORY_PROPERTY,
JmsParserUtils.determineConnectionFactoryBeanName(element, parserContext));
if ("channel".equals(element.getLocalName())) {
this.parseDestination(element, parserContext, builder, "queue");
}
else if ("publish-subscribe-channel".equals(element.getLocalName())) {
this.parseDestination(element, parserContext, builder, "topic");
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "max-subscribers");
String containerType = element.getAttribute(CONTAINER_TYPE_ATTRIBUTE);
String containerClass = element.getAttribute(CONTAINER_CLASS_ATTRIBUTE);
if (!StringUtils.hasText(containerClass) && StringUtils.hasText(containerType)) {
if ("default".equals(containerType)) {
containerClass = "org.springframework.jms.listener.DefaultMessageListenerContainer";
}
else if ("simple".equals(containerType)) {
containerClass = "org.springframework.jms.listener.SimpleMessageListenerContainer";
}
}
/*
* Schema docs tell the user to ensure that, if they supply a container-class, it
* is their responsibility to set the container-type appropriately, based on the superclass
* of their implementation (default="default"). If it is set incorrectly, some attributes
* may not be applied.
*
* We cannot reliably infer it here.
*/
if (StringUtils.hasText(containerClass)) {
builder.addPropertyValue("containerType", containerClass);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "receive-timeout");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "task-executor");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "transaction-manager");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converter");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-handler");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "selector", "messageSelector");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "phase");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "delivery-persistent");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "time-to-live");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "priority");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "explicit-qos-enabled");
String cache = element.getAttribute("cache");
if (StringUtils.hasText(cache)) {
if (containerType.startsWith("simple")) {
if (!("auto".equals(cache) || "consumer".equals(cache))) {
parserContext.getReaderContext().warning(
"'cache' attribute not actively supported for listener container of type \"simple\". " +
"Effective runtime behavior will be equivalent to \"consumer\" / \"auto\".", element);
}
}
else {
builder.addPropertyValue("cacheLevelName", "CACHE_" + cache.toUpperCase());
}
}
Integer acknowledgeMode = this.parseAcknowledgeMode(element, parserContext);
if (acknowledgeMode != null) {
if (acknowledgeMode == Session.SESSION_TRANSACTED) {
builder.addPropertyValue("sessionTransacted", Boolean.TRUE);
}
else {
builder.addPropertyValue("sessionAcknowledgeMode", acknowledgeMode);
}
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "concurrency");
String prefetch = element.getAttribute("prefetch");
if (StringUtils.hasText(prefetch)) {
if (containerType.startsWith("default")) {
builder.addPropertyValue("maxMessagesPerTask", Integer.valueOf(prefetch));
}
}
return builder;
}
private void parseDestination(Element element, ParserContext parserContext, BeanDefinitionBuilder builder, String type) {
boolean isPubSub = "topic".equals(type);
String ref = element.getAttribute(type);
String name = element.getAttribute(type + "-name");
boolean isReference = StringUtils.hasText(ref);
boolean isName = StringUtils.hasText(name);
if (!(isReference ^ isName)) {
parserContext.getReaderContext().error("Exactly one of the '" + type +
"' or '" + type + "-name' attributes is required.", element);
}
if (isReference) {
builder.addPropertyReference("destination", ref);
}
else if (isName) {
builder.addPropertyValue("destinationName", name);
builder.addPropertyValue("pubSubDomain", isPubSub);
String destinationResolver = element.getAttribute("destination-resolver");
if (StringUtils.hasText(destinationResolver)) {
builder.addPropertyReference("destinationResolver", destinationResolver);
}
}
if (isPubSub) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "durable", "subscriptionDurable");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "subscription", "durableSubscriptionName");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "subscription-shared");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "client-id");
}
}
private Integer parseAcknowledgeMode(Element ele, ParserContext parserContext) {
String acknowledge = ele.getAttribute(ACKNOWLEDGE_ATTRIBUTE);
if (StringUtils.hasText(acknowledge)) {
int acknowledgeMode = Session.AUTO_ACKNOWLEDGE;
if ("transacted".equals(acknowledge)) {
acknowledgeMode = Session.SESSION_TRANSACTED;
}
else if ("dups-ok".equals(acknowledge)) {
acknowledgeMode = Session.DUPS_OK_ACKNOWLEDGE;
}
else if ("client".equals(acknowledge)) {
acknowledgeMode = Session.CLIENT_ACKNOWLEDGE;
}
else if (!"auto".equals(acknowledge)) {
parserContext.getReaderContext().error("Invalid JMS Channel 'acknowledge' setting [" +
acknowledge + "]: only \"auto\", \"client\", \"dups-ok\" and \"transacted\" supported.", ele);
}
return acknowledgeMode;
}
else {
return null;
}
}
}