View Javadoc

1   /***
2    *
3    * Copyright 2005 LogicBlaze, Inc.
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   *
17   **/
18  
19  package org.logicblaze.lingo.jms;
20  
21  import org.logicblaze.lingo.LingoInvocation;
22  import org.logicblaze.lingo.LingoRemoteInvocationFactory;
23  import org.logicblaze.lingo.MetadataStrategy;
24  import org.logicblaze.lingo.MethodMetadata;
25  import org.logicblaze.lingo.SimpleMetadataStrategy;
26  import org.logicblaze.lingo.jms.impl.AsyncReplyHandler;
27  import org.logicblaze.lingo.jms.impl.MultiplexingRequestor;
28  import org.logicblaze.lingo.jms.marshall.DefaultMarshaller;
29  import org.logicblaze.lingo.jms.marshall.Marshaller;
30  import org.aopalliance.intercept.MethodInterceptor;
31  import org.aopalliance.intercept.MethodInvocation;
32  import org.springframework.aop.support.AopUtils;
33  import org.springframework.beans.factory.DisposableBean;
34  import org.springframework.beans.factory.InitializingBean;
35  import org.springframework.remoting.RemoteAccessException;
36  import org.springframework.remoting.support.RemoteInvocationBasedAccessor;
37  import org.springframework.remoting.support.RemoteInvocationFactory;
38  import org.springframework.remoting.support.RemoteInvocationResult;
39  
40  import javax.jms.ConnectionFactory;
41  import javax.jms.Destination;
42  import javax.jms.JMSException;
43  import javax.jms.Message;
44  import java.util.Map;
45  import java.util.WeakHashMap;
46  
47  /***
48   * Interceptor for accessing a JMS based service which must be configured with a
49   * {@link org.logicblaze.lingo.LingoRemoteInvocationFactory} instance.
50   *
51   * @author James Strachan
52   * @see #setServiceInterface
53   * @see #setServiceUrl
54   * @see JmsServiceExporter
55   * @see JmsProxyFactoryBean
56   */
57  public class JmsClientInterceptor extends RemoteInvocationBasedAccessor
58          implements MethodInterceptor, InitializingBean, DisposableBean {
59  
60      private Map remoteObjects = new WeakHashMap();
61      private Requestor requestor;
62      private Destination destination;
63      private Destination responseDestination;
64      private String correlationID;
65      private Marshaller marshaller;
66      private ConnectionFactory connectionFactory;
67  
68      public JmsClientInterceptor() {
69          setRemoteInvocationFactory(createRemoteInvocationFactory());
70      }
71  
72      public JmsClientInterceptor(Requestor requestor) {
73          this.requestor = requestor;
74          setRemoteInvocationFactory(createRemoteInvocationFactory());
75      }
76  
77      public JmsClientInterceptor(Requestor requestor, LingoRemoteInvocationFactory factory) {
78          this.requestor = requestor;
79          setRemoteInvocationFactory(factory);
80      }
81  
82      public Object invoke(MethodInvocation methodInvocation) throws Throwable {
83          if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
84              return "JMS invoker proxy for service URL [" + getServiceUrl() + "]";
85          }
86          LingoInvocation invocation = (LingoInvocation) createRemoteInvocation(methodInvocation);
87          MethodMetadata metadata = invocation.getMetadata();
88          replaceRemoteReferences(invocation, metadata);
89          try {
90              Message requestMessage = marshaller.createRequestMessage(requestor, invocation);
91              populateHeaders(requestMessage);
92              if (metadata.isOneWay()) {
93                  requestor.oneWay(destination, requestMessage);
94                  return null;
95              }
96              else {
97                  Message response = requestor.request(destination, requestMessage);
98                  RemoteInvocationResult result = marshaller.extractInvocationResult(response);
99                  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 }