/*
* 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.jbpm.ejb.impl;
import java.io.Serializable;
import javax.ejb.CreateException;
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.NamingException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jbpm.JbpmException;
import org.jbpm.command.Command;
import org.jbpm.ejb.LocalCommandService;
import org.jbpm.ejb.LocalCommandServiceHome;
import org.jbpm.persistence.db.DbPersistenceService;
import org.jbpm.persistence.db.StaleObjectLogConfigurer;
/**
* This message-driven bean listens for {@link ObjectMessage object messages} containing a
* command instance. The received commands are executed by the {@link CommandServiceBean command
* service} bean, using the local interface.
*
* The body of the message must be a Java object that implements the {@link Command} interface.
* The message properties, if any, are ignored.
*
* <h3>Environment</h3>
*
* <p>
* The environment entries and resources available for customization are summarized in the table
* below.
* </p>
*
* <table border="1">
* <tr>
* <th>Name</th>
* <th>Type</th>
* <th>Description</th>
* </tr>
* <tr>
* <td><code>ejb/LocalCommandServiceBean</code></td>
* <td>EJB Reference</td>
* <td>Link to the local {@linkplain CommandServiceBean session bean} that executes commands on
* a separate jBPM context.</td>
* </tr>
* <tr>
* <td><code>jms/JbpmConnectionFactory</code></td>
* <td>Resource Manager Reference</td>
* <td>Logical name of the factory that provides JMS connections for producing result messages.
* Required for command messages that indicate a reply destination.</td>
* </tr>
* </table>
*
* @author Jim Rigsbee
* @author Alejandro Guizar
* @author Tom Baeyens
*/
public class CommandListenerBean implements MessageDrivenBean, MessageListener {
private static final long serialVersionUID = 1L;
private MessageDrivenContext messageDrivenContext;
private LocalCommandService commandService;
private ConnectionFactory jmsConnectionFactory;
private static final Log log = LogFactory.getLog(CommandListenerBean.class);
public void onMessage(Message message) {
try {
// extract command from message
Command command = extractCommand(message);
if (command == null) return;
// execute command via local command executor bean
Object result;
try {
result = commandService.execute(command);
}
catch (RuntimeException e) {
// if this is a locking exception, keep it quiet
if (DbPersistenceService.isLockingException(e)) {
StaleObjectLogConfigurer.getStaleObjectExceptionsLog().error("failed to execute "
+ command, e);
}
else {
log.error("failed to execute " + command, e);
}
// MDBs are not supposed to throw exceptions
messageDrivenContext.setRollbackOnly();
return;
}
// send the result back if a "reply-to" destination is set
Destination replyTo;
if (jmsConnectionFactory != null
&& (replyTo = message.getJMSReplyTo()) != null
&& (result instanceof Serializable || result == null)) {
sendResult((Serializable) result, replyTo, message.getJMSMessageID());
}
}
catch (JMSException e) {
messageDrivenContext.setRollbackOnly();
log.error("failed to process message " + message, e);
}
}
protected Command extractCommand(Message message) throws JMSException {
if (message instanceof ObjectMessage) {
ObjectMessage objectMessage = (ObjectMessage) message;
Serializable object = objectMessage.getObject();
if (object instanceof Command) {
return (Command) object;
}
else {
log.warn("not a command: " + object);
}
}
else {
log.warn("not an object message: " + message);
}
return null;
}
private void sendResult(Serializable result, Destination destination, String correlationId)
throws JMSException {
if (log.isDebugEnabled()) log.debug("sending " + result + " to " + destination);
Connection jmsConnection = jmsConnectionFactory.createConnection();
try {
/*
* 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 - 17.3.5
*/
Session jmsSession = jmsConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Message resultMessage = jmsSession.createObjectMessage(result);
resultMessage.setJMSCorrelationID(correlationId);
jmsSession.createProducer(destination).send(resultMessage);
}
finally {
jmsConnection.close();
}
}
public void setMessageDrivenContext(MessageDrivenContext messageDrivenContext) {
this.messageDrivenContext = messageDrivenContext;
}
public void ejbRemove() {
jmsConnectionFactory = null;
commandService = null;
messageDrivenContext = null;
}
public void ejbCreate() {
try {
Context jndiContext = new InitialContext();
// create command service
LocalCommandServiceHome commandServiceHome = (LocalCommandServiceHome) jndiContext.lookup("java:comp/env/ejb/LocalCommandServiceBean");
commandService = commandServiceHome.create();
// look for optional connection factory
try {
jmsConnectionFactory = (ConnectionFactory) jndiContext.lookup("java:comp/env/jms/JbpmConnectionFactory");
}
catch (NamingException e) {
log.warn("error retrieving connection factory, results will not be sent back", e);
}
jndiContext.close();
}
catch (NamingException e) {
throw new JbpmException("error retrieving command service home", e);
}
catch (CreateException e) {
throw new JbpmException("failed to create command service", e);
}
}
}