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
123 marshaller = new DefaultMarshaller();
124 }
125 }
126
127 public void destroy() throws Exception {
128 requestor.close();
129 }
130
131
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
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 }