MQ Queue transaction not rolled back in a 2 phase transaction - java

I have an EJB timer (EJB 2.1) which has bean managed transaction.
The timer code calls a business method which deals with 2 resources in a single transaction. One is database and other one is MQ queue server.
Application server used is Websphere Application Server 7 (WAS). In order to ensure consistency across 2 resources (database and queue manager), we have enabled the option to support 2 phase commit in WAS. This is to ensure that in case of any exception during database operation, message posted in queue is rolled back along with database rollback and vice versa.
Below is the flow explained:
When timeout occurs in Timer code, startProcess() in DirectProcessor is called which is our business method. This method has a try block within which there is a method call to createPostXMLMessage() in the same class. This in turn has a call to another method postMessage() in class PostMsg.
The issue is when we encounter any database exception in createPostXMLMessage() method, the message posted earlier does not roll back although database part is successfully rolled back. Please help.
In ejb-jar.xml
<session id="Transmit">
<ejb-name>Transmit</ejb-name>
<home>com.TransmitHome</home>
<remote>com.Transmit</remote>
<ejb-class>com.TransmitBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Bean</transaction-type>
</session>
public class TransmitBean implements javax.ejb.SessionBean, javax.ejb.TimedObject {
public void ejbTimeout(Timer arg0) {
....
new DIRECTProcessor().startProcess(mySessionCtx);
}
}
public class DIRECTProcessor {
public String startProcess(javax.ejb.SessionContext mySessionCtx) {
....
UserTransaction ut= null;
ut = mySessionCtx.getUserTransaction();
try {
ut.begin();
createPostXMLMessage(interfaceObj, btch_id, dpId, errInd);
ut.commit();
}
catch (Exception e) {
ut.rollback();
ut=null;
}
}
public void createPostXMLMessage(ArrayList<InstrInterface> arr_instrObj, String batchId, String dpId,int errInd) throws Exception {
...
PostMsg pm = new PostMsg();
try {
pm.postMessage( q_name, final_msg.toString());
// database update operations using jdbc
}
catch (Exception e) {
throw e;
}
}
}
public class PostMsg {
public String postMessage(String qName, String message) throws Exception {
QueueConnectionFactory qcf = null;
Queue que = null;
QueueSession qSess = null;
QueueConnection qConn = null;
QueueSender qSender = null;
que = ServiceLocator.getInstance().getQ(qName);
try {
qConn = (QueueConnection) qcf.createQueueConnection(
Constants.QCONN_USER, Constants.QCONN_PSWD);
qSess = qConn.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
qSender = qSess.createSender(que);
TextMessage txt = qSess.createTextMessage();
txt.setJMSDestination(que);
txt.setText(message);
qSender.send(txt);
} catch (Exception e) {
retval = Constants.ERROR;
e.printStackTrace();
throw e;
} finally {
closeQSender(qSender);
closeQSession(qSess);
closeQConn(qConn);
}
return retval;
}
}

Related

Jersey SSE - eventOutput.write throws nullpointer after first message is sent

I have implemented a Restful web interface using Jersey for sending messages received from an internal JMS publisher to external clients via HTTP. I have managed to get a test message out to a Java client, but the Thread throws a null pointer exception before completing the write() execution, closing the connection and preventing further communication.
Here is my resource class:
#GET
#Path("/stream_data")
#Produces(SseFeature.SERVER_SENT_EVENTS)
public EventOutput getServerSentEvents(#Context ServletContext context){
final EventOutput eventOutput = new EventOutput();
new Thread( new ObserverThread(eventOutput, (MService) context.getAttribute("instance")) ).start();
return eventOutput;
}
And here is my thread's run method:
public class ObserverThread implements Observer, Runnable {
//constructor sets eventOutput & mService objects
//mService notifyObservers() called when JMS message received
//text added to Thread's message queue to await sending to client
public void run() {
try {
String message = "{'symbol':'test','entryType'='0','price'='test'}";
Thread.sleep(1000);
OutboundEvent.Builder builder = new OutboundEvent.Builder();
builder.mediaType(MediaType.APPLICATION_JSON_TYPE);
builder.data(String.class, message);
OutboundEvent event = builder.build();
eventOutput.write(event);
System.out.println(">>>>>>SSE CLIENT HAS BEEN REGISTERED!");
mService.addObserver(this);
while(!eventOutput.isClosed()){
if(!updatesQ.isEmpty()){
pushUpdate(updatesQ.dequeue());
}
}
System.out.println("<<<<<<<SSE CLIENT HAS BEEN DEREGISTERED!");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Here is my client code:
Client client = ClientBuilder.newBuilder().register(SseFeature.class).build();
WebTarget target = client.target(url);
EventInput eventInput = target.request().get(EventInput.class);
try {
while (!eventInput.isClosed()) {
eventInput.setChunkType(MediaType.WILDCARD_TYPE);
final InboundEvent inboundEvent = eventInput.read();
if (inboundEvent != null) {
String theString = inboundEvent.readData();
System.out.println(theString + "\n");
}
}
} catch (Exception e) {
e.printStackTrace();
}
I am getting the "{'symbol':'test','entryType'='0','price'='test'}" test message printed to the client console, but the server then prints a NullPointerException before it can print the ">>>>SSE Client registered" message. This closes the connection so the client exits the while loop and stops listening for updates.
I converted the project to a webapp 3.0 version facet in order to add an async-supported tag to the web.xml but i am receiving the same null pointer error. I am inclined to think that it is caused by the servlet ending the Request/Response objects once the first message is returned, evidence is shown in the stack trace:
Exception in thread "Thread-20" java.lang.NullPointerException
at org.apache.coyote.http11.InternalOutputBuffer.realWriteBytes(InternalOutputBuffer.java:741)
at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:434)
at org.apache.coyote.http11.InternalOutputBuffer.flush(InternalOutputBuffer.java:299)
at org.apache.coyote.http11.Http11Processor.action(Http11Processor.java:981)
at org.apache.coyote.Response.action(Response.java:183)
at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:314)
at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:288)
at org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:98)
at org.glassfish.jersey.message.internal.CommittingOutputStream.flush(CommittingOutputStream.java:292)
at org.glassfish.jersey.server.ChunkedOutput$1.call(ChunkedOutput.java:241)
at org.glassfish.jersey.server.ChunkedOutput$1.call(ChunkedOutput.java:192)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:242)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:345)
at org.glassfish.jersey.server.ChunkedOutput.flushQueue(ChunkedOutput.java:192)
at org.glassfish.jersey.server.ChunkedOutput.write(ChunkedOutput.java:182)
at com.bpc.services.service.ObserverThread.run(MarketObserverThread.java:32)
at java.lang.Thread.run(Thread.java:745)
<<<<<<<SSE CLIENT HAS BEEN DEREGISTERED!
I have attempted to test an sse broadcaster as well. In this case I am not seeing any exceptions thrown, but the connection is closed once the first message has been received, leading me to believe it is something in the servlet forcing the connection to close. Can anyone advise me on how to debug this on the server-side?
I had a similar issue from what seems to be a long standing bug in Jersey's #Context injection for ExecutorService instances. In their current implementation of Sse (version 2.27),
class JerseySse implements Sse {
#Context
private ExecutorService executorService;
#Override
public OutboundSseEvent.Builder newEventBuilder() {
return new OutboundEvent.Builder();
}
#Override
public SseBroadcaster newBroadcaster() {
return new JerseySseBroadcaster(executorService);
}
}
the executorService field is never initialized, so the JerseySseBroadcaster raises a NullPointerException in my case. I worked around the bug by explicitly triggering the injection.
If you're using HK2 for CDI (Jersey's default), a rough sketch of a solution to the question above could look similar to the following:
#Singleton
#Path("...")
public class JmsPublisher {
private Sse sse;
private SseBroadcaster broadcaster;
private final ExecutorService executor;
private final BlockingQueue<String> jmsMessageQueue;
...
#Context
public void setSse(Sse sse, ServiceLocator locator) {
locator.inject(sse); // Inject sse.executorService
this.sse = sse;
this.broadcaster = sse.newBroadcaster();
}
...
#GET
#Path("/stream_data")
#Produces(MediaType.SERVER_SENT_EVENTS)
public void register(SseEventSink eventSink) {
broadcaster.register(eventSink);
}
...
#PostConstruct
private void postConstruct() {
executor.submit(() -> {
try {
while(true) {
String message = jmsMessageQueue.take();
broadcaster.broadcast(sse.newEventBuilder()
.mediaType(MediaType.APPLICATION_JSON_TYPE)
.data(String.class, message)
.build());
}
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
#PreDestroy
private void preDestroy() {
executor.shutdownNow();
}
}

Problems with routing message to separate errorQueue

I have a MessageBean which reads from a Queue we'll name MainQ.
If the execution of the onMessage code throws a user-based Exception with a type we'll name UserException I want to catch this and put this message on a separate Queue named UserErrorQ.
If the exception is not of this type, the Exception is thrown on to be handled by the DMQ.
Here is my issue:
in my catch block I attempt, through a ErrorQueueHandler, to put this new message on the UserErrorQ. This results in an error when I attempt to connect to the connectionFactory to send the message to the UserErrorQ.
Apparently creating a new connection to a QueueConnectionFactory(javax.jms.ConnectionFactory) is causing problems
Error:
com.sun.messaging.jms.JMSException: MQRA:DCF:allocation failure:createConnection:Error in allocating a connection. Cause: javax.transaction.RollbackException
at com.sun.messaging.jms.ra.DirectConnectionFactory._allocateConnection(DirectConnectionFactory.java:548)
at com.sun.messaging.jms.ra.DirectConnectionFactory.createConnection(DirectConnectionFactory.java:265)
at com.sun.messaging.jms.ra.DirectConnectionFactory.createConnection(DirectConnectionFactory.java:244)`
MessageBean:
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void onMessage(Message message) {
try{
.
.
}catch(Exception e){
if(isUserExceptionWrappedInException(e){
errorQueueHandler.sendToErrorQueue(message);
}
}
}
private boolean isUserExceptionWrappedInException(Throwable t) {
if (t == null)
return false;
else if (t instanceof UserException)
return true;
else
return isUserExceptionWrappedInException(t.getCause());
}
ErrorQueueHandler:
public void sendToErrorQueue(Message message) {
try {
createConnection();
send((TextMessage)message);
} finally {
closeConnection();
}
}
private void createConnection() throws Exception {
try {
connection = connectionfactory.createConnection();
connection.start();
} catch (JMSException e) {
String msg = "Error while attempting to initialize connection to jms destination " + ERROR_QUEUE;
throw new OperationalException(msg, e, OperationalExceptionType.APPLIKASJONSTJENER);
}
}
As mentioned, the error occurs when attempting to make the connection. Anyone have a fix for this?
So, I have figured out the answer to my own question.
The reason for the connectionException was that the ErrorQueueHandler was not an EJB, but rather injected via CDI. There are no new instantiations allowed within a rollback state because the container discards the bean instance, which is why it failed. My REQUIRES_NEW annotation was also ignored as this belongs to the javax api, which will not affect a CDI injected bean.
Here are a few things to note:
Make sure the EJB has either no constructors, or public ones. The modifiers are important as the container needs these to be correct for it to instantiate the EJB.
There are a few problems with this approach.
As I am attempting to write the message to a separate error queue instead of the DMQ, I will have to consume the message and not throw the error on afterwards. Because the MDB is in a rollback state, the JMS spec clearly states that this will cause the message to be redelivered. What you will experience is that after writing to you custom errorQueue, the message will bounce right back to the queue and you now have an infinite loop.
Luckily i also have a solution:
The main issue here is controlling your transactions. For this scenario, i need 3 transactions:
One transaction for the MDB so that it is able to acknowledge the message event though i have a RuntimeException.
One transaction for the logic of the onMessage method so that i am able to do a rollback when i get an exception, but also still be able to write to the ErrorQueue.
One transaction for connecting and writing to the ErrorQueue while in a rollback state.
Code:
MessageBean:
#EJB
QueueService queueService;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void onMessage(Message message) {
try{
queueService.processMessageInNewTrasaction(message);
}catch(Exception e){
throw e;
}
}
QueueService:
import javax.jms.Message;
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
#Stateless
public class QueueService {
#EJB
ErrorQueueHandler errorQueueHandler;
public void processMessageInNewTransaction(Message message){
try {
.
.
} catch(Exception e) {
if(isUserExceptionWrappedInException(e)
errorQueueHandler.sendToErrorQueue(message);
}
}
private boolean isUserExceptionWrappedInException(Throwable t) {
if (t == null)
return false;
else if (t instanceof UserException)
return true;
else
return isUserExceptionWrappedInException(t.getCause());
}
}
ErrorQueueHandler:
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
#Stateless
public class ErrorQueueHandler{
public void sendToErrorQueue(Message message){
.
.
}
}
useful resource: http://weblogic-wonders.com/weblogic/2011/01/10/working-with-jms-and-the-standard-issues-in-jms/

how to use JPA life-cycle events to get entity data

I have a RESTful API that makes use of an entity class annotated with #EntityListners. And in the EntityListner.java, I have a method annotated with #PostPersist. So, when that event fires, I want to extract all the information regarding the entity that just got persisted to the database. But when I try to do that, Glassfish is generating an exception and the method in EntityListner class is not executing as expected. Here is the code
public class EntityListner {
private final static String QUEUE_NAME = "customer";
#PostUpdate
#PostPersist
public void notifyOther(Customer entity){
CustomerFacadeREST custFacade = new CustomerFacadeREST();
Integer customerId = entity.getCustomerId();
String custData = custFacade.find(customerId).toString();
String successMessage = "Entity added to server";
try{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// channel.basicPublish("", QUEUE_NAME, null, successMessage .getBytes());
channel.basicPublish("", QUEUE_NAME, null, custData.getBytes());
channel.close();
connection.close();
}
catch(IOException ex){
}
finally{
}
}
}
If I send the commented out successMessage message instead of custData, everything works fine.
http://www.objectdb.com/java/jpa/persistence/event says the following regarding the entity lifecycle methods, and I am wondering if that is the situation here.
To avoid conflicts with the original database operation that fires the entity lifecycle event (which is still in progress) callback methods should not call EntityMan­ager or Query methods and should not access any other entity objects
Any ideas?
As that paragraph says, the standard does not support calling entity manager methods from inside entity listeners. I strongly recommend building custData from the persisted entity, as Heiko Rupp says in his answer. If that is not feasible, consider:
notifying asynchronously. I do not really recommend this as it probably depends on timing to work properly:
public class EntityListener {
private final static String QUEUE_NAME = "customer";
private ScheduledExecutorService getExecutorService() {
// get asynchronous executor service from somewhere
// you will most likely need a ScheduledExecutorService
// instance, in order to schedule notification with
// some delay. Alternatively, you could try Thread.sleep(...)
// before notifying, but that is ugly.
}
private void doNotifyOtherInNewTransaction(Customer entity) {
// For all this to work correctly,
// you should execute your notification
// inside a new transaction. You might
// find it easier to do this declaratively
// by invoking some method demarcated
// with REQUIRES_NEW
try {
// (begin transaction)
doNotifyOther(entity);
// (commit transaction)
} catch (Exception ex) {
// (rollback transaction)
}
}
#PostUpdate
#PostPersist
public void notifyOther(final Customer entity) {
ScheduledExecutorService executor = getExecutorService();
// This is the "raw" version
// Most probably you will need to call
// executor.schedule and specify a delay,
// in order to give the old transaction some time
// to flush and commit
executor.execute(new Runnable() {
#Override
public void run() {
doNotifyOtherInNewTransaction(entity);
}
});
}
// This is exactly as your original code
public void doNotifyOther(Customer entity) {
CustomerFacadeREST custFacade = new CustomerFacadeREST();
Integer customerId = entity.getCustomerId();
String custData = custFacade.find(customerId).toString();
String successMessage = "Entity added to server";
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicPublish("", QUEUE_NAME, null, custData.getBytes());
channel.close();
connection.close();
}
catch(IOException ex){
}
finally {
}
}
}
registering some post-commit trigger (my recommendation if Heilo Rupp answer is not feasible). This is not timing dependant because it is guaranteed to execute after you have flushed to database. Furthermore, it has the added benefit that you don't notify if you end up rolling back your transaction. The way to do this depends on what you are using for transaction management, but basically you create an instance of some particular instance and then register it in some registry. For example, with JTA it would be:
public class EntityListener {
private final static String QUEUE_NAME = "customer";
private Transaction getTransaction() {
// get current JTA transaction reference from somewhere
}
private void doNotifyOtherInNewTransaction(Customer entity) {
// For all this to work correctly,
// you should execute your notification
// inside a new transaction. You might
// find it easier to do this declaratively
// by invoking some method demarcated
// with REQUIRES_NEW
try {
// (begin transaction)
doNotifyOther(entity);
// (commit transaction)
} catch (Exception ex) {
// (rollback transaction)
}
}
#PostUpdate
#PostPersist
public void notifyOther(final Customer entity) {
Transaction transaction = getTransaction();
transaction.registerSynchronization(new Synchronization() {
#Override
public void beforeCompletion() { }
#Override
public void afterCompletion(int status) {
if (status == Status.STATUS_COMMITTED) {
doNotifyOtherInNewTransaction(entity);
}
}
});
}
// This is exactly as your original code
public void doNotifyOther(Customer entity) {
CustomerFacadeREST custFacade = new CustomerFacadeREST();
Integer customerId = entity.getCustomerId();
String custData = custFacade.find(customerId).toString();
String successMessage = "Entity added to server";
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicPublish("", QUEUE_NAME, null, custData.getBytes());
channel.close();
connection.close();
}
catch(IOException ex){
}
finally {
}
}
}
If you are using Spring transactions, the code will be very similar, with just some class name changes.
Some pointers:
ScheduledExecutorService Javadoc, for triggering asynchronous actions.
transaction synchronization with JTA: Transaction Javadoc and Synchronization Javadoc
EJB transaction demarcation
the Spring equivalents: TransactionSynchronizationManager Javadoc and TransactionSynchronization Javadoc.
And some Spring documentation on Spring transactions
I guess you may be seeing a NPE, as you may be violating the paragraph you were citing:
String custData = custFacade.find(customerId).toString();
The find seems to implicitly querying for the object (as you describe), which may not be fully synced to the database and thus not yet accessible.
In his answer, gpeche noted that it's fairly straightforward to translate his option #2 into Spring. To save others the trouble of doing that:
package myapp.entity.listener;
import javax.persistence.PostPersist;
import javax.persistence.PostUpdate;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import myapp.util.ApplicationContextProvider;
import myapp.entity.NetScalerServer;
import myapp.service.LoadBalancerService;
public class NetScalerServerListener {
#PostPersist
#PostUpdate
public void postSave(final NetScalerServer server) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
#Override
public void afterCommit() { postSaveInNewTransaction(server); }
});
}
private void postSaveInNewTransaction(NetScalerServer server) {
ApplicationContext appContext =
ApplicationContextProvider.getApplicationContext();
LoadBalancer lbService = appContext.getBean(LoadBalancerService.class);
lbService.updateEndpoints(server);
}
}
The service method (here, updateEndpoints()) can use the JPA EntityManager (in my case, to issue queries and update entities) without any issue. Be sure to annotate the updateEndpoints() method with #Transaction(propagation = Propagation.REQUIRES_NEW) to ensure that there's a new transaction to perform the persistence operations.
Not directly related to the question, but ApplicationContextProvider is just a custom class to return an app context since JPA 2.0 entity listeners aren't managed components, and I'm too lazy to use #Configurable here. Here it is for completeness:
package myapp.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
#Override
public void setApplicationContext(ApplicationContext appContext)
throws BeansException {
applicationContext = appContext;
}
}

Problems with threads and hibernate sessions

Im using hibernate 3 and spring.
When I start a thread an exception occurred:
org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions
I dont know how to detach entities or close session with this architecture.
I appreciate some help.
CommunicationService.sendCommunications() code:
public void sendCommunications(HibernateMessageToSendRepository messageToSendRepository) {
Long messageId = new Long(41); //this is only for test. the idea is get a list of id and generate a thread group.
MessageSender sender = SmsSender(messageId, messageToSendRepository);
sender.start();
}
Invoking sendCommunications code:
ApplicationContext appCont = new ClassPathXmlApplicationContext("appContext.xml");
ServiceLocator serviceLocator = ServiceLocator.getInstance();
HibernateMessageToSendRepository messageToSendRepository = (HibernateMessageToSendRepository) appCont.getBean("messageToSendRepository");
CommunicationService communication = serviceLocator.getCommunicationService();
communication.sendCommunications(messageToSendRepository);
SmsSender (extends from MessageSender (thread)) code:
public class SmsSender extends MessageSender {
public SmsSender(Long messageToSendId, HibernateMessageToSendRepository messageToSendRepository) {
super(messageToSendRepository);
MessageToSend messageToSendNew = this.messageToSendRepository.getById(messageToSendId);
this.messageToSend = messageToSendNew;
}
public void run() {
try {
MessageToSendSms messageToSendSms = (MessageToSendSms) this.messageToSend;
Iterator<CustomerByMessage> itCbmsgs = messageToSendSms.getCustomerByMessage().iterator();
while (itCbmsgs.hasNext()) {
CustomerByMessage cbm = (CustomerByMessage) itCbmsgs.next();
//sms sending
this.getGateway().sendSMS(cbm.getBody(), cbm.getCellphone());
cbm.setStatus(CustomerByMessageStatus.SENT_OK);
cbm.setSendingDate(Calendar.getInstance().getTime());
}
messageToSendSms.getMessage().setStatus(messageToSendStatus.ALL_MESSAGES_SENT);
this.messageToSendRepository.update(messageToSendSms);
} catch (Exception e) {
this.log.error("Error en sms sender " + e.getMessage());
}
}
}
MessageToSendRepository code:
public void update(MessageToSend messageToSend) {
try {
this.getSession().update(messageToSend);
} catch (HibernateException e) {
this.log.error(e.getMessage(), e);
throw e;
}
}
You need to detach messageToSendNew after you you retrieve it, but before you share it with another thread. You can detach the object by calling Session.close() on your hibernate session.
Caveat you must eagerly populate all the fields that you need.
If you need to reconnect it with a new session you can use the merge() method.

Message Driven Bean with a Datasource

My question is how do I configure an EJB 3.0 style message driven bean to use a configured JMS datasource in jboss.
For example, my MDB looks something like:
#MessageDriven(mappedName = "ExampleMDB", activationConfig = {
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"),
#ActivationConfigProperty(propertyName = "destination", propertyValue = "MyTopic"),
#ActivationConfigProperty(propertyName = "channel", propertyValue = "MyChannel"),
})
#ResourceAdapter(value = "wmq.jmsra.rar")
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
#TransactionManagement(TransactionManagementType.BEAN)
public class MyMDB implements MessageListener {
.....
}
But I would like the bean to attached to a given JMS datasource ( in the case of jboss 4.2.2 this is in deploy/jms/jms-ds.xml). Perhaps this is not even possible but is worth asking.
If I understood your problem correctly, MyMDB listens to a topic on WebLogic, and you want to use an additional JMS destination provided by JBoss, defined in a deployed configuration file and identified by its JNDI name (by default, deploy/jms/jms-ds.xml only contains the configuration for the JMS provider and connection factories -- no data sources).
The easiest way is to let the container inject the JMS destination and a connection factory via its JNDI name (in JBoss the JMS destinations are configured by deploying xxx-service.xml files). On startup you can then initialize the connection, and perform cleanup as soon as the MDB is released.
The following examples shows injection (#Resource) and resource managemend (#PostConstruct and #PreDestroy). The JMS connection and destination is used in useJmsDestination(String) to send a text message.
public class MyMDB implements MessageListener {
#Resource(mappedName = "queue/YourQueueName") // can be topic too
private Queue targetDestination;
#Resource(mappedName = "QueueConnectionFactory") // or ConnectionFactory
private QueueConnectionFactory factory;
private Connection conn;
public void onMessage(Message m) {
// parse message and do what you need to do
...
// do something with the message and the JBoss JMS destination
useJmsDestination(messageString);
}
private void useJmsDestination(String text) {
Session session = null;
MessageProducer producer = null;
try {
session = conn.createSession(true, Session.AUTO_ACKNOWLEDGE);
producer = session.createProducer(targetDestination);
TextMessage msg = session.createTextMessage(text);
producer.send(msg);
} catch (JMSException e) {
throw new RuntimeException(e);
} finally {
try {
if (producer != null) {
producer.close();
}
if (session != null) {
session.close();
}
} catch (JMSException e) {
// handle error, should be non-fatal, as the message is already sent.
}
}
}
#PostConstruct
void init() {
initConnection();
// other initialization logic
...
}
#PreDestroy
void cleanUp() {
closeConnection();
// other cleanup logic
...
}
private void initConnection() {
try {
conn = factory.createConnection();
} catch (JMSException e) {
throw new RuntimeException("Could not initialize connection", e);
}
}
private void closeConnection() {
try {
conn.close();
} catch (JMSException e) {
// handle error, should be non-fatal, as the connection is being closed
}
}
}
I hope this can help you.
I think what you are asking is "How do I specify the JNDI location of the JMS datasource to use for an MDB?"
In which case the answer is:
#ActivationConfigProperty(propertyName = "providerAdapterJNDI", propertyValue = "java:/DefaultJMSProvider")
Also, take a look at the following page which provides loads of useful details on configuring MDBs in jBoss:
http://www.jboss.org/community/docs/DOC-9352

Categories

Resources