
org.ow2.bonita.pvm.internal.ejb.CommandReceiverMDB Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.ow2.bonita.pvm.internal.ejb;
import java.io.Serializable;
import javax.ejb.CreateException;
import javax.ejb.EJBException;
import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import org.ow2.bonita.pvm.internal.cmd.Command;
import org.ow2.bonita.pvm.internal.jobexecutor.ExecuteJobCmd;
import org.ow2.bonita.pvm.internal.log.Log;
import org.ow2.bonita.pvm.job.Job;
/**
* Message driven command receiver.
*
* Configuration
*
*
* The command receiver listens for messages on the destination established
* during deployment. Upon delivery, this bean extracts a {@linkplain Command
* command} from the message according to the format described in the
* {@link #onMessage(Message) onMessage} method.
*
*
* If the message does not match the expected format, it is forwarded to the
* message destination referenced by jms/DeadLetterQueue
. No
* further processing is done on the message.
*
*
*
* After extraction, the receiver dispatches the command to the
* {@linkplain LocalCommandExecutor local command executor} specified by the EJB
* reference ejb/LocalCommandExecutor
.
*
*
*
* In case the incoming message includes a replyTo
destination, the
* result of the command execution is wrapped into a message and sent there. The
* resource manager connection factory reference
* jms/CommandConnectionFactory
specifies the factory used to
* obtain JMS connections.
*
*
* @author Jim Rigsbee
* @author Tom Baeyens
* @author Alejandro Guizar
*/
public class CommandReceiverMDB implements MessageDrivenBean, MessageListener {
private MessageDrivenContext messageDrivenContext;
private LocalCommandExecutor commandExecutor;
private Connection jmsConnection;
private Destination deadLetterQueue;
private static final long serialVersionUID = 1L;
private static final Log LOG = Log.getLog(CommandReceiverMDB.class.getName());
/**
* Processes a command message.
*
* Message format
*
*
* The incoming messages must conform to one of the following formats.
*
*
*
* - Command object. The body of the message is a Java
* {@linkplain ObjectMessage object} that implements the {@link Command}
* interface. The header properties of the message, if any, are ignored.
* - Job reference. The message contains a header property
* called
jobId
of type long
which references a
* saved {@linkplain Job job} that is outstanding for execution. The body of
* the message, if any, is ignored.
*
*/
public void onMessage(Message message) {
try {
// extract command from message
Command< ? > command = extractCommand(message);
if (command == null) {
discard(message);
return;
}
// execute command via local command executor bean
Object result = commandExecutor.execute(command);
// send a response back if a "reply to" destination is set
Destination replyTo = message.getJMSReplyTo();
if (replyTo != null && (result instanceof Serializable || result == null)) {
sendResult((Serializable) result, replyTo, message.getJMSMessageID());
}
} catch (JMSException e) {
throw new EJBException("could not process message " + message, e);
}
}
private static Command< ? > extractCommand(Message message) throws JMSException {
Command< ? > command = null;
// messages can contain serialized commands
if (message instanceof ObjectMessage) {
ObjectMessage objectMessage = (ObjectMessage) message;
Serializable body = objectMessage.getObject();
LOG.debug("received object message with body " + body);
if (body instanceof Command) {
command = (Command< ? >) body;
} else {
LOG.info("WARNING: ignoring object message, body is not a command: "
+ message);
}
} else if (message.getObjectProperty("jobId") != null) {
// messages can also contain a jobId property
long jobId = message.getLongProperty("jobId");
LOG.debug("received message with jobImpl id " + jobId);
// create a command with the given jobId
command = new ExecuteJobCmd(jobId);
} else {
LOG.info("WARNING: ignoring message, unknown format: " + message);
}
return command;
}
private void discard(Message message) throws JMSException {
// if a dead letter queue is configured, send the message there
if (deadLetterQueue != null) {
Session jmsSession = createSession();
try {
jmsSession.createProducer(deadLetterQueue).send(message);
} finally {
jmsSession.close();
}
} else {
// reject the message
messageDrivenContext.setRollbackOnly();
}
}
private Session createSession() throws JMSException {
/*
* if the connection supports xa, the session will be transacted, else the
* session will auto acknowledge; in either case no explicit transaction
* control must be performed - see ejb 2.1 section 17.3.5
*/
return jmsConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
}
private void sendResult(Serializable result, Destination destination,
String correlationId) throws JMSException {
LOG.debug("sending result " + result + " to " + destination);
Session jmsSession = createSession();
try {
Message resultMessage = jmsSession.createObjectMessage(result);
resultMessage.setJMSCorrelationID(correlationId);
jmsSession.createProducer(destination).send(resultMessage);
} finally {
jmsSession.close();
}
}
public void setMessageDrivenContext(MessageDrivenContext messageDrivenContext) {
this.messageDrivenContext = messageDrivenContext;
}
public void ejbCreate() {
try {
Context context = new InitialContext();
LocalCommandExecutorHome commandExecutorHome = (LocalCommandExecutorHome) context
.lookup("java:comp/env/ejb/LocalCommandExecutor");
ConnectionFactory jmsConnectionFactory = (ConnectionFactory) context
.lookup("java:comp/env/jms/CommandConnectionFactory");
try {
deadLetterQueue = (Destination) context
.lookup("java:comp/env/jms/DeadLetterQueue");
} catch (NameNotFoundException e) {
LOG
.info("dead letter queue not specified, messages with unknown formats will be rejected");
}
context.close();
commandExecutor = commandExecutorHome.create();
jmsConnection = jmsConnectionFactory.createConnection();
} catch (NamingException e) {
throw new EJBException("error retrieving managed objects", e);
} catch (CreateException e) {
throw new EJBException("error creating command executor", e);
} catch (JMSException e) {
throw new EJBException("error creating jms connection", e);
}
}
public void ejbRemove() {
if (jmsConnection != null) {
try {
jmsConnection.close();
} catch (JMSException e) {
LOG.debug("could not close jms connection", e);
}
jmsConnection = null;
}
commandExecutor = null;
messageDrivenContext = null;
}
}