New EJB 3.2 feature - Modernized JCA-based Message-Driven Bean

Datetime:2016-08-23 01:57:27          Topic: EJB           Share

WebLogic Server 12.2.1 is a fully compatible implementation of Java EE 7 specification. One of the big improvements in EJB container in this release of WebLogic Server is that, a message-driven bean is able to implement a listener interface with no methods. When such a no-methods listener interface is used, all non-static public methods of the bean class (and of the bean class's super classes except java.lang.Object) are exposed as message listener methods.

Let's develop a sample step by step. The sample application assumes that an e-commercial website sends the buy/sell events to JMS Queues - buyQueue and sellQueue - respectively when a product is sold or bought. The connector listens on the queues, and execute message-driven bean's non-static public methods to persist the records in events to persistent store.

1. Define a no-methods message listener interface

In our sample, the message listener interface NoMethodsListenerIntf has no methods in it.

List 1 - No-methods message listener interface

public NoMethodsListenerIntf {

}

2. Now define the bean class

In message-driven bean class, there are two non-static public methods - productBought and productSold, so they are both exposed as message listener methods. When connector gets a product-sold event from sellQueue, it will then invoke message-driven bean's productSold method, and likewise for product-bought event. We annotate productSold method and productBought method with @EventMonitor, indicating that they are the target methods that connector should execute. These two methods will persist the records into database or other persistent store.

You can define more non-static public methods, but which ones should be executed by connector are up to connector itself.

List 2 - Message-Driven Bean

@MessageDriven(activationConfig = {

@ActivationConfigProperty(propertyName = "resourceAdapterJndiName", propertyValue = "eis/TradeEventConnector")

})

public class TradeEventProcessingMDB implements NoMethodsListenerIntf {

@EventMonitor(type = "Retailer")

public void productSold(long retailerUUID, long productId) {

System.out.println("Retailer [" + retailerUUID + "], product [" + productId + "] has been sold!");

// persist to database

}

@EventMonitor(type = "Customer")

public void productBought(long customerId, long productId) {

System.out.println("Customer [" + customerId + "] has bought product [" + productId + "]!");

// persist to database

}

}

The EventMonitor annotation is defined as below:

List 3 - EventMonitor annotation

@Target({ ElementType.METHOD })

@Retention(RetentionPolicy.RUNTIME)

public @interface EventMonitor {

public String type();

}

When this message-driven bean is deployed onto WebLogic Server, EJB container detects that it's an EJB 3.2 compatible message-driven bean. If you forgot to specify a value for resourceAdapterJndiName, WebLogic Server will try to locate a suitable connector resource, for example, a connector that is declaring support of the same no-methods message listener interface (in the current application or server-wide connector that is global-accessible).

If a suitable connector is found and associated with message-driven bean, the connector can retrieve the bean class definition and then analyze.

3. Developing a connector that is used to associate with message-driven bean

In connector application, we retrieve the bean class definition via getEndpointClass() method of MessageEndpointFactory, and then inspect every method if it's annotated with @EventMonitor. After that, we create a javax.jms.MessageListener with the target method of the bean class to listen on the event queues.

List 4 - trade event connector

@Connector(

description = "This is a sample resource adapter",

eisType = "Trade Event Connector",

vendorName = "Oracle WLS",

version = "1.0")

public class TradeEventConnector implements ResourceAdapter, Serializable {

// jms related resources

......

private static final String CALLBACK_METHOD_TYPE_RETAILER = "Retailer";

private static final String CALLBACK_METHOD_TYPE_CUSTOMER = "Customer";

@Override

public void endpointActivation(MessageEndpointFactory mef, ActivationSpec activationSpec)

throws ResourceException {

try {

Class<?> beanClass = mef.getEndpointClass(); // retrieve bean class definition

......

jmsContextForSellingEvent = ...; // create jms context

jmsContextForBuyingEvent = ...;

jmsConsumerForSellingEvent = jmsContextForSellingEvent.createConsumer(sellingEventQueue);

jmsConsumerForBuyingEvent = jmsContextForBuyingEvent.createConsumer(buyingEventQueue);

jmsConsumerForSellingEvent.setMessageListener(createTradeEventListener(mef, beanClass, CALLBACK_METHOD_TYPE_RETAILER));

jmsConsumerForBuyingEvent.setMessageListener(createTradeEventListener(mef, beanClass, CALLBACK_METHOD_TYPE_CUSTOMER));

jmsContextForSellingEvent.start();

jmsContextForBuyingEvent.start();

} catch (Exception e) {

throw new ResourceException(e);

}

}

private MessageListener createTradeEventListener(MessageEndpointFactory mef, Class<?> beanClass, String callbackType) {

for (Method m : beanClass.getMethods()) {

if (m.isAnnotationPresent(EventMonitor.class)) {

EventMonitor eventMonitorAnno = m.getAnnotation(EventMonitor.class);

if (callbackType.equals(eventMonitorAnno.type())) {

return new JmsMessageEventListener(mef, m);

}

}

}

return null;

}

@Override

public void endpointDeactivation(MessageEndpointFactory mef, ActivationSpec spec) {

// deactivate connector

}

......

}

The associated activation spec for the connector is defined as below:

List 5 - the activation spec

@Activation(

messageListeners = {NoMethodsListenerIntf.class}

)

public class TradeEventSpec implements ActivationSpec, Serializable {

......

}

4. Developing a message listener to listen on the event queue.

When message listener's onMessage() is invoked, we create a message endpoint via MessageEndpointFactory, and invoke the target method on this message endpoint.

List 6 - jms message listener

public class JmsMessageEventListener implements MessageListener {

private MessageEndpointFactory endpointFactory;

private Method targetMethod;

public JmsMessageEventListener(MessageEndpointFactory mef, Method executeTargetMethod) {

this.endpointFactory = mef;

this.targetMethod = executeTargetMethod;

}

@Override

public void onMessage(Message message) {

MessageEndpoint endpoint = null;

String msgText = null;

try {

if (message instanceof TextMessage) {

msgText = ((TextMessage) message).getText();

} else {

msgText = message.toString();

}

long uid = Long.parseLong(msgText.substring(0, msgText.indexOf(",")));

long pid = Long.parseLong(msgText.substring(msgText.indexOf(",") + 1));

endpoint = endpointFactory.createEndpoint(null);

endpoint.beforeDelivery(targetMethod);

targetMethod.invoke(endpoint, new Object[]{uid, pid});

endpoint.afterDelivery();

} catch (Exception e) {

// log exception

System.err.println("Error when processing message: " + e.getMessage());

} finally {

if (endpoint != null) {

endpoint.release();

}

}

}

}

5. Verify the application

We assume that the syntax of the event is composed of two digits separated with ",", for example, 328365,87265. The former digit is customer or retailer id, and the latter digit is product id.

Now sending such events to the event queues, you'll find that they are persisted by message-driven bean.





About List