001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jms.listener.endpoint;
018
019import java.util.Map;
020import javax.jms.JMSException;
021import javax.jms.Queue;
022import javax.jms.Session;
023import javax.jms.Topic;
024import javax.resource.spi.ActivationSpec;
025import javax.resource.spi.ResourceAdapter;
026
027import org.springframework.beans.BeanUtils;
028import org.springframework.beans.BeanWrapper;
029import org.springframework.beans.PropertyAccessorFactory;
030import org.springframework.jms.support.destination.DestinationResolutionException;
031import org.springframework.jms.support.destination.DestinationResolver;
032
033/**
034 * Standard implementation of the {@link JmsActivationSpecFactory} interface.
035 * Supports the standard JMS properties as defined by the JMS 1.5 specification
036 * (Appendix B); ignores Spring's "maxConcurrency" and "prefetchSize" settings.
037 *
038 * <p>The 'activationSpecClass' property is required, explicitly defining
039 * the fully-qualified class name of the provider's ActivationSpec class
040 * (e.g. "org.apache.activemq.ra.ActiveMQActivationSpec").
041 *
042 * <p>Check out {@link DefaultJmsActivationSpecFactory} for an extended variant
043 * of this class, supporting some further default conventions beyond the plain
044 * JMS 1.5 specification.
045 *
046 * @author Juergen Hoeller
047 * @since 2.5
048 * @see #setActivationSpecClass
049 * @see DefaultJmsActivationSpecFactory
050 */
051public class StandardJmsActivationSpecFactory implements JmsActivationSpecFactory {
052
053        private Class<?> activationSpecClass;
054
055        private Map<String, String> defaultProperties;
056
057        private DestinationResolver destinationResolver;
058
059
060        /**
061         * Specify the fully-qualified ActivationSpec class name for the target
062         * provider (e.g. "org.apache.activemq.ra.ActiveMQActivationSpec").
063         */
064        public void setActivationSpecClass(Class<?> activationSpecClass) {
065                this.activationSpecClass = activationSpecClass;
066        }
067
068        /**
069         * Specify custom default properties, with String keys and String values.
070         * <p>Applied to each ActivationSpec object before it gets populated with
071         * listener-specific settings. Allows for configuring vendor-specific properties
072         * beyond the Spring-defined settings in {@link JmsActivationSpecConfig}.
073         */
074        public void setDefaultProperties(Map<String, String> defaultProperties) {
075                this.defaultProperties = defaultProperties;
076        }
077
078        /**
079         * Set the DestinationResolver to use for resolving destination names
080         * into the JCA 1.5 ActivationSpec "destination" property.
081         * <p>If not specified, destination names will simply be passed in as Strings.
082         * If specified, destination names will be resolved into Destination objects first.
083         * <p>Note that a DestinationResolver for use with this factory must be
084         * able to work <i>without</i> an active JMS Session: e.g.
085         * {@link org.springframework.jms.support.destination.JndiDestinationResolver}
086         * or {@link org.springframework.jms.support.destination.BeanFactoryDestinationResolver}
087         * but not {@link org.springframework.jms.support.destination.DynamicDestinationResolver}.
088         */
089        public void setDestinationResolver(DestinationResolver destinationResolver) {
090                this.destinationResolver = destinationResolver;
091        }
092
093        /**
094         * Return the {@link DestinationResolver} to use for resolving destinations names.
095         */
096        public DestinationResolver getDestinationResolver() {
097                return this.destinationResolver;
098        }
099
100        @Override
101        public ActivationSpec createActivationSpec(ResourceAdapter adapter, JmsActivationSpecConfig config) {
102                Class<?> activationSpecClassToUse = this.activationSpecClass;
103                if (activationSpecClassToUse == null) {
104                        activationSpecClassToUse = determineActivationSpecClass(adapter);
105                        if (activationSpecClassToUse == null) {
106                                throw new IllegalStateException("Property 'activationSpecClass' is required");
107                        }
108                }
109
110                ActivationSpec spec = (ActivationSpec) BeanUtils.instantiateClass(activationSpecClassToUse);
111                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(spec);
112                if (this.defaultProperties != null) {
113                        bw.setPropertyValues(this.defaultProperties);
114                }
115                populateActivationSpecProperties(bw, config);
116                return spec;
117        }
118
119        /**
120         * Determine the ActivationSpec class for the given ResourceAdapter,
121         * if possible. Called if no 'activationSpecClass' has been set explicitly
122         * @param adapter the ResourceAdapter to check
123         * @return the corresponding ActivationSpec class, or {@code null}
124         * if not determinable
125         * @see #setActivationSpecClass
126         */
127        protected Class<?> determineActivationSpecClass(ResourceAdapter adapter) {
128                return null;
129        }
130
131        /**
132         * Populate the given ApplicationSpec object with the settings
133         * defined in the given configuration object.
134         * <p>This implementation applies all standard JMS settings, but ignores
135         * "maxConcurrency" and "prefetchSize" - not supported in standard JCA 1.5.
136         * @param bw the BeanWrapper wrapping the ActivationSpec object
137         * @param config the configured object holding common JMS settings
138         */
139        protected void populateActivationSpecProperties(BeanWrapper bw, JmsActivationSpecConfig config) {
140                String destinationName = config.getDestinationName();
141                boolean pubSubDomain = config.isPubSubDomain();
142                Object destination = destinationName;
143                if (this.destinationResolver != null) {
144                        try {
145                                destination = this.destinationResolver.resolveDestinationName(null, destinationName, pubSubDomain);
146                        }
147                        catch (JMSException ex) {
148                                throw new DestinationResolutionException("Cannot resolve destination name [" + destinationName + "]", ex);
149                        }
150                }
151                bw.setPropertyValue("destination", destination);
152                bw.setPropertyValue("destinationType", pubSubDomain ? Topic.class.getName() : Queue.class.getName());
153
154                if (bw.isWritableProperty("subscriptionDurability")) {
155                        bw.setPropertyValue("subscriptionDurability", config.isSubscriptionDurable() ? "Durable" : "NonDurable");
156                }
157                else if (config.isSubscriptionDurable()) {
158                        // Standard JCA 1.5 "subscriptionDurability" apparently not supported...
159                        throw new IllegalArgumentException(
160                                        "Durable subscriptions not supported by underlying provider: " + this.activationSpecClass.getName());
161                }
162                if (config.isSubscriptionShared()) {
163                        throw new IllegalArgumentException("Shared subscriptions not supported for JCA-driven endpoints");
164                }
165
166                if (config.getSubscriptionName() != null) {
167                        bw.setPropertyValue("subscriptionName", config.getSubscriptionName());
168                }
169                if (config.getClientId() != null) {
170                        bw.setPropertyValue("clientId", config.getClientId());
171                }
172                if (config.getMessageSelector() != null) {
173                        bw.setPropertyValue("messageSelector", config.getMessageSelector());
174                }
175                applyAcknowledgeMode(bw, config.getAcknowledgeMode());
176        }
177
178        /**
179         * Apply the specified acknowledge mode to the ActivationSpec object.
180         * <p>This implementation applies the standard JCA 1.5 acknowledge modes
181         * "Auto-acknowledge" and "Dups-ok-acknowledge". It throws an exception in
182         * case of {@code CLIENT_ACKNOWLEDGE} or {@code SESSION_TRANSACTED}
183         * having been requested.
184         * @param bw the BeanWrapper wrapping the ActivationSpec object
185         * @param ackMode the configured acknowledge mode
186         * (according to the constants in {@link javax.jms.Session}
187         * @see javax.jms.Session#AUTO_ACKNOWLEDGE
188         * @see javax.jms.Session#DUPS_OK_ACKNOWLEDGE
189         * @see javax.jms.Session#CLIENT_ACKNOWLEDGE
190         * @see javax.jms.Session#SESSION_TRANSACTED
191         */
192        protected void applyAcknowledgeMode(BeanWrapper bw, int ackMode) {
193                if (ackMode == Session.SESSION_TRANSACTED) {
194                        throw new IllegalArgumentException("No support for SESSION_TRANSACTED: Only \"Auto-acknowledge\" " +
195                                        "and \"Dups-ok-acknowledge\" supported in standard JCA 1.5");
196                }
197                else if (ackMode == Session.CLIENT_ACKNOWLEDGE) {
198                        throw new IllegalArgumentException("No support for CLIENT_ACKNOWLEDGE: Only \"Auto-acknowledge\" " +
199                                        "and \"Dups-ok-acknowledge\" supported in standard JCA 1.5");
200                }
201                else if (bw.isWritableProperty("acknowledgeMode")) {
202                        bw.setPropertyValue("acknowledgeMode",
203                                        ackMode == Session.DUPS_OK_ACKNOWLEDGE ? "Dups-ok-acknowledge" : "Auto-acknowledge");
204                }
205                else if (ackMode == Session.DUPS_OK_ACKNOWLEDGE) {
206                        // Standard JCA 1.5 "acknowledgeMode" apparently not supported (e.g. WebSphere MQ 6.0.2.1)
207                        throw new IllegalArgumentException(
208                                        "Dups-ok-acknowledge not supported by underlying provider: " + this.activationSpecClass.getName());
209                }
210        }
211
212}