001/*
002 * Copyright 2002-2018 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.jca.endpoint;
018
019import java.lang.reflect.Method;
020import javax.resource.ResourceException;
021import javax.resource.spi.ApplicationServerInternalException;
022import javax.resource.spi.UnavailableException;
023import javax.resource.spi.endpoint.MessageEndpoint;
024import javax.resource.spi.endpoint.MessageEndpointFactory;
025import javax.transaction.Transaction;
026import javax.transaction.TransactionManager;
027import javax.transaction.xa.XAResource;
028
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031
032import org.springframework.beans.factory.BeanNameAware;
033import org.springframework.transaction.jta.SimpleTransactionFactory;
034import org.springframework.transaction.jta.TransactionFactory;
035
036/**
037 * Abstract base implementation of the JCA 1.5/1.6/1.7
038 * {@link javax.resource.spi.endpoint.MessageEndpointFactory} interface,
039 * providing transaction management capabilities as well as ClassLoader
040 * exposure for endpoint invocations.
041 *
042 * @author Juergen Hoeller
043 * @since 2.5
044 * @see #setTransactionManager
045 */
046public abstract class AbstractMessageEndpointFactory implements MessageEndpointFactory, BeanNameAware {
047
048        /** Logger available to subclasses */
049        protected final Log logger = LogFactory.getLog(getClass());
050
051        private TransactionFactory transactionFactory;
052
053        private String transactionName;
054
055        private int transactionTimeout = -1;
056
057        private String beanName;
058
059
060        /**
061         * Set the XA transaction manager to use for wrapping endpoint
062         * invocations, enlisting the endpoint resource in each such transaction.
063         * <p>The passed-in object may be a transaction manager which implements
064         * Spring's {@link org.springframework.transaction.jta.TransactionFactory}
065         * interface, or a plain {@link javax.transaction.TransactionManager}.
066         * <p>If no transaction manager is specified, the endpoint invocation
067         * will simply not be wrapped in an XA transaction. Check out your
068         * resource provider's ActivationSpec documentation for local
069         * transaction options of your particular provider.
070         * @see #setTransactionName
071         * @see #setTransactionTimeout
072         */
073        public void setTransactionManager(Object transactionManager) {
074                if (transactionManager instanceof TransactionFactory) {
075                        this.transactionFactory = (TransactionFactory) transactionManager;
076                }
077                else if (transactionManager instanceof TransactionManager) {
078                        this.transactionFactory = new SimpleTransactionFactory((TransactionManager) transactionManager);
079                }
080                else {
081                        throw new IllegalArgumentException("Transaction manager [" + transactionManager +
082                                        "] is neither a [org.springframework.transaction.jta.TransactionFactory} nor a " +
083                                        "[javax.transaction.TransactionManager]");
084                }
085        }
086
087        /**
088         * Set the Spring TransactionFactory to use for wrapping endpoint
089         * invocations, enlisting the endpoint resource in each such transaction.
090         * <p>Alternatively, specify an appropriate transaction manager through
091         * the {@link #setTransactionManager "transactionManager"} property.
092         * <p>If no transaction factory is specified, the endpoint invocation
093         * will simply not be wrapped in an XA transaction. Check out your
094         * resource provider's ActivationSpec documentation for local
095         * transaction options of your particular provider.
096         * @see #setTransactionName
097         * @see #setTransactionTimeout
098         */
099        public void setTransactionFactory(TransactionFactory transactionFactory) {
100                this.transactionFactory = transactionFactory;
101        }
102
103        /**
104         * Specify the name of the transaction, if any.
105         * <p>Default is none. A specified name will be passed on to the transaction
106         * manager, allowing to identify the transaction in a transaction monitor.
107         */
108        public void setTransactionName(String transactionName) {
109                this.transactionName = transactionName;
110        }
111
112        /**
113         * Specify the transaction timeout, if any.
114         * <p>Default is -1: rely on the transaction manager's default timeout.
115         * Specify a concrete timeout to restrict the maximum duration of each
116         * endpoint invocation.
117         */
118        public void setTransactionTimeout(int transactionTimeout) {
119                this.transactionTimeout = transactionTimeout;
120        }
121
122        /**
123         * Set the name of this message endpoint. Populated with the bean name
124         * automatically when defined within Spring's bean factory.
125         */
126        @Override
127        public void setBeanName(String beanName) {
128                this.beanName = beanName;
129        }
130
131
132        /**
133         * Implementation of the JCA 1.7 {@code #getActivationName()} method,
134         * returning the bean name as set on this MessageEndpointFactory.
135         * @see #setBeanName
136         */
137        public String getActivationName() {
138                return this.beanName;
139        }
140
141        /**
142         * This implementation returns {@code true} if a transaction manager
143         * has been specified; {@code false} otherwise.
144         * @see #setTransactionManager
145         * @see #setTransactionFactory
146         */
147        @Override
148        public boolean isDeliveryTransacted(Method method) throws NoSuchMethodException {
149                return (this.transactionFactory != null);
150        }
151
152        /**
153         * The standard JCA 1.5 version of {@code createEndpoint}.
154         * <p>This implementation delegates to {@link #createEndpointInternal()},
155         * initializing the endpoint's XAResource before the endpoint gets invoked.
156         */
157        @Override
158        public MessageEndpoint createEndpoint(XAResource xaResource) throws UnavailableException {
159                AbstractMessageEndpoint endpoint = createEndpointInternal();
160                endpoint.initXAResource(xaResource);
161                return endpoint;
162        }
163
164        /**
165         * The alternative JCA 1.6 version of {@code createEndpoint}.
166         * <p>This implementation delegates to {@link #createEndpointInternal()},
167         * ignoring the specified timeout. It is only here for JCA 1.6 compliance.
168         */
169        public MessageEndpoint createEndpoint(XAResource xaResource, long timeout) throws UnavailableException {
170                AbstractMessageEndpoint endpoint = createEndpointInternal();
171                endpoint.initXAResource(xaResource);
172                return endpoint;
173        }
174
175        /**
176         * Create the actual endpoint instance, as a subclass of the
177         * {@link AbstractMessageEndpoint} inner class of this factory.
178         * @return the actual endpoint instance (never {@code null})
179         * @throws UnavailableException if no endpoint is available at present
180         */
181        protected abstract AbstractMessageEndpoint createEndpointInternal() throws UnavailableException;
182
183
184        /**
185         * Inner class for actual endpoint implementations, based on template
186         * method to allow for any kind of concrete endpoint implementation.
187         */
188        protected abstract class AbstractMessageEndpoint implements MessageEndpoint {
189
190                private TransactionDelegate transactionDelegate;
191
192                private boolean beforeDeliveryCalled = false;
193
194                private ClassLoader previousContextClassLoader;
195
196                /**
197                 * Initialize this endpoint's TransactionDelegate.
198                 * @param xaResource the XAResource for this endpoint
199                 */
200                void initXAResource(XAResource xaResource) {
201                        this.transactionDelegate = new TransactionDelegate(xaResource);
202                }
203
204                /**
205                 * This {@code beforeDelivery} implementation starts a transaction,
206                 * if necessary, and exposes the endpoint ClassLoader as current
207                 * thread context ClassLoader.
208                 * <p>Note that the JCA 1.5 specification does not require a ResourceAdapter
209                 * to call this method before invoking the concrete endpoint. If this method
210                 * has not been called (check {@link #hasBeforeDeliveryBeenCalled()}), the
211                 * concrete endpoint method should call {@code beforeDelivery} and its
212                 * sibling {@link #afterDelivery()} explicitly, as part of its own processing.
213                 */
214                @Override
215                public void beforeDelivery(Method method) throws ResourceException {
216                        this.beforeDeliveryCalled = true;
217                        try {
218                                this.transactionDelegate.beginTransaction();
219                        }
220                        catch (Throwable ex) {
221                                throw new ApplicationServerInternalException("Failed to begin transaction", ex);
222                        }
223                        Thread currentThread = Thread.currentThread();
224                        this.previousContextClassLoader = currentThread.getContextClassLoader();
225                        currentThread.setContextClassLoader(getEndpointClassLoader());
226                }
227
228                /**
229                 * Template method for exposing the endpoint's ClassLoader
230                 * (typically the ClassLoader that the message listener class
231                 * has been loaded with).
232                 * @return the endpoint ClassLoader (never {@code null})
233                 */
234                protected abstract ClassLoader getEndpointClassLoader();
235
236                /**
237                 * Return whether the {@link #beforeDelivery} method of this endpoint
238                 * has already been called.
239                 */
240                protected final boolean hasBeforeDeliveryBeenCalled() {
241                        return this.beforeDeliveryCalled;
242                }
243
244                /**
245                 * Callback method for notifying the endpoint base class
246                 * that the concrete endpoint invocation led to an exception.
247                 * <p>To be invoked by subclasses in case of the concrete
248                 * endpoint throwing an exception.
249                 * @param ex the exception thrown from the concrete endpoint
250                 */
251                protected final void onEndpointException(Throwable ex) {
252                        this.transactionDelegate.setRollbackOnly();
253                        logger.debug("Transaction marked as rollback-only after endpoint exception", ex);
254                }
255
256                /**
257                 * This {@code afterDelivery} implementation resets the thread context
258                 * ClassLoader and completes the transaction, if any.
259                 * <p>Note that the JCA 1.5 specification does not require a ResourceAdapter
260                 * to call this method after invoking the concrete endpoint. See the
261                 * explanation in {@link #beforeDelivery}'s javadoc.
262                 */
263                @Override
264                public void afterDelivery() throws ResourceException {
265                        this.beforeDeliveryCalled = false;
266                        Thread.currentThread().setContextClassLoader(this.previousContextClassLoader);
267                        this.previousContextClassLoader = null;
268                        try {
269                                this.transactionDelegate.endTransaction();
270                        }
271                        catch (Throwable ex) {
272                                logger.warn("Failed to complete transaction after endpoint delivery", ex);
273                                throw new ApplicationServerInternalException("Failed to complete transaction", ex);
274                        }
275                }
276
277                @Override
278                public void release() {
279                        try {
280                                this.transactionDelegate.setRollbackOnly();
281                                this.transactionDelegate.endTransaction();
282                        }
283                        catch (Throwable ex) {
284                                logger.warn("Could not complete unfinished transaction on endpoint release", ex);
285                        }
286                }
287        }
288
289
290        /**
291         * Private inner class that performs the actual transaction handling,
292         * including enlistment of the endpoint's XAResource.
293         */
294        private class TransactionDelegate {
295
296                private final XAResource xaResource;
297
298                private Transaction transaction;
299
300                private boolean rollbackOnly;
301
302                public TransactionDelegate(XAResource xaResource) {
303                        if (xaResource == null && transactionFactory != null &&
304                                        !transactionFactory.supportsResourceAdapterManagedTransactions()) {
305                                throw new IllegalStateException("ResourceAdapter-provided XAResource is required for " +
306                                                "transaction management. Check your ResourceAdapter's configuration.");
307                        }
308                        this.xaResource = xaResource;
309                }
310
311                public void beginTransaction() throws Exception {
312                        if (transactionFactory != null && this.xaResource != null) {
313                                this.transaction = transactionFactory.createTransaction(transactionName, transactionTimeout);
314                                this.transaction.enlistResource(this.xaResource);
315                        }
316                }
317
318                public void setRollbackOnly() {
319                        if (this.transaction != null) {
320                                this.rollbackOnly = true;
321                        }
322                }
323
324                public void endTransaction() throws Exception {
325                        if (this.transaction != null) {
326                                try {
327                                        if (this.rollbackOnly) {
328                                                this.transaction.rollback();
329                                        }
330                                        else {
331                                                this.transaction.commit();
332                                        }
333                                }
334                                finally {
335                                        this.transaction = null;
336                                        this.rollbackOnly = false;
337                                }
338                        }
339                }
340        }
341
342}