001    /**
002     *
003     * Copyright 2005 LogicBlaze, Inc.
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     * http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     *
017     **/
018    
019    package org.logicblaze.lingo.jms;
020    
021    import org.logicblaze.lingo.LingoInvocation;
022    import org.logicblaze.lingo.LingoRemoteInvocationFactory;
023    import org.logicblaze.lingo.MetadataStrategy;
024    import org.logicblaze.lingo.MethodMetadata;
025    import org.logicblaze.lingo.SimpleMetadataStrategy;
026    import org.logicblaze.lingo.jms.impl.AsyncReplyHandler;
027    import org.logicblaze.lingo.jms.impl.MultiplexingRequestor;
028    import org.logicblaze.lingo.jms.marshall.DefaultMarshaller;
029    import org.logicblaze.lingo.jms.marshall.Marshaller;
030    import org.aopalliance.intercept.MethodInterceptor;
031    import org.aopalliance.intercept.MethodInvocation;
032    import org.springframework.aop.support.AopUtils;
033    import org.springframework.beans.factory.DisposableBean;
034    import org.springframework.beans.factory.InitializingBean;
035    import org.springframework.remoting.RemoteAccessException;
036    import org.springframework.remoting.support.RemoteInvocationBasedAccessor;
037    import org.springframework.remoting.support.RemoteInvocationFactory;
038    import org.springframework.remoting.support.RemoteInvocationResult;
039    
040    import javax.jms.ConnectionFactory;
041    import javax.jms.Destination;
042    import javax.jms.JMSException;
043    import javax.jms.Message;
044    import java.util.Map;
045    import java.util.WeakHashMap;
046    
047    /**
048     * Interceptor for accessing a JMS based service which must be configured with a
049     * {@link org.logicblaze.lingo.LingoRemoteInvocationFactory} instance.
050     *
051     * @author James Strachan
052     * @see #setServiceInterface
053     * @see #setServiceUrl
054     * @see JmsServiceExporter
055     * @see JmsProxyFactoryBean
056     */
057    public class JmsClientInterceptor extends RemoteInvocationBasedAccessor
058            implements MethodInterceptor, InitializingBean, DisposableBean {
059    
060        private Map remoteObjects = new WeakHashMap();
061        private Requestor requestor;
062        private Destination destination;
063        private Destination responseDestination;
064        private String correlationID;
065        private Marshaller marshaller;
066        private ConnectionFactory connectionFactory;
067    
068        public JmsClientInterceptor() {
069            setRemoteInvocationFactory(createRemoteInvocationFactory());
070        }
071    
072        public JmsClientInterceptor(Requestor requestor) {
073            this.requestor = requestor;
074            setRemoteInvocationFactory(createRemoteInvocationFactory());
075        }
076    
077        public JmsClientInterceptor(Requestor requestor, LingoRemoteInvocationFactory factory) {
078            this.requestor = requestor;
079            setRemoteInvocationFactory(factory);
080        }
081    
082        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
083            if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
084                return "JMS invoker proxy for service URL [" + getServiceUrl() + "]";
085            }
086            LingoInvocation invocation = (LingoInvocation) createRemoteInvocation(methodInvocation);
087            MethodMetadata metadata = invocation.getMetadata();
088            replaceRemoteReferences(invocation, metadata);
089            try {
090                Message requestMessage = marshaller.createRequestMessage(requestor, invocation);
091                populateHeaders(requestMessage);
092                if (metadata.isOneWay()) {
093                    requestor.oneWay(destination, requestMessage);
094                    return null;
095                }
096                else {
097                    Message response = requestor.request(destination, requestMessage);
098                    RemoteInvocationResult result = marshaller.extractInvocationResult(response);
099                    return recreateRemoteInvocationResult(result);
100                }
101            }
102            catch (JMSException ex) {
103                throw new RemoteAccessException("Cannot access JMS invoker remote service at [" + getServiceUrl() + "]", ex);
104            }
105        }
106    
107        public void afterPropertiesSet() throws JMSException {
108            RemoteInvocationFactory factory = getRemoteInvocationFactory();
109            if (!(factory instanceof LingoRemoteInvocationFactory)) {
110                throw new IllegalArgumentException("remoteInvocationFactory must be an instance of LingoRemoteInvocationFactory but was: " + factory);
111    
112            }
113            if (requestor == null) {
114                if (connectionFactory == null) {
115                    throw new IllegalArgumentException("requestor or connectionFactory is required");
116                }
117                else {
118                    requestor = MultiplexingRequestor.newInstance(connectionFactory, destination, responseDestination);
119                }
120            }
121            if (marshaller == null) {
122                // default to standard JMS marshalling
123                marshaller = new DefaultMarshaller();
124            }
125        }
126    
127        public void destroy() throws Exception {
128            requestor.close();
129        }
130    
131        // Properties
132        //-------------------------------------------------------------------------
133        public Requestor getRequestor() {
134            return requestor;
135        }
136    
137        public void setRequestor(Requestor requestor) {
138            this.requestor = requestor;
139        }
140    
141        public Destination getDestination() {
142            return destination;
143        }
144    
145        /**
146         * Sets the destination used to make requests
147         *
148         * @param destination
149         */
150        public void setDestination(Destination destination) {
151            this.destination = destination;
152        }
153    
154        public Destination getResponseDestination() {
155            return responseDestination;
156        }
157    
158        /**
159         * Sets the destination used to consume responses on - or null
160         * and a temporary queue will be created.
161         *
162         * @param responseDestination
163         */
164        public void setResponseDestination(Destination responseDestination) {
165            this.responseDestination = responseDestination;
166        }
167    
168        public void setCorrelationID(String correlationID) {
169            this.correlationID = correlationID;
170        }
171    
172        public Marshaller getMarshaller() {
173            return marshaller;
174        }
175    
176        public void setMarshaller(Marshaller marshaller) {
177            this.marshaller = marshaller;
178        }
179    
180        public ConnectionFactory getConnectionFactory() {
181            return connectionFactory;
182        }
183    
184        /**
185         * Used to create a default {@link Requestor} if no requestor is explicitly
186         * configured.
187         */
188        public void setConnectionFactory(ConnectionFactory connectionFactory) {
189            this.connectionFactory = connectionFactory;
190        }
191    
192        // Implementation methods
193        //-------------------------------------------------------------------------
194    
195        protected void populateHeaders(Message requestMessage) throws JMSException {
196            if (correlationID != null) {
197                requestMessage.setJMSCorrelationID(correlationID);
198            }
199        }
200    
201    
202        /**
203         * Recreate the invocation result contained in the given RemoteInvocationResult
204         * object. The default implementation calls the default recreate method.
205         * <p>Can be overridden in subclass to provide custom recreation, potentially
206         * processing the returned result object.
207         *
208         * @param result the RemoteInvocationResult to recreate
209         * @return a return value if the invocation result is a successful return
210         * @throws Throwable if the invocation result is an exception
211         * @see org.springframework.remoting.support.RemoteInvocationResult#recreate
212         */
213        protected Object recreateRemoteInvocationResult(RemoteInvocationResult result) throws Throwable {
214            return result.recreate();
215        }
216    
217        protected void replaceRemoteReferences(LingoInvocation invocation, MethodMetadata metadata) {
218            Object[] arguments = invocation.getArguments();
219            Class[] parameterTypes = invocation.getParameterTypes();
220            for (int i = 0; i < parameterTypes.length; i++) {
221                if (metadata.isRemoteParameter(i)) {
222                    arguments[i] = remoteReference(parameterTypes[i], arguments[i]);
223                }
224            }
225        }
226    
227        protected Object remoteReference(Class type, Object value) {
228            if (value == null) {
229                return null;
230            }
231            String correlationID = (String) remoteObjects.get(value);
232            if (correlationID == null) {
233                correlationID = requestor.createCorrelationID();
234                remoteObjects.put(value, correlationID);
235            }
236            if (requestor instanceof MultiplexingRequestor) {
237                MultiplexingRequestor multiplexingRequestor = (MultiplexingRequestor) requestor;
238                multiplexingRequestor.registerHandler(correlationID, new AsyncReplyHandler(value, marshaller));
239            }
240            else {
241                throw new IllegalArgumentException("You can only pass remote references with a MultiplexingRequestor");
242            }
243            return correlationID;
244        }
245    
246        /**
247         * Factory method to create a default lingo based invocation factory if none is configured
248         */
249        protected LingoRemoteInvocationFactory createRemoteInvocationFactory() {
250            return new LingoRemoteInvocationFactory(createMetadataStrategy());
251        }
252    
253        /**
254         * Factory method to create a default metadata strategy if none is configured
255         *
256         * @return
257         */
258        protected MetadataStrategy createMetadataStrategy() {
259            return new SimpleMetadataStrategy();
260        }
261    
262    }