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.jms.listener.endpoint;
018
019import javax.jms.Message;
020import javax.jms.MessageListener;
021import javax.resource.ResourceException;
022import javax.resource.spi.UnavailableException;
023
024import org.springframework.jca.endpoint.AbstractMessageEndpointFactory;
025import org.springframework.lang.Nullable;
026import org.springframework.util.Assert;
027
028/**
029 * JMS-specific implementation of the JCA 1.7
030 * {@link javax.resource.spi.endpoint.MessageEndpointFactory} interface,
031 * providing transaction management capabilities for a JMS listener object
032 * (e.g. a {@link javax.jms.MessageListener} object).
033 *
034 * <p>Uses a static endpoint implementation, simply wrapping the
035 * specified message listener object and exposing all of its implemented
036 * interfaces on the endpoint instance.
037 *
038 * <p>Typically used with Spring's {@link JmsMessageEndpointManager},
039 * but not tied to it. As a consequence, this endpoint factory could
040 * also be used with programmatic endpoint management on a native
041 * {@link javax.resource.spi.ResourceAdapter} instance.
042 *
043 * @author Juergen Hoeller
044 * @author Stephane Nicoll
045 * @since 2.5
046 * @see #setMessageListener
047 * @see #setTransactionManager
048 * @see JmsMessageEndpointManager
049 */
050public class JmsMessageEndpointFactory extends AbstractMessageEndpointFactory  {
051
052        @Nullable
053        private MessageListener messageListener;
054
055
056        /**
057         * Set the JMS MessageListener for this endpoint.
058         */
059        public void setMessageListener(MessageListener messageListener) {
060                this.messageListener = messageListener;
061        }
062
063        /**
064         * Return the JMS MessageListener for this endpoint.
065         */
066        protected MessageListener getMessageListener() {
067                Assert.state(this.messageListener != null, "No MessageListener set");
068                return this.messageListener;
069        }
070
071        /**
072         * Creates a concrete JMS message endpoint, internal to this factory.
073         */
074        @Override
075        protected AbstractMessageEndpoint createEndpointInternal() throws UnavailableException {
076                return new JmsMessageEndpoint();
077        }
078
079
080        /**
081         * Private inner class that implements the concrete JMS message endpoint.
082         */
083        private class JmsMessageEndpoint extends AbstractMessageEndpoint implements MessageListener {
084
085                @Override
086                public void onMessage(Message message) {
087                        Throwable endpointEx = null;
088                        boolean applyDeliveryCalls = !hasBeforeDeliveryBeenCalled();
089                        if (applyDeliveryCalls) {
090                                try {
091                                        beforeDelivery(null);
092                                }
093                                catch (ResourceException ex) {
094                                        throw new JmsResourceException(ex);
095                                }
096                        }
097                        try {
098                                getMessageListener().onMessage(message);
099                        }
100                        catch (RuntimeException | Error ex) {
101                                endpointEx = ex;
102                                onEndpointException(ex);
103                                throw ex;
104                        }
105                        finally {
106                                if (applyDeliveryCalls) {
107                                        try {
108                                                afterDelivery();
109                                        }
110                                        catch (ResourceException ex) {
111                                                if (endpointEx == null) {
112                                                        throw new JmsResourceException(ex);
113                                                }
114                                        }
115                                }
116                        }
117                }
118
119                @Override
120                protected ClassLoader getEndpointClassLoader() {
121                        return getMessageListener().getClass().getClassLoader();
122                }
123        }
124
125
126        /**
127         * Internal exception thrown when a ResourceException has been encountered
128         * during the endpoint invocation.
129         * <p>Will only be used if the ResourceAdapter does not invoke the
130         * endpoint's {@code beforeDelivery} and {@code afterDelivery}
131         * directly, leaving it up to the concrete endpoint to apply those -
132         * and to handle any ResourceExceptions thrown from them.
133         */
134        @SuppressWarnings("serial")
135        public static class JmsResourceException extends RuntimeException {
136
137                public JmsResourceException(ResourceException cause) {
138                        super(cause);
139                }
140        }
141
142}