I have a solution that works. It is not the best solution but it does work extremely well.
What we have done is to create a very simple ActivationSpecWrapper class to extend the IBM com.ibm.mq.connector.inbound.ActivationSpecImpl class. This wrapper class has one public set/get property (asJNDI). The purpose if the class is to read via JNDI context the Properties class defined in the App server that contains all the properties to be assigned in the activation of the MDB.
First, create the new ActivationSpecWrapper class. you can put this in any package of your choosing.
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.InitialContext;
import com.ibm.mq.connector.inbound.ActivationSpecImpl;
public class ActivationSpecWrapper extends ActivationSpecImpl
{
private static final long serialVersionUID = -529716553593856979L;
private static final String sourceClass = ActivationSpecWrapper.class.getName();
private static final Logger log = Logger.getLogger(sourceClass);
private String asJNDI = null;
public void setAsJNDI(String asJNDI)
{
log.config("asJNDI = " + asJNDI);
this.asJNDI = asJNDI;
try
{
final InitialContext ctx = new InitialContext();
final Properties properties = (Properties) ctx.lookup(asJNDI);
for (final Object key : properties.keySet())
{
try
{
final String value = properties.getProperty((String) key);
final Object field = getSetter((String) key);
if (field != null)
{
if (field instanceof Field)
{
log.fine("Setting " + key + " via Field " + (String) key + " = " + value);
((Field) field).set(this, value);
}
else
{
log.fine("Setting " + key + " via Method " + (String) key + " = " + value);
((Method) field).invoke(this, value);
}
log.config(key + " = " + value);
}
else
{
log.warning("Invalid ActivationSpec Field: " + key);
}
}
catch (final NoSuchFieldException e)
{
log.throwing(sourceClass, "setAsJNDI", e);
}
}
}
catch (final Exception e)
{
log.log(Level.SEVERE, "Error looking up " + asJNDI, e);
return;
}
}
public String getAsJNDI()
{
return asJNDI;
}
private static Object getField(String fieldName) throws NoSuchFieldException
{
return ActivationSpecWrapper.class.getField(fieldName);
}
private static Object getSetter(String fieldName) throws NoSuchFieldException
{
try
{
final StringBuilder sb = new StringBuilder(fieldName.length() + 3).append("set").append(fieldName);
sb.setCharAt(3, Character.toUpperCase(sb.charAt(3)));
return ActivationSpecWrapper.class.getMethod(sb.toString(), String.class);
}
catch (final NoSuchMethodException e)
{
return getField(fieldName);
}
}
}
To implement the class you just need to modify the META-INF/ra.xml file inside the wmq.jmsra.rar file. Change the one occurrence of the ActivationSpecImpl class to your class. This will be your new incoming connection factory's ActivationSpecWrapper class that it uses. So now your wrapper class can look to the app server for the properties to use.
I do this as follows:
: jar -xvf wmq.jmsra.rar META-INF/ra.xml
: perl -pi -e 's/com\.ibm\.mq\.connector\.inbound\.ActivationSpecImpl/your.new.package.ActivatonSpecWrapper/g' META-INF/ra.xml
: jar -uvf wmq.jmsra.rar META-INF/ra.xml
Before modifying the META-INF/ra.xml looks like:
<activationspec>
<activationspec-class>
com.ibm.mq.connector.inbound.ActivationSpecImpl
</activationspec-class>
<required-config-property>
<config-property-name>destination</config-property-name>
</required-config-property>
<required-config-property>
<config-property-name>destinationType</config-property-name>
</required-config-property>
</activationspec>
After the change, the META-INF/ra.xml should like like:
<activationspec>
<activationspec-class>
your.new.package.ActivatonSpecWrapper
</activationspec-class>
<required-config-property>
<config-property-name>destination</config-property-name>
</required-config-property>
<required-config-property>
<config-property-name>destinationType</config-property-name>
</required-config-property>
</activationspec>
Now you will need to add your new package to the RAR file. It should be in standard directory structure. like this:
: jar -uvf wmq.jmsra.rar your/new/package/ActivationSpecWrapper.class
The problem stems from IBM placing the host/port/queue manager/channel (etc.) into the activation spec instead of the administration object. It belongs in the administration object since that is the connection factory for MDB queues. IBM only allows two properties there.
Also if you are using glassfish, oracle really botched things up for MDB classes that need resource adapters, because the glassfish @MessageDriven annotation assumes the app containers default resource adapter (OpenMQ) for JMS. This means the vendor specific ActivationSpecImpl does not work, and thus IMB's custom parameters for host/port and other activation config properties are not supported via annotations until after the resource adaptor is switch via the glassfish-ejb-jar.xml.
JBoss allows for the @ResourceAdapter annotation to change the resource adapter but Glassfish only allows this via the glassfish-ejb-jar.xml file. And when this is used, you only need to annotate your MDB with three activation config properties (the destinationType). Everything else you will place in your JNDI published Properties.
The glassfish-ejb-jar.xml should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<glassfish-ejb-jar>
<enterprise-beans >
<unique-id>1</unique-id>
<ejb>
<ejb-name>MyMDB</ejb-name>
<mdb-resource-adapter>
<resource-adapter-mid>wmq.jmsra</resource-adapter-mid>
<activation-config>
<activation-config-property>
<activation-config-property-name>asJNDI</activation-config-property-name>
<activation-config-property-value>mq/InboundMessages</activation-config-property-value>
</activation-config-property>
</activation-config>
</mdb-resource-adapter>
</ejb>
</enterprise-beans>
</glassfish-ejb-jar>
The MDB @MessageDriven annotation will look something like this:
@MessageDriven(activationConfig =
{
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName = "destination", propertyValue = "jms/InboundMessage_queue"),
@ActivationConfigProperty(propertyName = "useJNDI", propertyValue = "true") })
public class MyMDB implement MessageListener
{
public void onMessage(Message message)
{
// message handler code goes here...
}
}
The last step to make this work, is to add the mq/InboundMessages properties to JDNI, to define the factory properties for the MQ listener resource. This is how it is defined in the domain.xml file:
<custom-resource res-type="java.util.Properties" jndi-name="mq/InboundMessages" factory-class="org.glassfish.resources.custom.factory.PropertiesFactory">
<property name="hostName" value="mqserver"></property>
<property name="port" value="1422"></property>
<property name="queueManager" value="MQMNGR"></property>
<property name="channel" value="MQMNGR.SM.S1"></property>
<property name="transportType" value="CLIENT"></property>
</custom-resource>
I hope this helps. This isn't the easiest solution but it is simple enough, and once it has been established, it is very portable, and allows the app server administrator to manage the connection details to the MQ, instead of the developer.