/*
* Copyright (C) 2004-2006 Paul Browne, http://www.firstpartners.net,
*
* released under terms of the GPL license
* http://www.opensource.org/licenses/gpl-license.php
*
*/
package net.fp.rp.workflow.message;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Hashtable;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import net.fp.rp.common.exception.RpException;
import net.fp.rp.jms.ITestMessageSender;
import net.fp.rp.jms.JmsTextMessageSender;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Spring Bean for sending JMS Messages. This one uses the JBoss components
* internally (as JBoss is the only app server / environment that has a problem
* with the Spring way of doing things). If you can, use *
*
* @see JmsTextMessageSender for sending JMS messages as it is more robust
* across different platforms
*
* This code will <b>not</b> work in a non-JBoss environment, as the JBoss
* components do not always map to the 'official' components. This code is also
* very hacky (stable enough for production use, but hard to understand). The
* basic problem is that (a) the JBoss JMS implementation differs ever so
* slightly from the JMS standard and (b) there is a classloading issue when you
* try to compile and run <b>directly</b> against the JBoss implmentation.
*
* A couple of workarounds were tried , including the use of anothe library
* (such as OpenJMS or ActiveMQ). This solves the original problem, but creates
* difficulites elsewhere (e.g. how does ActiveMQ find the JBoss Queue?). This
* solution uses reflection: We do not need to JBoss types at runtime, but can
* defer finding out what they are until 'after' we deploy. The downside is that
* we lose strong typing (e.g. everything becomes java.lang.object).
*
* On a positive note what makes this solution 'less bad': 1) We provide an
* alternative in the shape of
* @see JmsTextMessageSender 2) All the ugly bits are within <b>private</b>
* methods of the one class 3) If / When we get a solution it is an easy
* fix - put together a class implementing ITestMessageSender and then
* change the Spring config file to use that instead. 4) The incoming
* messaging handling should not be a problem - we can just write a message
* driven EJB (no need to write JMS related code)
*
* @todo - resolve the issue above
* @todo - optimisation of this class if required for performance
*
* This project uses Apache, Spring, JBoss and other GPL Licenced Code
*
* @author Paul Browne
*/
public class JBossTextMessageSender implements ITestMessageSender {
private Log log = LogFactory.getLog(getClass());
// Properties we set
private String queueName = null;
private String serverUrl = null;
private String namingContextFactory = null;
private String namingUrlPackages = null;
/**
* Due to a combination of classloading issues a non standard implementation
* of JMS on JBoss , we invoke the methods on a number of the classes below
* using reflection - ugly , but it works
*/
public void sendMessage(String messageText) throws RpException {
log.debug("Queue name is " + queueName);
// Objects that we must deal with using reflection
Object queueConnectionFactory = null; // Should be
// javax.jms.QueueConnectionFactory
InitialContext jndiContext = null;
Object queueConnection = null; // should be javax.jms.QueueConnection
Object queueSession = null; // should be javax.jms.QueueSession
Object queueSender = null; // should be javax.jms.QueueSender;
Object queue = null; // should be //javax.jms.Queue;
Object message = null; // TextMessage
/*
* Create JNDI Initial Context
*/
try {
Hashtable env = new Hashtable();
env.put("java.naming.factory.initial", namingContextFactory);
env.put("java.naming.provider.url", serverUrl);
env.put("java.naming.factory.url.pkgs", namingUrlPackages);
jndiContext = new InitialContext(env);
} catch (NamingException e) {
log.debug("Could not create JNDI API " + "context: ", e);
throw new RpException(e);
}
/*
* Get queue connection factory and queue objects from JNDI context.
*/
try {
queueConnectionFactory = jndiContext
.lookup("UIL2ConnectionFactory");
queue = jndiContext.lookup(queueName);
} catch (NamingException e) {
log.debug("JNDI API lookup failed: ", e);
throw new RpException(e);
}
/*
* Create connection, session, sender objects. Send the message. Cleanup
* JMS connection.
*/
try {
queueConnection = getQueueConnection(queueConnectionFactory);
queueSession = createQueueSession(queueConnection);
queueSender = createQueueSender(queueSession, queue);
message = createTextMessage(messageText, queueSession);
log.debug("Sending message: " + messageText);
sendMessageText(queueSender, message);
} finally {
if (queueConnection != null) {
try {
invokeCloseOnConnection(queueConnection);
} catch (RpException e) {
// Ignore - we can do nothign about this
}
}
}
}
private Object createTextMessage(String message, Object qSession)
throws RpException {
// qSession is a QueueSession
// we want to invoke create createTextMessage (string)
Object returnObject = null;
log.debug("Incoming object is null:" + (qSession == null));
// Create Parameter Away (Method
Class[] paramMethodArray = new Class[1];
paramMethodArray[0] = String.class;
// Create Param Array (invoke)
Object[] invokeMethodArray = new Object[1];
invokeMethodArray[0] = message;
try {
// Create the Text Method
Method tmpMethod = qSession.getClass().getMethod(
"createTextMessage", paramMethodArray);
log.debug("tmpMethod:" + tmpMethod);
returnObject = tmpMethod.invoke(qSession, invokeMethodArray);
} catch (IllegalAccessException iae) {
log.warn(iae);
throw new RpException(iae);
} catch (InvocationTargetException ite) {
log.warn(ite);
throw new RpException(ite);
} catch (NoSuchMethodException nsme) {
log.warn(nsme);
throw new RpException(nsme);
}
return returnObject;
}
private void sendMessageText(Object qSender, Object message)
throws RpException {
// queueSender.send(message);
// queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
log.debug("Incoming object is null:" + (qSender == null));
// Create Parameter Away (Method
Class[] paramMethodArray = new Class[1];
paramMethodArray[0] = javax.jms.Message.class;
// Create Param Array (invoke)
Object[] invokeMethodArray = new Object[1];
invokeMethodArray[0] = message;
try {
// Loop and find the method we are looking for
Method tmpMethod = null;
Method[] methods = qSender.getClass().getMethods();
for (int a = 0; a < methods.length; a++) {
// log.debug("Method:" + a + " " + methods[a]);
if (methods[a].toString().indexOf("send") > -1) {
log.debug("match found");
tmpMethod = methods[a];
}
}
// Carry out the method call
if (tmpMethod != null) {
log.debug("tmpMethod:" + tmpMethod);
tmpMethod.invoke(qSender, invokeMethodArray);
} else {
throw new RpException("No createSenderMethod found");
}
} catch (IllegalAccessException iae) {
log.warn(iae);
throw new RpException(iae);
} catch (InvocationTargetException ite) {
log.warn(ite);
throw new RpException(ite);
}
}
private void invokeCloseOnConnection(Object queueConnection)
throws RpException {
// queueConnection.close();
log.debug("Incoming object is null:" + (queueConnection == null));
try {
Method tmpMethod = queueConnection.getClass().getMethod("close",
new Class[0]);
log.debug("tmpMethod:" + tmpMethod);
tmpMethod.invoke(queueConnection, new Object[0]);
} catch (IllegalAccessException iae) {
log.debug(iae);
throw new RpException(iae);
} catch (InvocationTargetException ite) {
log.debug(ite);
throw new RpException(ite);
} catch (NoSuchMethodException nsme) {
log.debug(nsme);
throw new RpException(nsme);
}
}
private Object createQueueSession(Object queueConnection)
throws RpException {
// should return QueueSession
// queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
Object returnObject = null;
log.debug("Incoming object is null:" + (queueConnection == null));
// Create Parameter Away (Method
Class[] paramMethodArray = new Class[2];
paramMethodArray[0] = boolean.class;
paramMethodArray[1] = int.class;
// Create Param Array (invoke)
Object[] invokeMethodArray = new Object[2];
invokeMethodArray[0] = Boolean.FALSE;
invokeMethodArray[1] = javax.jms.Session.AUTO_ACKNOWLEDGE;
try {
Method tmpMethod = queueConnection.getClass().getMethod(
"createQueueSession", paramMethodArray);
log.debug("tmpMethod:" + tmpMethod);
returnObject = tmpMethod.invoke(queueConnection, invokeMethodArray);
} catch (IllegalAccessException iae) {
log.warn(iae);
throw new RpException(iae);
} catch (InvocationTargetException ite) {
log.warn(ite);
throw new RpException(ite);
} catch (NoSuchMethodException nsme) {
log.warn(nsme);
throw new RpException(nsme);
}
return returnObject;
}
/**
* We do it this way , so we don't have to risk a class cast (causing
* problems on JBoss)
*
* @param qSession
* @param queue
* @return
*/
private Object createQueueSender(Object qSession, Object queue)
throws RpException {
Object returnObject = null;
log.debug("Incoming QueueSession is null:" + (qSession == null));
log.debug("Incoming queue is null:" + (queue == null));
// Create Param Array (invoke)
Object[] invokeMethodArray = new Object[1];
invokeMethodArray[0] = queue;
try {
// Loop and find the method we are looking for
Method tmpMethod = null;
Method[] methods = qSession.getClass().getMethods();
for (int a = 0; a < methods.length; a++) {
if (methods[a].toString().indexOf("createSender") > -1) {
tmpMethod = methods[a];
log.debug("match found" + tmpMethod);
}
}
// Carry out the method call
if (tmpMethod != null) {
log.debug("using method:" + tmpMethod);
returnObject = tmpMethod.invoke(qSession, invokeMethodArray);
} else {
throw new RpException("No createSenderMethod found");
}
} catch (IllegalAccessException iae) {
log.debug(iae);
throw new RpException(iae);
} catch (InvocationTargetException ite) {
log.debug(ite);
throw new RpException(ite);
}
return returnObject;
}
/**
* Get the QueueConnection on an object using reflection
*
* @param QueueConnection ,
* passed in as an object
* @return
*/
private Object getQueueConnection(Object queueConnection)
throws RpException {
Object returnObject = null;
log.debug("Incoming object is null:" + (queueConnection == null));
try {
Method tmpMethod = queueConnection.getClass().getMethod(
"createQueueConnection", new Class[0]);
log.debug("tmpMethod:" + tmpMethod);
returnObject = tmpMethod.invoke(queueConnection, new Object[0]);
} catch (IllegalAccessException iae) {
log.debug(iae);
throw new RpException(iae);
} catch (InvocationTargetException ite) {
log.debug(ite);
throw new RpException(ite);
} catch (NoSuchMethodException nsme) {
log.debug(nsme);
throw new RpException(nsme);
}
return returnObject;
}
/**
* @param queueName
* The queueName to set.
*/
public void setQueueName(String queueName) {
this.queueName = queueName;
}
/**
* @return Returns the queueName.
*/
public String getQueueName() {
return queueName;
}
/**
* @param serverUrl
* The serverUrl to set.
*/
public void setServerUrl(String serverUrl) {
this.serverUrl = serverUrl;
}
/**
* @return Returns the serverUrl.
*/
public String getServerUrl() {
return serverUrl;
}
/**
* @param namingContextFactory
* The namingContextFactory to set.
*/
public void setNamingContextFactory(String namingContextFactory) {
this.namingContextFactory = namingContextFactory;
}
/**
* @return Returns the namingContextFactory.
*/
public String getNamingContextFactory() {
return namingContextFactory;
}
/**
* @param namingUrlPackages
* The namingUrlPackages to set.
*/
public void setNamingUrlPackages(String namingUrlPackages) {
this.namingUrlPackages = namingUrlPackages;
}
/**
* @return Returns the namingUrlPackages.
*/
public String getNamingUrlPackages() {
return namingUrlPackages;
}
}