Is it possible to use EHCache replication over JMS with IBM's Websphere MQ 7.0? I have a working sample using ActiveMQ (out of the box) but am not sure how to configure EHCache to work with Websphere MQ.
Below is a sample of my working ehcache.xml and ActiveMQInitialContextFactory configuration for ActiveMQ.
_______________________________ActiveMQ Sample_____________________________
ehcache.xml
<!-- ActiveMQ configuration out of the box -->
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.jms.JMSCacheManagerPeerProviderFactory"
properties="initialContextFactoryName=com.hcsc.contextfactory.ActiveMQContextFactory,
providerURL=tcp://LJGO4RAJ:61616,
replicationTopicConnectionFactoryBindingName=topicConnectionFactory,
getQueueConnectionFactoryBindingName=queueConnectionFactory,
getQueueBindingName=ehcache,
listenToTopic=true,
replicationTopicBindingName=ehcache"
propertySeparator="," />
<cache name="distributedCache" maxEntriesLocalHeap="1000"
eternal="true" overflowToDisk="false">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.jms.JMSCacheReplicatorFactory"
properties="replicateAsynchronously=true,
replicatePuts=true,
replicateUpdates=true,
replicateUpdatesViaCopy=true,
replicateRemovals=true, asynchronousReplicationIntervalMillis=500"
propertySeparator="-" />
</cache>
ActiveMQInitialContextFactory implementation (from ehcache.xml)
public class ActiveMQContextFactory extends ActiveMQInitialContextFactory {
#Override
public Context getInitialContext(Hashtable environment)
throws NamingException {
Map<String, Object> data = new ConcurrentHashMap<String, Object>();
// Configure the Topic connection factory binding name
String factoryBindingName = (String) environment
.get(JMSUtil.TOPIC_CONNECTION_FACTORY_BINDING_NAME);
try {
data.put(factoryBindingName, createConnectionFactory(environment));
} catch (URISyntaxException e) {
throw new NamingException("Error initialisating ConnectionFactory"
+ " with message " + e.getMessage());
}
String topicBindingName = (String) environment
.get(JMSUtil.REPLICATION_TOPIC_BINDING_NAME);
data.put(topicBindingName, createTopic(topicBindingName));
// Configure queue connection factory binding name
String getQueueConnectionfactoryBindingName = (String) environment
.get(JMSUtil.GET_QUEUE_CONNECTION_FACTORY_BINDING_NAME);
if (getQueueConnectionfactoryBindingName != null) {
try {
data.put(getQueueConnectionfactoryBindingName,
createConnectionFactory(environment));
} catch (URISyntaxException e) {
throw new NamingException(
"Error initialisating TopicConnectionFactory with message "
+ e.getMessage());
}
}
String getQueueBindingName = (String) environment
.get(JMSUtil.GET_QUEUE_BINDING_NAME);
if (getQueueBindingName != null) {
data.put(getQueueBindingName, createQueue(getQueueBindingName));
}
return createContext(environment, data);
}
}
The above configuration works perfectly, but I need to convert it to use Websphere MQ. Below is what I have so far...
_______________________________WebsphereMQ Sample__________________________
ehcache.xml
<cacheManagerPeerProviderFactory
class="net.sf.ehcache.distribution.jms.JMSCacheManagerPeerProviderFactory"
properties="initialContextFactoryName=com.hcsc.contextfactory.WebSphereMQContextFactory,
providerURL=tcp://LJGO4RAJ:1415,
replicationTopicConnectionFactoryBindingName=topicConnectionFactory,
getQueueConnectionFactoryBindingName=queueConnectionFactory,
getQueueBindingName=myqueue,
listenToTopic=true,
replicationTopicBindingName=ehcache"
propertySeparator="," />
<cache name="distributedCache" maxEntriesLocalHeap="1000"
eternal="true" overflowToDisk="false">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.jms.JMSCacheReplicatorFactory"
properties="replicateAsynchronously=true,
replicatePuts=true,
replicateUpdates=true,
replicateUpdatesViaCopy=true,
replicateRemovals=true,
asynchronousReplicationIntervalMillis=500"
propertySeparator="-" />
</cache>
MQQueueConnectionFactory (I assume I need this similar to ActiveMQContextFactory)
public class WebSphereMQContextFactory extends MQQueueConnectionFactory {
// TODO - NOT SURE HOW TO CONFIGURE THIS FOR EHCACHE?
}
I believe I will need a MQQueueConnectionFactory similar to how I created one for ActiveMQ, am I going in the right direction? I cannot find any sample code to achieve this.
Related
Spring framework 4.1.4
Spring batch 3.0.2
Tomcat 7
Morning,
I have successfully used a configuration based on Marten Deinum's Post
to dynamically connect (or create and connect) to dbs only known at run time.
The problem I am having is the process now intermittently fails to switch between dbs. The first always works but putting in logging code shows the first properly being created and then the second or third not firing dataSource == null section in the MdyDataSourceFactory - it appears if the process thinks it is the same db and so doesn't change but at some point it triggers and connects to a subsequent db!
Running a simple JUnit test switching between dbs works fine (code correctly fires and dbs switch and select statement returns different id results as expected).
It is being called within a Batch Job step tasklet. The process grabs a count of resultSets (summary table) and connects to the db. For each resultSet it calls the db registry and if exists - returns existing or creates a new one and returns.
My dynamic db xml is:
<bean id="mdyDSRegistry" class="com.k12knowledge.db.MdyDataSourceFactory" />
<bean id="mdyDSTargetSource" class="com.k12knowledge.db.ContextSwappableMdyTargetSource">
<constructor-arg type="java.lang.Class">
<value>javax.sql.DataSource</value>
</constructor-arg>
<property name="targetRegistry" ref="mdyDSRegistry"></property>
</bean>
<bean id="proxyMdyDataSource" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<list>
<value>javax.sql.DataSource</value>
</list>
</property>
<property name="targetSource">
<ref bean="mdyDSTargetSource" />
</property>
</bean>
The MdyDataSourceFactory is:
public class MdyDataSourceFactory implements TargetRegistry {
private ConcurrentMap<String, DataSource> map = new ConcurrentHashMap<String, DataSource>();
#Override
public DataSource getTarget(String context) {
IClientDs client = MdyContextHolder.getClientDs();
Assert.notNull(client, "Client was not set.");
String key = client.getUrl();
DataSource dataSource = map.get(key);
if (dataSource == null) {
System.out.println("dataSource == null - creating new");
dataSource = getDataSource(client);
dataSource = map.putIfAbsent(key, dataSource);
if (dataSource == null) {
// put success
dataSource = map.get(key);
}
}
System.out.println("client key: " + key);
return dataSource;
}
private DataSource getDataSource(IClientDs client) {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(client.getDriver());
dataSource.setUrl(client.getUrl());
dataSource.setUsername(client.getUsername());
dataSource.setPassword(client.getPassword());
dataSource.setValidationQuery("/* ping */");
dataSource.setMaxActive(10);
dataSource.setMaxIdle(5);
dataSource.setTestOnBorrow(true);
dataSource.setTestWhileIdle(true);
dataSource.setRemoveAbandoned(true);
dataSource.setRemoveAbandonedTimeout(20);
dataSource.setTimeBetweenEvictionRunsMillis(34000);
dataSource.setMinEvictableIdleTimeMillis(55000);
try {
System.out.println("dataSource " + client.getUrl() + " closed ? " + dataSource.getConnection().isClosed());
} catch (SQLException e) {
e.printStackTrace();
}
return dataSource;
}
}
At one point I thought there was an issue with the key I used (some databases are only different in name by 1 letter). I then tried adding an UUID to the front of the key to see if that was the issue - no change.
Really puzzled as to why it is not working... Any pointers greatly appreciated.
Thank you for looking.
ContextHolder
public abstract class ContextHolder {
private static final ThreadLocal<IClientDs> holder = new ThreadLocal<IClientDs>();
public static void setClientDs(IClientDs context) {
LoggerFactory.getLogger(ContextHolder.class).debug("context set '{}'", context);
holder.set(context);
}
public static IClientDs getClientDs() {
return (IClientDs) holder.get();
}
}
I have written a web service client (using Java Spring and JAXB Marshaller) that works with a 3rd party web service. When I send a valid request everything works well. When I send an invalid request then the web service server responds with a SOAP Fault. The client application just fails with a UnmarshallingFailureException
org.springframework.oxm.UnmarshallingFailureException: JAXB unmarshalling
exception; nested exception is javax.xml.bind.UnmarshalException:
unexpected element (uri:"http://schemas.xmlsoap.org/soap/envelope/", local:"Fault").
Appears to me that my ws client isn't able to decipher the SOAP fault returned by the web service. I wrote a custom FaultMessageResolver, but it doesn't get invoked (I set a breakpoint there but it doesn't hit. The FaultMessageResolver just worked fine before I added the Wss4jSecurityInterceptor for signature, encryption/decryption stuff). Here's the code:
public class VehicleServiceClientExceptionResolver implements FaultMessageResolver {
#Override
public void resolveFault(WebServiceMessage message) throws IOException {
SoapMessage soapMessage = (SoapMessage) message;
try {
JAXBContext context = JAXBContext.newInstance(ErrorMessages.class);
Unmarshaller unMarshaller = context.createUnmarshaller();
ErrorMessages errorMessages = (ErrorMessages)unMarshaller.unmarshal(soapMessage.getSoapBody().getFault().getFaultDetail().getDetailEntries().next().getSource());
if (errorMessages.getErrorMessage().size() > 0) {
throw new VehicleServiceClientException(errorMessages);
}
} catch (JAXBException e) {
LOGGER.debug(e.getMessage());
}
}
}
And this custom soap fault resolver is injected into client side web service template like below:
<bean id="vehicleQuotationWebServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
<constructor-arg ref="messageFactory"/>
<property name="interceptors">
<list>
<ref bean="wsSecurityInterceptor"/>
</list>
</property>
<property name="marshaller" ref="vehicleQuotationMarshaller" />
<property name="unmarshaller" ref="vehicleQuotationMarshaller" />
<property name="messageSender" ref="urlMessageSender"/>
<property name="faultMessageResolver" ref="vehicleServiceClientFaultMessageResolver" />
<property name="defaultUri" value="https://*********/*********Service"/>
</bean>
The most weird thing is although I got that unmarshall exception, I did see the encrypted server response was decrypted in my eclipse console when I change the log level from INFO to DEBUG, I am not sure where this DigesterOutputStream comes from, but I think it might be the key to solve this.
Anyone got any idea? Thanks!
DEBUG p.xml.dsig.internal.DigesterOutputStream:
<soapenv:Body xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Id-af090516-9e00-4590-b481-c78e59d6b2fc"><soapenv:Fault><faultcode>soapenv:Client.Validation</faultcode><faultstring</faultstring><detail><em:ErrorMessages xmlns:em="urn:ford/errormessage/v1.0"><em:ErrorMessage><em:ErrorCode>GLSE903100</em:ErrorCode><em:ErrorDescription> CTT System Quote Id already exists ('1041')</em:ErrorDescription><em:ErrorTime>2014-05-16T15:13:20</em:ErrorTime></em:ErrorMessage></em:ErrorMessages></detail></soapenv:Fault></soapenv:Body>
I found the solution here: Adding a WebServiceMessageExtractor<Object> to:
WebServiceTemplate.sendAndReceive(
new WebServiceMessageCallback(),
new WebServiceMessageExtractor<Object>())
does the trick.
Another solution:
public class ExampleInterceptor implements ClientInterceptor {
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
var resp = (SoapMessage) messageContext.getResponse();
Optional.of(resp)
.filter(res -> !hasFault(res))
.orElseThrow(() -> new SoapFaultClientException(resp));
return true;
}
private boolean hasFault(final WebServiceMessage response) {
return Optional.ofNullable(response)
.filter(resp -> resp instanceof FaultAwareWebServiceMessage)
.map(resp -> (FaultAwareWebServiceMessage) resp)
.map(FaultAwareWebServiceMessage::hasFault)
.orElse(false);
}
}
#Configuration
public class ExampleConnectorConfig extends WSConnectorConfig
#Bean
public WSConnector soapConnector(Jaxb2Marshaller marshaller) {
var client = new WSConnector(messageFactory());
client.setInterceptors(new ClientInterceptor[]{new ExampleInterceptor()});
client.setDefaultUri(proxy);
return client;
}
//Example
#Bean
public SaajSoapMessageFactory messageFactory() {
SaajSoapMessageFactory messageFactory = new SaajSoapMessageFactory();
messageFactory.afterPropertiesSet();
return messageFactory;
}
}
I'm new to J2EE - MDB but I'm trying to create a Message Driven Bean (MDB) that simply listens to a queue (read the messages), then process that message and push it to a different queue. I have found several working examples on Google to achieve these two tasks in a separate fashion, but I've been having issues trying to do them both on the same MDB.
This is the code for my MDB
#MessageDriven(mappedName = "jms/propuestasQ")
public class ObtenerNumPolizaBean implements MessageListener {
#Resource(name="jms/polizasQCF")
private QueueConnectionFactory connectionFactory;
private Connection connection;
#Resource(name = "jms/polizasQ")
private Destination targetQueue;
#PostConstruct
private void initJMS() {
try {
connection = connectionFactory.createConnection();
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
#PreDestroy
private void closeJMS() {
try {
connection.close();
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
/**
* #see MessageListener#onMessage(Message)
*/
#Override
public void onMessage(Message message) {
//validate the received message type
if (message instanceof FolioEntity) {
try {
//generate Web Service proxy
GenerarFoliosImplService serviceGenerarFolios = new GenerarFoliosImplService();
GenerarFoliosImplDelegate delGenerarFolios = serviceGenerarFolios.getGenerarFoliosImplPort();
//call the method with the object
FolioEntity responseFolio = delGenerarFolios.generarFolios((FolioEntity)message);
System.out.println("Bean generated the following FolioNumber: " + responseFolio.getNumeroFolio());
//put the message on the next queue
putMessage(responseFolio);
}
catch (JMSException e) {
throw new RuntimeException(e);
}
}
else {
throw new IllegalArgumentException("Message must be of type FolioEntity");
}
}
private void putMessage(final FolioEntity folio) throws JMSException {
final Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
final MessageProducer producer = session.createProducer(targetQueue);
final ObjectMessage objectMessage = session.createObjectMessage();
producer.send(objectMessage);
session.close();
}
Here is the content of my ejb-jar.xml file
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">
<display-name>MDBRenovarPolizaEJB </display-name>
<enterprise-beans >
<message-driven>
<ejb-name>ObtenerNumPolizaBean</ejb-name>
<message-destination-ref>
<description />
<message-destination-ref-name>
jms/polizasQ
</message-destination-ref-name>
<message-destination-type>
javax.jms.Queue
</message-destination-type>
<message-destination-usage>
ConsumesProduces
</message-destination-usage>
<message-destination-link>
jms/polizasQ
</message-destination-link>
</message-destination-ref>
<message-destination-ref>
<description />
<message-destination-ref-name>
jms/polizasQCF
</message-destination-ref-name>
<message-destination-type>
javax.jms.QueueConnectionFactory
</message-destination-type>
<message-destination-usage>
ConsumesProduces
</message-destination-usage>
<message-destination-link>
jms/polizasQCF
</message-destination-link>
</message-destination-ref>
The issue I'm having is that I can't set the "Message Driven Bean listener bindings" on WAS Console 8.5.5, when I try to set the activation specification I'm getting the error:
MDBRenovarPolizaModelEJB.jar\META-INF\ejb-jar_merged.xml (The system cannot find the file specified.)
I don't know what this exception means. I've always set the "Activation specification" this way to listen to a particular queue, so I have no idea what is this file: "ejb-jar_merged.xml".
Any clue? Thanks in advance.
Or if anyone has a working example to achieve this with step by step to make it work under WebSphere that would be useful.
I just solved similar problem (WAS 8.5.5, but MDB, EJB, servlet - all stuffed into one war module)
It is clearly a bug in WAS. This is workaround:
Ensure Run server with resources on Server
Publish to repeat an error (but this time with resources on server)
Find directory where WAS expects "ejb-jar_merged.xml":
Locate WAS SystemErr.log
There are messages about missing "ejb-jar_merged.xml".
You are looking for directory name of missing file.
Goto to the found directory.
(something like .IBM/WebSphere/AppServer/profiles/AppSrv01/wstemp/0/workspace/... ....deployments /.....-INF/
copy ejb-jar.xml ejb-jar_merged.xml
In case of missing web_merged.xml just copy web.xml into web_merged.xml.
The problem will not appear again at application updates, it has to be reapplied sometimes after app remove/install.
I'm new in MDB and EE. Please tell me there is i'm wrong.
My app must interaction with Websphere MQ (wait a messaege in queue, do something and reply).
I'm using NetBeans 7.3 ,GlassFish 3.1, Websphere MQ 6.2, resorce adapter wmq.jmsra.rar. Interaction must be not in jms format is, only Web MQ nature.
I'm deploy adapter and create Connecton pool and Administrated Object.
In domain.xml
<connector-connection-pool description="" name="cpMqAdapter" resource-adapter-name="wmq.jmsra" connection-definition-name="javax.jms.QueueConnectionFactory" transaction-support="LocalTransaction">
<property name="port" value="1414"></property>
<property name="CCSID" value="866"></property>
<property name="hostName" value="192.168.0.11"></property>
<property name="queueManager" value="QM"></property>
<property name="channel" value="SrvConn"></property>
<property description="CLIENT - mq on other computer" name="transportType" value="CLIENT"></property>
</connector-connection-pool>
<admin-object-resource enabled="false" res-adapter="wmq.jmsra" res-type="javax.jms.Queue" description="" jndi-name="wmqJmsAOR" class-name="com.ibm.mq.connector.outbound.MQQueueProxy">
<property name="priority" value="APP"></property>
<property name="failIfQuiesce" value="true"></property>
<property name="baseQueueManagerName" value="QM"></property>
<property name="CCSID" value="1208"></property>
<property name="persistence" value="APP"></property>
<property name="encoding" value="NATIVE"></property>
<property name="baseQueueName" value="TEST"></property>
<property name="targetClient" value="MQ"></property>
<property name="expiry" value="APP"></property>
</admin-object-resource>
'
In netbeans i'm create EE project and message driven bean. i'm gett this code '
#MessageDriven(mappedName = "wmqJmsAOR", activationConfig = {
#ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-
acknowledge"),
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")
})
public class NewMessage implements MessageListener {
public NewMessage() {
super();
System.out.println("I created");
}
#Override
public void onMessage(Message message) {
System.out.println("I'm getting message");
}
`
Please tell me why this MDB is not listenig a queue (i'm put test message in Websphere MQ console). May be i'm must write something in config (now project as default netbeans created).
Alexei
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.
Is there anything that can achieve the equivalent of the below:
<import resource="a.xml">
<prop name="key" value="a"/>
</import>
<import resource="a.xml">
<prop name="key" value="b"/>
</import>
Such that the beans defined in resouce a would see the property key with two different values? The intention would be that this would be used to name the beans in the imports such that resource a.xml would appear:
<bean id="${key}"/>
And hence the application would have two beans named a and b now available with the same definition but as distinct instances. I know about prototype scope; it is not intended for this reason, there will be many objects created with interdepednencies that are not actually prototypes. Currently I am simply copying a.xml, creating b.xml and renaming all the beans using the equivalent of a sed command. I feel there must be a better way.
I suppose that PropertyPlaceholderConfigurers work on a per container basis, so you can't achieve this with xml imports.
Re The application would have two beans named a and b now available with the same definition but as distinct instances
I think you should consider creating additional application contexts(ClassPathXmlApplicationContext for example) manually, using your current application context as the parent application context.
So your many objects created with interdependencies sets will reside in its own container each.
However, in this case you will not be able to reference b-beans from a-container.
update you can postprocess the bean definitions(add new ones) manually by registering a BeanDefinitionRegistryPostProcessor specialized bean, but this solution also does not seem to be easy.
OK, here's my rough attempt to import xml file manually:
disclaimer: I'm very bad java io programmer actually so double check the resource related code :-)
public class CustomXmlImporter implements BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
private Map<String, String> properties;
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
public Map<String, String> getProperties() {
return properties;
}
private void readXml(XmlBeanDefinitionReader reader) {
InputStream inputStream;
try {
inputStream = new ClassPathResource(this.classpathXmlLocation).getInputStream();
} catch (IOException e1) {
throw new AssertionError();
}
try {
Scanner sc = new Scanner(inputStream);
try {
sc.useDelimiter("\\A");
if (!sc.hasNext())
throw new AssertionError();
String entireXml = sc.next();
PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("${",
"}", null, false);
Properties props = new Properties();
props.putAll(this.properties);
String newXml = helper.replacePlaceholders(entireXml, props);
reader.loadBeanDefinitions(new ByteArrayResource(newXml.getBytes()));
} finally {
sc.close();
}
} finally {
try {
inputStream.close();
} catch (IOException e) {
throw new AssertionError();
}
}
}
private String classpathXmlLocation;
public void setClassPathXmlLocation(String classpathXmlLocation) {
this.classpathXmlLocation = classpathXmlLocation;
}
public String getClassPathXmlLocation() {
return this.classpathXmlLocation;
}
#Override
public void postProcessBeanDefinitionRegistry(
BeanDefinitionRegistry registry) throws BeansException {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
readXml(reader);
}
}
XML configuration:
<bean class="CustomXmlImporter">
<property name="classPathXmlLocation" value="a.xml" />
<property name="properties">
<map>
<entry key="key" value="a" />
</map>
</property>
</bean>
<bean class="CustomXmlImporter">
<property name="classPathXmlLocation" value="a.xml" />
<property name="properties">
<map>
<entry key="key" value="b" />
</map>
</property>
</bean>
this code loads the resources from classpath. I would think twice before doing something like that, anyway, you can use this as a starting point.