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