001/*
002 * Copyright 2002-2019 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 javax.jms.Session;
020import javax.resource.spi.ResourceAdapter;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024
025import org.springframework.beans.BeanWrapper;
026
027/**
028 * Default implementation of the {@link JmsActivationSpecFactory} interface.
029 * Supports the standard JMS properties as defined by the JCA 1.5 specification,
030 * as well as Spring's extended "maxConcurrency" and "prefetchSize" settings
031 * through autodetection of well-known vendor-specific provider properties.
032 *
033 * <p>An ActivationSpec factory is effectively dependent on the concrete
034 * JMS provider, e.g. on ActiveMQ. This default implementation simply
035 * guesses the ActivationSpec class name from the provider's class name
036 * ("ActiveMQResourceAdapter" -> "ActiveMQActivationSpec" in the same package,
037 * or "ActivationSpecImpl" in the same package as the ResourceAdapter class),
038 * and populates the ActivationSpec properties as suggested by the
039 * JCA 1.5 specification (Appendix B). Specify the 'activationSpecClass'
040 * property explicitly if these default naming rules do not apply.
041 *
042 * <p>Note: ActiveMQ, JORAM and WebSphere are supported in terms of extended
043 * settings (through the detection of their bean property naming conventions).
044 * The default ActivationSpec class detection rules may apply to other
045 * JMS providers as well.
046 *
047 * <p>Thanks to Agim Emruli and Laurie Chan for pointing out WebSphere MQ
048 * settings and contributing corresponding tests!
049 *
050 * @author Juergen Hoeller
051 * @since 2.5
052 * @see #setActivationSpecClass
053 */
054public class DefaultJmsActivationSpecFactory extends StandardJmsActivationSpecFactory {
055
056        private static final String RESOURCE_ADAPTER_SUFFIX = "ResourceAdapter";
057
058        private static final String RESOURCE_ADAPTER_IMPL_SUFFIX = "ResourceAdapterImpl";
059
060        private static final String ACTIVATION_SPEC_SUFFIX = "ActivationSpec";
061
062        private static final String ACTIVATION_SPEC_IMPL_SUFFIX = "ActivationSpecImpl";
063
064
065        /** Logger available to subclasses */
066        protected final Log logger = LogFactory.getLog(getClass());
067
068
069        /**
070         * This implementation guesses the ActivationSpec class name from the
071         * provider's class name: e.g. "ActiveMQResourceAdapter" ->
072         * "ActiveMQActivationSpec" in the same package, or a class named
073         * "ActivationSpecImpl" in the same package as the ResourceAdapter class.
074         */
075        @Override
076        protected Class<?> determineActivationSpecClass(ResourceAdapter adapter) {
077                String adapterClassName = adapter.getClass().getName();
078
079                if (adapterClassName.endsWith(RESOURCE_ADAPTER_SUFFIX)) {
080                        // e.g. ActiveMQ
081                        String providerName =
082                                        adapterClassName.substring(0, adapterClassName.length() - RESOURCE_ADAPTER_SUFFIX.length());
083                        String specClassName = providerName + ACTIVATION_SPEC_SUFFIX;
084                        try {
085                                return adapter.getClass().getClassLoader().loadClass(specClassName);
086                        }
087                        catch (ClassNotFoundException ex) {
088                                if (logger.isDebugEnabled()) {
089                                        logger.debug("No default <Provider>ActivationSpec class found: " + specClassName);
090                                }
091                        }
092                }
093
094                else if (adapterClassName.endsWith(RESOURCE_ADAPTER_IMPL_SUFFIX)){
095                        //e.g. WebSphere
096                        String providerName =
097                                        adapterClassName.substring(0, adapterClassName.length() - RESOURCE_ADAPTER_IMPL_SUFFIX.length());
098                        String specClassName = providerName + ACTIVATION_SPEC_IMPL_SUFFIX;
099                        try {
100                                return adapter.getClass().getClassLoader().loadClass(specClassName);
101                        }
102                        catch (ClassNotFoundException ex) {
103                                if (logger.isDebugEnabled()) {
104                                        logger.debug("No default <Provider>ActivationSpecImpl class found: " + specClassName);
105                                }
106                        }
107                }
108
109                // e.g. JORAM
110                String providerPackage = adapterClassName.substring(0, adapterClassName.lastIndexOf('.') + 1);
111                String specClassName = providerPackage + ACTIVATION_SPEC_IMPL_SUFFIX;
112                try {
113                        return adapter.getClass().getClassLoader().loadClass(specClassName);
114                }
115                catch (ClassNotFoundException ex) {
116                        if (logger.isDebugEnabled()) {
117                                logger.debug("No default ActivationSpecImpl class found in provider package: " + specClassName);
118                        }
119                }
120
121                // ActivationSpecImpl class in "inbound" subpackage (WebSphere MQ 6.0.2.1)
122                specClassName = providerPackage + "inbound." + ACTIVATION_SPEC_IMPL_SUFFIX;
123                try {
124                        return adapter.getClass().getClassLoader().loadClass(specClassName);
125                }
126                catch (ClassNotFoundException ex) {
127                        if (logger.isDebugEnabled()) {
128                                logger.debug("No default ActivationSpecImpl class found in inbound subpackage: " + specClassName);
129                        }
130                }
131
132                throw new IllegalStateException("No ActivationSpec class defined - " +
133                                "specify the 'activationSpecClass' property or override the 'determineActivationSpecClass' method");
134        }
135
136        /**
137         * This implementation supports Spring's extended "maxConcurrency"
138         * and "prefetchSize" settings through detecting corresponding
139         * ActivationSpec properties: "maxSessions"/"maxNumberOfWorks" and
140         * "maxMessagesPerSessions"/"maxMessages", respectively
141         * (following ActiveMQ's and JORAM's naming conventions).
142         */
143        @Override
144        protected void populateActivationSpecProperties(BeanWrapper bw, JmsActivationSpecConfig config) {
145                super.populateActivationSpecProperties(bw, config);
146                if (config.getMaxConcurrency() > 0) {
147                        if (bw.isWritableProperty("maxSessions")) {
148                                // ActiveMQ
149                                bw.setPropertyValue("maxSessions", Integer.toString(config.getMaxConcurrency()));
150                        }
151                        else if (bw.isWritableProperty("maxNumberOfWorks")) {
152                                // JORAM
153                                bw.setPropertyValue("maxNumberOfWorks", Integer.toString(config.getMaxConcurrency()));
154                        }
155                        else if (bw.isWritableProperty("maxConcurrency")){
156                                // WebSphere
157                                bw.setPropertyValue("maxConcurrency", Integer.toString(config.getMaxConcurrency()));
158                        }
159                }
160                if (config.getPrefetchSize() > 0) {
161                        if (bw.isWritableProperty("maxMessagesPerSessions")) {
162                                // ActiveMQ
163                                bw.setPropertyValue("maxMessagesPerSessions", Integer.toString(config.getPrefetchSize()));
164                        }
165                        else if (bw.isWritableProperty("maxMessages")) {
166                                // JORAM
167                                bw.setPropertyValue("maxMessages", Integer.toString(config.getPrefetchSize()));
168                        }
169                        else if (bw.isWritableProperty("maxBatchSize")){
170                                // WebSphere
171                                bw.setPropertyValue("maxBatchSize", Integer.toString(config.getPrefetchSize()));
172                        }
173                }
174        }
175
176        /**
177         * This implementation maps {@code SESSION_TRANSACTED} onto an
178         * ActivationSpec property named "useRAManagedTransaction", if available
179         * (following ActiveMQ's naming conventions).
180         */
181        @Override
182        protected void applyAcknowledgeMode(BeanWrapper bw, int ackMode) {
183                if (ackMode == Session.SESSION_TRANSACTED && bw.isWritableProperty("useRAManagedTransaction")) {
184                        // ActiveMQ
185                        bw.setPropertyValue("useRAManagedTransaction", "true");
186                }
187                else {
188                        super.applyAcknowledgeMode(bw, ackMode);
189                }
190        }
191
192}