/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.jms.remoting;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageFormatException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jms.listener.SessionAwareMessageListener;
import org.springframework.jms.support.JmsUtils;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.SimpleMessageConverter;
import org.springframework.remoting.support.RemoteInvocation;
import org.springframework.remoting.support.RemoteInvocationBasedExporter;
import org.springframework.remoting.support.RemoteInvocationResult;
/**
* JMS message listener that exports the specified service bean as a
* JMS service endpoint, accessible via a JMS invoker proxy.
*
* <p>Note that this class implements Spring's
* {@link org.springframework.jms.listener.SessionAwareMessageListener}
* interface, since it requires access to the active JMS Session.
* Hence, this class can only be used with message listener containers
* which support the SessionAwareMessageListener interface (e.g. Spring's
* {@link org.springframework.jms.listener.DefaultMessageListenerContainer}).
*
* <p>Thanks to James Strachan for the original prototype that this
* JMS invoker mechanism was inspired by!
*
* @author Juergen Hoeller
* @author James Strachan
* @since 2.0
* @see JmsInvokerClientInterceptor
* @see JmsInvokerProxyFactoryBean
*/
public class JmsInvokerServiceExporter extends RemoteInvocationBasedExporter
implements SessionAwareMessageListener<Message>, InitializingBean {
private MessageConverter messageConverter = new SimpleMessageConverter();
private boolean ignoreInvalidRequests = true;
private Object proxy;
/**
* Specify the MessageConverter to use for turning request messages into
* {@link org.springframework.remoting.support.RemoteInvocation} objects,
* as well as {@link org.springframework.remoting.support.RemoteInvocationResult}
* objects into response messages.
* <p>Default is a {@link org.springframework.jms.support.converter.SimpleMessageConverter},
* using a standard JMS {@link javax.jms.ObjectMessage} for each invocation /
* invocation result object.
* <p>Custom implementations may generally adapt Serializables into
* special kinds of messages, or might be specifically tailored for
* translating RemoteInvocation(Result)s into specific kinds of messages.
*/
public void setMessageConverter(MessageConverter messageConverter) {
this.messageConverter = (messageConverter != null ? messageConverter : new SimpleMessageConverter());
}
/**
* Set whether invalidly formatted messages should be discarded.
* Default is "true".
* <p>Switch this flag to "false" to throw an exception back to the
* listener container. This will typically lead to redelivery of
* the message, which is usually undesirable - since the message
* content will be the same (that is, still invalid).
*/
public void setIgnoreInvalidRequests(boolean ignoreInvalidRequests) {
this.ignoreInvalidRequests = ignoreInvalidRequests;
}
public void afterPropertiesSet() {
this.proxy = getProxyForService();
}
public void onMessage(Message requestMessage, Session session) throws JMSException {
RemoteInvocation invocation = readRemoteInvocation(requestMessage);
if (invocation != null) {
RemoteInvocationResult result = invokeAndCreateResult(invocation, this.proxy);
writeRemoteInvocationResult(requestMessage, session, result);
}
}
/**
* Read a RemoteInvocation from the given JMS message.
* @param requestMessage current request message
* @return the RemoteInvocation object (or <code>null</code>
* in case of an invalid message that will simply be ignored)
* @throws javax.jms.JMSException in case of message access failure
*/
protected RemoteInvocation readRemoteInvocation(Message requestMessage) throws JMSException {
Object content = this.messageConverter.fromMessage(requestMessage);
if (content instanceof RemoteInvocation) {
return (RemoteInvocation) content;
}
return onInvalidRequest(requestMessage);
}
/**
* Send the given RemoteInvocationResult as a JMS message to the originator.
* @param requestMessage current request message
* @param session the JMS Session to use
* @param result the RemoteInvocationResult object
* @throws javax.jms.JMSException if thrown by trying to send the message
*/
protected void writeRemoteInvocationResult(
Message requestMessage, Session session, RemoteInvocationResult result) throws JMSException {
Message response = createResponseMessage(requestMessage, session, result);
MessageProducer producer = session.createProducer(requestMessage.getJMSReplyTo());
try {
producer.send(response);
}
finally {
JmsUtils.closeMessageProducer(producer);
}
}
/**
* Create the invocation result response message.
* <p>The default implementation creates a JMS ObjectMessage for the given
* RemoteInvocationResult object. It sets the response's correlation id
* to the request message's correlation id, if any; otherwise to the
* request message id.
* @param request the original request message
* @param session the JMS session to use
* @param result the invocation result
* @return the message response to send
* @throws javax.jms.JMSException if creating the messsage failed
*/
protected Message createResponseMessage(Message request, Session session, RemoteInvocationResult result)
throws JMSException {
Message response = this.messageConverter.toMessage(result, session);
String correlation = request.getJMSCorrelationID();
if (correlation == null) {
correlation = request.getJMSMessageID();
}
response.setJMSCorrelationID(correlation);
return response;
}
/**
* Callback that is invoked by {@link #readRemoteInvocation}
* when it encounters an invalid request message.
* <p>The default implementation either discards the invalid message or
* throws a MessageFormatException - according to the "ignoreInvalidRequests"
* flag, which is set to "true" (that is, discard invalid messages) by default.
* @param requestMessage the invalid request message
* @return the RemoteInvocation to expose for the invalid request (typically
* <code>null</code> in case of an invalid message that will simply be ignored)
* @throws javax.jms.JMSException in case of the invalid request supposed
* to lead to an exception (instead of ignoring it)
* @see #readRemoteInvocation
* @see #setIgnoreInvalidRequests
*/
protected RemoteInvocation onInvalidRequest(Message requestMessage) throws JMSException {
if (this.ignoreInvalidRequests) {
if (logger.isWarnEnabled()) {
logger.warn("Invalid request message will be discarded: " + requestMessage);
}
return null;
}
else {
throw new MessageFormatException("Invalid request message: " + requestMessage);
}
}
}
|