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 javax.resource.ResourceException;
020import javax.resource.spi.UnavailableException;
021import javax.resource.spi.endpoint.MessageEndpoint;
022import javax.transaction.xa.XAResource;
023
024import org.aopalliance.intercept.MethodInterceptor;
025import org.aopalliance.intercept.MethodInvocation;
026
027import org.springframework.aop.framework.ProxyFactory;
028import org.springframework.aop.support.DelegatingIntroductionInterceptor;
029import org.springframework.lang.Nullable;
030import org.springframework.util.Assert;
031import org.springframework.util.ReflectionUtils;
032
033/**
034 * Generic implementation of the JCA 1.7
035 * {@link javax.resource.spi.endpoint.MessageEndpointFactory} interface,
036 * providing transaction management capabilities for any kind of message
037 * listener object (e.g. {@link javax.jms.MessageListener} objects or
038 * {@link javax.resource.cci.MessageListener} objects.
039 *
040 * <p>Uses AOP proxies for concrete endpoint instances, simply wrapping
041 * the specified message listener object and exposing all of its implemented
042 * interfaces on the endpoint instance.
043 *
044 * <p>Typically used with Spring's {@link GenericMessageEndpointManager},
045 * but not tied to it. As a consequence, this endpoint factory could
046 * also be used with programmatic endpoint management on a native
047 * {@link javax.resource.spi.ResourceAdapter} instance.
048 *
049 * @author Juergen Hoeller
050 * @since 2.5
051 * @see #setMessageListener
052 * @see #setTransactionManager
053 * @see GenericMessageEndpointManager
054 */
055public class GenericMessageEndpointFactory extends AbstractMessageEndpointFactory {
056
057        @Nullable
058        private Object messageListener;
059
060
061        /**
062         * Specify the message listener object that the endpoint should expose
063         * (e.g. a {@link javax.jms.MessageListener} objects or
064         * {@link javax.resource.cci.MessageListener} implementation).
065         */
066        public void setMessageListener(Object messageListener) {
067                this.messageListener = messageListener;
068        }
069
070        /**
071         * Return the message listener object for this endpoint.
072         * @since 5.0
073         */
074        protected Object getMessageListener() {
075                Assert.state(this.messageListener != null, "No message listener set");
076                return this.messageListener;
077        }
078
079        /**
080         * Wrap each concrete endpoint instance with an AOP proxy,
081         * exposing the message listener's interfaces as well as the
082         * endpoint SPI through an AOP introduction.
083         */
084        @Override
085        public MessageEndpoint createEndpoint(XAResource xaResource) throws UnavailableException {
086                GenericMessageEndpoint endpoint = (GenericMessageEndpoint) super.createEndpoint(xaResource);
087                ProxyFactory proxyFactory = new ProxyFactory(getMessageListener());
088                DelegatingIntroductionInterceptor introduction = new DelegatingIntroductionInterceptor(endpoint);
089                introduction.suppressInterface(MethodInterceptor.class);
090                proxyFactory.addAdvice(introduction);
091                return (MessageEndpoint) proxyFactory.getProxy();
092        }
093
094        /**
095         * Creates a concrete generic message endpoint, internal to this factory.
096         */
097        @Override
098        protected AbstractMessageEndpoint createEndpointInternal() throws UnavailableException {
099                return new GenericMessageEndpoint();
100        }
101
102
103        /**
104         * Private inner class that implements the concrete generic message endpoint,
105         * as an AOP Alliance MethodInterceptor that will be invoked by a proxy.
106         */
107        private class GenericMessageEndpoint extends AbstractMessageEndpoint implements MethodInterceptor {
108
109                @Override
110                public Object invoke(MethodInvocation methodInvocation) throws Throwable {
111                        Throwable endpointEx = null;
112                        boolean applyDeliveryCalls = !hasBeforeDeliveryBeenCalled();
113                        if (applyDeliveryCalls) {
114                                try {
115                                        beforeDelivery(null);
116                                }
117                                catch (ResourceException ex) {
118                                        throw adaptExceptionIfNecessary(methodInvocation, ex);
119                                }
120                        }
121                        try {
122                                return methodInvocation.proceed();
123                        }
124                        catch (Throwable ex) {
125                                endpointEx = ex;
126                                onEndpointException(ex);
127                                throw ex;
128                        }
129                        finally {
130                                if (applyDeliveryCalls) {
131                                        try {
132                                                afterDelivery();
133                                        }
134                                        catch (ResourceException ex) {
135                                                if (endpointEx == null) {
136                                                        throw adaptExceptionIfNecessary(methodInvocation, ex);
137                                                }
138                                        }
139                                }
140                        }
141                }
142
143                private Exception adaptExceptionIfNecessary(MethodInvocation methodInvocation, ResourceException ex) {
144                        if (ReflectionUtils.declaresException(methodInvocation.getMethod(), ex.getClass())) {
145                                return ex;
146                        }
147                        else {
148                                return new InternalResourceException(ex);
149                        }
150                }
151
152                @Override
153                protected ClassLoader getEndpointClassLoader() {
154                        return getMessageListener().getClass().getClassLoader();
155                }
156        }
157
158
159        /**
160         * Internal exception thrown when a ResourceException has been encountered
161         * during the endpoint invocation.
162         * <p>Will only be used if the ResourceAdapter does not invoke the
163         * endpoint's {@code beforeDelivery} and {@code afterDelivery}
164         * directly, leaving it up to the concrete endpoint to apply those -
165         * and to handle any ResourceExceptions thrown from them.
166         */
167        @SuppressWarnings("serial")
168        public static class InternalResourceException extends RuntimeException {
169
170                public InternalResourceException(ResourceException cause) {
171                        super(cause);
172                }
173        }
174
175}