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.