001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.jms.remoting;
018:
019: import javax.jms.JMSException;
020: import javax.jms.Message;
021: import javax.jms.MessageFormatException;
022: import javax.jms.Queue;
023: import javax.jms.QueueConnection;
024: import javax.jms.QueueConnectionFactory;
025: import javax.jms.QueueRequestor;
026: import javax.jms.QueueSession;
027: import javax.jms.Session;
028:
029: import org.aopalliance.intercept.MethodInterceptor;
030: import org.aopalliance.intercept.MethodInvocation;
031:
032: import org.springframework.aop.support.AopUtils;
033: import org.springframework.beans.factory.InitializingBean;
034: import org.springframework.jms.connection.ConnectionFactoryUtils;
035: import org.springframework.jms.support.JmsUtils;
036: import org.springframework.jms.support.converter.MessageConverter;
037: import org.springframework.jms.support.converter.SimpleMessageConverter;
038: import org.springframework.jms.support.destination.DestinationResolver;
039: import org.springframework.jms.support.destination.DynamicDestinationResolver;
040: import org.springframework.remoting.RemoteAccessException;
041: import org.springframework.remoting.support.DefaultRemoteInvocationFactory;
042: import org.springframework.remoting.support.RemoteInvocation;
043: import org.springframework.remoting.support.RemoteInvocationFactory;
044: import org.springframework.remoting.support.RemoteInvocationResult;
045:
046: /**
047: * Interceptor for accessing a JMS-based remote service.
048: *
049: * <p>Serializes remote invocation objects and deserializes remote invocation
050: * result objects. Uses Java serialization just like RMI, but with the JMS
051: * provider as communication infrastructure.
052: *
053: * <p>To be configured with a {@link javax.jms.QueueConnectionFactory} and a
054: * target queue (either as {@link javax.jms.Queue} reference or as queue name).
055: *
056: * <p>Thanks to James Strachan for the original prototype that this
057: * JMS invoker mechanism was inspired by!
058: *
059: * @author Juergen Hoeller
060: * @author James Strachan
061: * @since 2.0
062: * @see #setConnectionFactory
063: * @see #setQueue
064: * @see #setQueueName
065: * @see org.springframework.jms.remoting.JmsInvokerServiceExporter
066: * @see org.springframework.jms.remoting.JmsInvokerProxyFactoryBean
067: */
068: public class JmsInvokerClientInterceptor implements MethodInterceptor,
069: InitializingBean {
070:
071: private QueueConnectionFactory connectionFactory;
072:
073: private Object queue;
074:
075: private DestinationResolver destinationResolver = new DynamicDestinationResolver();
076:
077: private RemoteInvocationFactory remoteInvocationFactory = new DefaultRemoteInvocationFactory();
078:
079: private MessageConverter messageConverter = new SimpleMessageConverter();
080:
081: /**
082: * Set the QueueConnectionFactory to use for obtaining JMS QueueConnections.
083: */
084: public void setConnectionFactory(
085: QueueConnectionFactory connectionFactory) {
086: this .connectionFactory = connectionFactory;
087: }
088:
089: /**
090: * Return the QueueConnectionFactory to use for obtaining JMS QueueConnections.
091: */
092: protected QueueConnectionFactory getConnectionFactory() {
093: return this .connectionFactory;
094: }
095:
096: /**
097: * Set the target Queue to send invoker requests to.
098: */
099: public void setQueue(Queue queue) {
100: this .queue = queue;
101: }
102:
103: /**
104: * Set the name of target queue to send invoker requests to.
105: * The specified name will be dynamically resolved via the
106: * {@link #setDestinationResolver DestinationResolver}.
107: */
108: public void setQueueName(String queueName) {
109: this .queue = queueName;
110: }
111:
112: /**
113: * Set the DestinationResolver that is to be used to resolve Queue
114: * references for this accessor.
115: * <p>The default resolver is a DynamicDestinationResolver. Specify a
116: * JndiDestinationResolver for resolving destination names as JNDI locations.
117: * @see org.springframework.jms.support.destination.DynamicDestinationResolver
118: * @see org.springframework.jms.support.destination.JndiDestinationResolver
119: */
120: public void setDestinationResolver(
121: DestinationResolver destinationResolver) {
122: this .destinationResolver = (destinationResolver != null ? destinationResolver
123: : new DynamicDestinationResolver());
124: }
125:
126: /**
127: * Set the RemoteInvocationFactory to use for this accessor.
128: * Default is a {@link org.springframework.remoting.support.DefaultRemoteInvocationFactory}.
129: * <p>A custom invocation factory can add further context information
130: * to the invocation, for example user credentials.
131: */
132: public void setRemoteInvocationFactory(
133: RemoteInvocationFactory remoteInvocationFactory) {
134: this .remoteInvocationFactory = (remoteInvocationFactory != null ? remoteInvocationFactory
135: : new DefaultRemoteInvocationFactory());
136: }
137:
138: /**
139: * Specify the MessageConverter to use for turning
140: * {@link org.springframework.remoting.support.RemoteInvocation}
141: * objects into request messages, as well as response messages into
142: * {@link org.springframework.remoting.support.RemoteInvocationResult} objects.
143: * <p>Default is a {@link org.springframework.jms.support.converter.SimpleMessageConverter},
144: * using a standard JMS {@link javax.jms.ObjectMessage} for each invocation /
145: * invocation result object.
146: * <p>Custom implementations may generally adapt Serializables into
147: * special kinds of messages, or might be specifically tailored for
148: * translating RemoteInvocation(Result)s into specific kinds of messages.
149: */
150: public void setMessageConverter(MessageConverter messageConverter) {
151: this .messageConverter = (messageConverter != null ? messageConverter
152: : new SimpleMessageConverter());
153: }
154:
155: public void afterPropertiesSet() {
156: if (getConnectionFactory() == null) {
157: throw new IllegalArgumentException(
158: "Property 'connectionFactory' is required");
159: }
160: if (this .queue == null) {
161: throw new IllegalArgumentException(
162: "'queue' or 'queueName' is required");
163: }
164: }
165:
166: public Object invoke(MethodInvocation methodInvocation)
167: throws Throwable {
168: if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
169: return "JMS invoker proxy for queue [" + this .queue + "]";
170: }
171:
172: RemoteInvocation invocation = createRemoteInvocation(methodInvocation);
173: RemoteInvocationResult result = null;
174: try {
175: result = executeRequest(invocation);
176: } catch (JMSException ex) {
177: throw new RemoteAccessException(
178: "Cannot access JMS invoker queue [" + this .queue
179: + "]", ex);
180: }
181: return recreateRemoteInvocationResult(result);
182: }
183:
184: /**
185: * Create a new RemoteInvocation object for the given AOP method invocation.
186: * The default implementation delegates to the RemoteInvocationFactory.
187: * <p>Can be overridden in subclasses to provide custom RemoteInvocation
188: * subclasses, containing additional invocation parameters like user credentials.
189: * Note that it is preferable to use a custom RemoteInvocationFactory which
190: * is a reusable strategy.
191: * @param methodInvocation the current AOP method invocation
192: * @return the RemoteInvocation object
193: * @see RemoteInvocationFactory#createRemoteInvocation
194: */
195: protected RemoteInvocation createRemoteInvocation(
196: MethodInvocation methodInvocation) {
197: return this .remoteInvocationFactory
198: .createRemoteInvocation(methodInvocation);
199: }
200:
201: /**
202: * Execute the given remote invocation, sending an invoker request message
203: * to this accessor's target queue and waiting for a corresponding response.
204: * @param invocation the RemoteInvocation to execute
205: * @return the RemoteInvocationResult object
206: * @throws JMSException in case of JMS failure
207: * @see #doExecuteRequest
208: */
209: protected RemoteInvocationResult executeRequest(
210: RemoteInvocation invocation) throws JMSException {
211: QueueConnection con = getConnectionFactory()
212: .createQueueConnection();
213: QueueSession session = null;
214: QueueRequestor requestor = null;
215: try {
216: session = con.createQueueSession(false,
217: Session.AUTO_ACKNOWLEDGE);
218: Queue queueToUse = resolveQueue(session);
219: Message requestMessage = createRequestMessage(session,
220: invocation);
221: con.start();
222: Message responseMessage = doExecuteRequest(session,
223: queueToUse, requestMessage);
224: return extractInvocationResult(responseMessage);
225: } finally {
226: JmsUtils.closeQueueRequestor(requestor);
227: JmsUtils.closeSession(session);
228: ConnectionFactoryUtils.releaseConnection(con,
229: getConnectionFactory(), true);
230: }
231: }
232:
233: /**
234: * Resolve this accessor's target queue.
235: * @param session the current JMS Session
236: * @return the resolved target Queue
237: * @throws JMSException if resolution failed
238: */
239: protected Queue resolveQueue(Session session) throws JMSException {
240: if (this .queue instanceof Queue) {
241: return (Queue) this .queue;
242: } else if (this .queue instanceof String) {
243: return resolveQueueName(session, (String) this .queue);
244: } else {
245: throw new javax.jms.IllegalStateException(
246: "Queue object ["
247: + this .queue
248: + "] is neither a [javax.jms.Queue] nor a queue name String");
249: }
250: }
251:
252: /**
253: * Resolve the given queue name into a JMS {@link javax.jms.Queue},
254: * via this accessor's {@link DestinationResolver}.
255: * @param session the current JMS Session
256: * @param queueName the name of the queue
257: * @return the located Queue
258: * @throws JMSException if resolution failed
259: * @see #setDestinationResolver
260: */
261: protected Queue resolveQueueName(Session session, String queueName)
262: throws JMSException {
263: return (Queue) this .destinationResolver.resolveDestinationName(
264: session, queueName, false);
265: }
266:
267: /**
268: * Create the invoker request message.
269: * <p>The default implementation creates a JMS ObjectMessage
270: * for the given RemoteInvocation object.
271: * @param session the current JMS Session
272: * @param invocation the remote invocation to send
273: * @return the JMS Message to send
274: * @throws JMSException if the message could not be created
275: */
276: protected Message createRequestMessage(Session session,
277: RemoteInvocation invocation) throws JMSException {
278: return this .messageConverter.toMessage(invocation, session);
279: }
280:
281: /**
282: * Actually execute the given request, sending the invoker request message
283: * to the specified target queue and waiting for a corresponding response.
284: * <p>The default implementation is based on a standard JMS
285: * {@link javax.jms.QueueRequestor}, using a freshly obtained JMS Session.
286: * @param session the JMS Session to use
287: * @param queue the resolved target Queue to send to
288: * @param requestMessage the JMS Message to send
289: * @return the RemoteInvocationResult object
290: * @throws JMSException in case of JMS failure
291: */
292: protected Message doExecuteRequest(QueueSession session,
293: Queue queue, Message requestMessage) throws JMSException {
294:
295: QueueRequestor requestor = new QueueRequestor(session, queue);
296: return requestor.request(requestMessage);
297: }
298:
299: /**
300: * Extract the invocation result from the response message.
301: * <p>The default implementation expects a JMS ObjectMessage carrying
302: * a RemoteInvocationResult object. If an invalid response message is
303: * encountered, the <code>onInvalidResponse</code> callback gets invoked.
304: * @param responseMessage the response message
305: * @return the invocation result
306: * @throws JMSException is thrown if a JMS exception occurs
307: * @see #onInvalidResponse
308: */
309: protected RemoteInvocationResult extractInvocationResult(
310: Message responseMessage) throws JMSException {
311: Object content = this .messageConverter
312: .fromMessage(responseMessage);
313: if (content instanceof RemoteInvocationResult) {
314: return (RemoteInvocationResult) content;
315: }
316: return onInvalidResponse(responseMessage);
317: }
318:
319: /**
320: * Callback that is invoked by <code>extractInvocationResult</code>
321: * when it encounters an invalid response message.
322: * <p>The default implementation throws a MessageFormatException.
323: * @param responseMessage the invalid response message
324: * @return an alternative invocation result that should be
325: * returned to the caller (if desired)
326: * @throws JMSException if the invalid response should lead
327: * to an infrastructure exception propagated to the caller
328: * @see #extractInvocationResult
329: */
330: protected RemoteInvocationResult onInvalidResponse(
331: Message responseMessage) throws JMSException {
332: throw new MessageFormatException("Invalid response message: "
333: + responseMessage);
334: }
335:
336: /**
337: * Recreate the invocation result contained in the given RemoteInvocationResult
338: * object. The default implementation calls the default recreate method.
339: * <p>Can be overridden in subclass to provide custom recreation, potentially
340: * processing the returned result object.
341: * @param result the RemoteInvocationResult to recreate
342: * @return a return value if the invocation result is a successful return
343: * @throws Throwable if the invocation result is an exception
344: * @see org.springframework.remoting.support.RemoteInvocationResult#recreate()
345: */
346: protected Object recreateRemoteInvocationResult(
347: RemoteInvocationResult result) throws Throwable {
348: return result.recreate();
349: }
350:
351: }
|