/* * Copyright 2002-2008 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.jms.listener.endpoint; import java.util.Properties; import javax.jms.JMSException; import javax.jms.Queue; import javax.jms.Session; import javax.jms.Topic; import javax.resource.spi.ActivationSpec; import javax.resource.spi.ResourceAdapter; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapper; import org.springframework.beans.PropertyAccessorFactory; import org.springframework.jms.support.destination.DestinationResolutionException; import org.springframework.jms.support.destination.DestinationResolver; /** * Standard implementation of the {@link JmsActivationSpecFactory} interface. * Supports the standard JMS properties as defined by the JMS 1.5 specification * (Appendix B); ignores Spring's "maxConcurrency" and "prefetchSize" settings. * * <p>The 'activationSpecClass' property is required, explicitly defining * the fully-qualified class name of the provider's ActivationSpec class * (e.g. "org.apache.activemq.ra.ActiveMQActivationSpec"). * * <p>Check out {@link DefaultJmsActivationSpecFactory} for an extended variant * of this class, supporting some further default conventions beyond the plain * JMS 1.5 specification. * * @author Juergen Hoeller * @since 2.5 * @see #setActivationSpecClass * @see DefaultJmsActivationSpecFactory */ public class StandardJmsActivationSpecFactory implements JmsActivationSpecFactory { private Class activationSpecClass; private Properties defaultProperties; private DestinationResolver destinationResolver; /** * Specify the fully-qualified ActivationSpec class name for the target * provider (e.g. "org.apache.activemq.ra.ActiveMQActivationSpec"). */ public void setActivationSpecClass(Class activationSpecClass) { this.activationSpecClass = activationSpecClass; } /** * Specify custom default properties, with String keys and String values. * <p>Applied to each ActivationSpec object before it gets populated with * listener-specific settings. Allows for configuring vendor-specific properties * beyond the Spring-defined settings in {@link JmsActivationSpecConfig}. */ public void setDefaultProperties(Properties defaultProperties) { this.defaultProperties = defaultProperties; } /** * Set the DestinationResolver to use for resolving destination names * into the JCA 1.5 ActivationSpec "destination" property. * <p>If not specified, destination names will simply be passed in as Strings. * If specified, destination names will be resolved into Destination objects first. * <p>Note that a DestinationResolver for use with this factory must be * able to work <i>without</i> an active JMS Session: e.g. * {@link org.springframework.jms.support.destination.JndiDestinationResolver} * or {@link org.springframework.jms.support.destination.BeanFactoryDestinationResolver} * but not {@link org.springframework.jms.support.destination.DynamicDestinationResolver}. */ public void setDestinationResolver(DestinationResolver destinationResolver) { this.destinationResolver = destinationResolver; } public ActivationSpec createActivationSpec(ResourceAdapter adapter, JmsActivationSpecConfig config) { Class activationSpecClassToUse = this.activationSpecClass; if (activationSpecClassToUse == null) { activationSpecClassToUse = determineActivationSpecClass(adapter); if (activationSpecClassToUse == null) { throw new IllegalStateException("Property 'activationSpecClass' is required"); } } ActivationSpec spec = (ActivationSpec) BeanUtils.instantiateClass(activationSpecClassToUse); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(spec); if (this.defaultProperties != null) { bw.setPropertyValues(this.defaultProperties); } populateActivationSpecProperties(bw, config); return spec; } /** * Determine the ActivationSpec class for the given ResourceAdapter, * if possible. Called if no 'activationSpecClass' has been set explicitly * @param adapter the ResourceAdapter to check * @return the corresponding ActivationSpec class, or <code>null</code> * if not determinable * @see #setActivationSpecClass */ protected Class determineActivationSpecClass(ResourceAdapter adapter) { return null; } /** * Populate the given ApplicationSpec object with the settings * defined in the given configuration object. * <p>This implementation applies all standard JMS settings, but ignores * "maxConcurrency" and "prefetchSize" - not supported in standard JCA 1.5. * @param bw the BeanWrapper wrapping the ActivationSpec object * @param config the configured object holding common JMS settings */ protected void populateActivationSpecProperties(BeanWrapper bw, JmsActivationSpecConfig config) { String destinationName = config.getDestinationName(); boolean pubSubDomain = config.isPubSubDomain(); Object destination = destinationName; if (this.destinationResolver != null) { try { destination = this.destinationResolver.resolveDestinationName(null, destinationName, pubSubDomain); } catch (JMSException ex) { throw new DestinationResolutionException("Cannot resolve destination name [" + destinationName + "]", ex); } } bw.setPropertyValue("destination", destination); bw.setPropertyValue("destinationType", pubSubDomain ? Topic.class.getName() : Queue.class.getName()); if (bw.isWritableProperty("subscriptionDurability")) { bw.setPropertyValue("subscriptionDurability", config.isSubscriptionDurable() ? "Durable" : "NonDurable"); } else if (config.isSubscriptionDurable()) { // Standard JCA 1.5 "subscriptionDurability" apparently not supported... throw new IllegalArgumentException( "Durable subscriptions not supported by underlying provider: " + this.activationSpecClass.getName()); } if (config.getDurableSubscriptionName() != null) { bw.setPropertyValue("subscriptionName", config.getDurableSubscriptionName()); } if (config.getClientId() != null) { bw.setPropertyValue("clientId", config.getClientId()); } if (config.getMessageSelector() != null) { bw.setPropertyValue("messageSelector", config.getMessageSelector()); } applyAcknowledgeMode(bw, config.getAcknowledgeMode()); } /** * Apply the specified acknowledge mode to the ActivationSpec object. * <p>This implementation applies the standard JCA 1.5 acknowledge modes * "Auto-acknowledge" and "Dups-ok-acknowledge". It throws an exception in * case of <code>CLIENT_ACKNOWLEDGE</code> or <code>SESSION_TRANSACTED</code> * having been requested. * @param bw the BeanWrapper wrapping the ActivationSpec object * @param ackMode the configured acknowledge mode * (according to the constants in {@link javax.jms.Session} * @see javax.jms.Session#AUTO_ACKNOWLEDGE * @see javax.jms.Session#DUPS_OK_ACKNOWLEDGE * @see javax.jms.Session#CLIENT_ACKNOWLEDGE * @see javax.jms.Session#SESSION_TRANSACTED */ protected void applyAcknowledgeMode(BeanWrapper bw, int ackMode) { if (ackMode == Session.SESSION_TRANSACTED) { throw new IllegalArgumentException("No support for SESSION_TRANSACTED: Only \"Auto-acknowledge\" " + "and \"Dups-ok-acknowledge\" supported in standard JCA 1.5"); } else if (ackMode == Session.CLIENT_ACKNOWLEDGE) { throw new IllegalArgumentException("No support for CLIENT_ACKNOWLEDGE: Only \"Auto-acknowledge\" " + "and \"Dups-ok-acknowledge\" supported in standard JCA 1.5"); } else if (bw.isWritableProperty("acknowledgeMode")) { bw.setPropertyValue("acknowledgeMode", ackMode == Session.DUPS_OK_ACKNOWLEDGE ? "Dups-ok-acknowledge" : "Auto-acknowledge"); } else if (ackMode == Session.DUPS_OK_ACKNOWLEDGE) { // Standard JCA 1.5 "acknowledgeMode" apparently not supported (e.g. WebSphere MQ 6.0.2.1) throw new IllegalArgumentException( "Dups-ok-acknowledge not supported by underlying provider: " + this.activationSpecClass.getName()); } } }