User: pra
Date: 01/03/01 13:58:24
Modified: src/docs jbossdocs.xml
Added: src/docs howtomdb.xml
Log:
Added chapter on MDB
Revision Changes Path
1.3 +2 -1 manual/src/docs/jbossdocs.xml
Index: jbossdocs.xml
===================================================================
RCS file: /products/cvs/ejboss/manual/src/docs/jbossdocs.xml,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- jbossdocs.xml 2001/02/11 00:57:38 1.2
+++ jbossdocs.xml 2001/03/01 21:58:24 1.3
@@ -19,7 +19,7 @@
<!ENTITY howtoejx.xml SYSTEM "howtoejx.xml">
<!ENTITY howtojca.xml SYSTEM "howtojca.xml">
<!ENTITY basicconfiguration.xml SYSTEM "basicconfiguration.xml">
-
+<!ENTITY howtomdb.xml SYSTEM "howtomdb.xml">
]>
<book>
@@ -38,6 +38,7 @@
&cmp.xml;
&customizingjaws.xml;
&advconfig.xml;
+&howtomdb.xml;
&designnotes.xml;
<chapter><title>Howto</title>
1.1 manual/src/docs/howtomdb.xml
Index: howtomdb.xml
===================================================================
<chapter>
<title>Working with Message Driven Beans</title>
<para>Author:
<author><firstname>Peter</firstname><surname>Antman</surname></author>
<email>[EMAIL PROTECTED]</email>
</para>
<section><title>Introduction</title>
<para>This chapter describes how to use Message Driven Beans with JBoss. Message
Driven Beans are a new bean type added in the EJB 2.0 specification. They are
therefore still somewhat unknown and under utilized. Hopefully this documentation will
lessen that a bit, since JBoss has full support for Message Driven Beans.</para>
</section>
<section><title>Six steps to MDB nirvana in JBoss</title>
<para>If you don't need to do any special configuration and already know how to code
a message driven bean (MDB), here are 6 easy step to get it up and working with JBoss.
(The easiest way to get up and working with Message Driven Beans in JBoss is the
check out the jbosstest from cvs and look into the mdb test.)</para>
<orderedlist>
<listitem>
<para>
Check out the latest JBoss from cvs and compile.
</para>
</listitem>
<listitem>
<para>
Write the source code for a Message Driven Bean.
</para>
</listitem>
<listitem>
<para>Write the ejb-jar.xml descriptor.</para>
<para>Here is an example for a bean listening on a queue with bean managed
transactions :</para>
<programlisting><![CDATA[
<?xml version="1.0"?>
<!DOCTYPE ejb-jar>
<!--
PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
"http://java.sun.com/dtd/ejb-jar_2_0.dtd"
-->
<ejb-jar>
<enterprise-beans>
<message-driven>
<ejb-name>QueueBean</ejb-name>
<ejb-class>org.jboss.test.mdb.bean.QueueBean</ejb-class>
<message-selector></message-selector>
<transaction-type>Bean</transaction-type>
<acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
<message-driven-destination>
<destination-type>javax.jms.Queue</destination-type>
<subscription-durability>NonDurable</subscription-durability>
</message-driven-destination>
</message-driven>
</enterprise-beans>
</ejb-jar>
]]></programlisting>
<para>
And here are one for a durable topic with container managed transactions:</para>
<programlisting><![CDATA[
<?xml version="1.0"?>
<!DOCTYPE ejb-jar>
<message-driven>
<ejb-name>DurableTopicBean</ejb-name>
<ejb-class>org.jboss.test.mdb.bean.TopicBean</ejb-class>
<message-selector></message-selector>
<transaction-type>Container</transaction-type>
<message-driven-destination>
<destination-type>javax.jms.Topic</destination-type>
<subscription-durability>Durable</subscription-durability>
</message-driven-destination>
</message-driven>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>DurableTopicBean</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
]]></programlisting>
</listitem>
<listitem>
<para>
Write the jboss.xml deployment descriptor (this MUST always be done with MDB in
jboss), but for a MDB without special need the container configuration need not be
filled in. </para>
<para>The destination-jndi-name element points to the queue.</para>
<para> Here are the one for the queue bean:</para>
<programlisting><![CDATA[
<?xml version="1.0" encoding="Cp1252"?>
<jboss>
<enterprise-beans>
<message-driven>
<ejb-name>QueueBean</ejb-name>
<configuration-name>Standard Message Driven Bean</configuration-name>
<destination-jndi-name>queue/testQueue</destination-jndi-name>
</message-driven>
</enterprise-beans>
</jboss>
]]></programlisting>
<para> And here for the durable topic:</para>
<programlisting><![CDATA[
<?xml version="1.0" encoding="Cp1252"?>
<jboss>
<enterprise-beans>
<message-driven>
<ejb-name>DurableTopicBean</ejb-name>
<configuration-name>Standard Message Driven Bean</configuration-name>
<destination-jndi-name>topic/testDurableTopic</destination-jndi-name>
<mdb-user>john</mdb-user>
<mdb-passwd>needle</mdb-passwd>
<mdb-client-id>DurableSubscriberExample</mdb-client-id>
</message-driven>
</jboss>
]]></programlisting>
</listitem>
<listitem>
<para>
Edit jbossmq.xml in conf/default and ad the queue or topic
</para>
<para>Eg:</para>
<programlisting><![CDATA[
<Queue>
<Name>testQueue</Name>
</Queue>
]]></programlisting>
<para> For the queue, and </para>
<programlisting><![CDATA[
<Topic>
<Name>testDurableTopic</Name>
</Topic>
]]></programlisting>
<para>plus</para>
<programlisting><![CDATA[
<User>
<Name>john</Name>
<Password>needle</Password>
<Id>DurableSubscriberExample</Id>
<DurableSubscription>
<Name>DurableSubscriberExample</Name>
<TopicName>testDurableTopic</TopicName>
</DurableSubscription>
</User>
]]></programlisting>
<para>for the durable topic.</para>
</listitem>
<listitem>
<para>
Deploy the bean, for example by packing it in a jar and copy it into the jboss
deploy directory.</para>
</listitem>
</orderedlist>
<para>Start sending messages to your bean</para>
</section>
<section><title>Writing Message Driven Bean</title>
<para>Message Driven Beans are a new part of EJB 2.0. The reason these beast where
added is that there was no way in EJB 1.1 to handle asynchronous invocation. The
primary reason behind this is that an EJB bean may never be invoke from other objects
other than through its remote interface. Therefore a bean could never set itself up as
a listener for asynchronous invocation.</para>
<para>With MDB this lack is filled. An MDB is a bean without a remote interface,
where the container sets itself up as a listener for asynchronous invocation and
handles the invocation of the concrete bean, which follows all the usual roles for EJB
beans.</para>
<para>Message Driven Beans are primarily focused on JMS. An MDB is either a topic or
a queue subscriber. One nice feature with MDB is that one gets multithreaded
subscribers (even for Topics) without having to care about the subtle difficulties to
write multithreaded code and to write multithreaded JMS message consumers.</para>
<para>What should you use your beans to then? Basically you would use MDB any time
you are about to create a JMS subscriber. Typical conditions for doing this is:</para>
<itemizedlist>
<listitem>
Decouple an invoker from the invoked code.
</listitem>
<listitem>
Make it possible for multiple parties to get you messages.
</listitem>
<listitem>
Get asynchronous behavior, i.e. start long running code without the need to wait for
the call to return.
</listitem>
</itemizedlist>
<para>Here we will write some typical MDB.</para>
<section><title>Hello World MDB</title>
<para>An MDB follows a typical EJB contract. It must implement the following two
interfaces:</para>
<itemizedlist>
<listitem>
javax.ejb.MessageDrivenBean
</listitem>
<listitem>
javax.jms.MessageListener
</listitem>
</itemizedlist>
<para>An MDB must therefore typically contain the following four methods:</para>
<programlisting><![CDATA[
public void setMessageDrivenContext(MessageDrivenContext ctx);
public void ejbCreate();
public void ejbRemove();
public void onMessage(Message message);
]]></programlisting>
<para>The full program listing of a simple Hello World bean could look like
this:</para>
<programlisting><![CDATA[
package test.bean;
import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
import javax.ejb.EJBException;
import javax.jms.MessageListener;
import javax.jms.Message;
public class MDB implements MessageDrivenBean, MessageListener{
private MessageDrivenContext ctx = null;
public MDB() {
}
public void setMessageDrivenContext(MessageDrivenContext ctx)
throws EJBException {
this.ctx = ctx;
}
public void ejbCreate() {}
public void ejbRemove() {ctx=null;}
public void onMessage(Message message) {
System.err.println("Bean got message" + message.toString() );
}
}
]]></programlisting>
<para>To deploy this into JBoss we will have to write two deployment descriptors.
One standard ejb-jar and one that is JBoss specific. We will chose to make this bean a
Topic subscriber. Since we do not do anything we could typically chose to use
container managed transaction with NotRequired (although this would in most cases not
be the best thing to do)</para>
<programlisting><![CDATA[
<?xml version="1.0"?>
<!DOCTYPE ejb-jar>
<message-driven>
<ejb-name>MDB</ejb-name>
<ejb-class>test.bean.MDB</ejb-class>
<message-selector></message-selector>
<transaction-type>Container</transaction-type>
<message-driven-destination>
<destination-type>javax.jms.Topic</destination-type>
<subscription-durability>NonDurable</subscription-durability>
</message-driven-destination>
</message-driven>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>DurableTopicBean</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>NotRequired</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
]]></programlisting>
<para>We also need to write a small deployment descriptor that is specific for
JBoss. The full version of this is quite big, and with it it is possible to configure
the MDB container quite a bit. For most users this is not necessary, and the may use
the standard configuration. The most important part of the descriptor is the
specification of the destination. We chose the testTopic since it is always available
in JBoss.</para>
<programlisting><![CDATA[
<?xml version="1.0" encoding="Cp1252"?>
<jboss>
<enterprise-beans>
<message-driven>
<ejb-name>MDB</ejb-name>
<configuration-name>Standard Message Driven Bean</configuration-name>
<destination-jndi-name>test/testTopic</destination-jndi-name>
</message-driven>
</enterprise-beans>
</jboss>
]]></programlisting>
<para>Then you will have to pack these into a jar. Here is one way to do it:</para>
<programlisting><![CDATA[
mkdir dist
mkdir dist/META-INF
mkdir dist/test
cp MDB.class dist/test
cp MDB-jar.xml dist/META-INF/ejb-jar.xml
cp MDB-jboss.xml dist/META-INF/jboss.xml
cd dist
java jar -cvf mdb.jar .
]]></programlisting>
<para>Copy the bean into the deploy directory of JBoss.</para>
<para>To send stuff to your bean you need a JMS publisher. This is standard JMS
programming, but here is one way to do it:</para>
<programlisting><![CDATA[
import javax.naming.*;
import javax.jms.*;
public class Main {
public static void main(String arg[]) {
try {
// Get access to JNDI
Context context = new InitialContext();
// Lookup the managed connection factory for a topic
TopicConnectionFactory topicFactory =
(TopicConnectionFactory)context.lookup(TOPIC_FACTORY);
// Create a connection to the JMS provider
TopicConnection topicConnection = topicFactory.createTopicConnection();
// Creat a topic session
TopicSession session = topicConnection.createTopicSession(
// No transaction
false,
// Auto ack
Session.AUTO_ACKNOWLEDGE);
// Lookup the destination you want to publish to
Topic topic = (Topic)context.lookup("topic/testTopic");
// Create a publisher
TopicPublisher pub = session.createPublisher(topic);
// Create a message
TextMessage message = session.createTextMessage();
message.setText("Hello World!");
// Publish the message
pub.publish(topic, message);
// Close the stuff
session.close();
topicConnection.close();
catch (Exception e) {
e.printStackTrace();
}
}
}
]]></programlisting>
<para>
You will typically have to include the following jars in your classpath:
jbossmq.client.jar, jms.jar, jnp-client.jar and jta-spec1_0_1.jar.
</para>
</section>
<section><title>MDB as a listener</title>
<para>We will here look at an example that does something more real. One nice way to
use MDB is to have them act/emulate the listener pattern. The normal way with
listeners is to have them register them self on the object emitting events. This is
not possible with MDB, since the bean them self may only do some work when the receive
an even (a message). Set up of MDB as a listener will therefore have to be done
outside of the MDB. This could be as simple as defining a topic or queue and hardwire
into the event generator to publish it's events/messages to that destination. It's
also possible to create a more generic framework for message driven callback,
something I have done with JMS and JMX. But that is for another document. Lets instead
look on the MDB side.</para>
<para>One way to partition the logic in EJB is to have one bean that does the real
work (contains the logic), this could be a stateless session bean or an entity bean,
and one bean acting as a listener. Lets write a working, but simplified version of
this patter. We start with a very simple stateless session bean, with just one method:
doWork. To make it easy we let it take a String as its mission. This is straight
forward. First we need a home interface:</para>
<programlisting><![CDATA[
package msgbean.interfaces;
import java.rmi.RemoteException;
import javax.ejb.*;
public interface WorkerHome extends EJBHome {
public Worker create() throws RemoteException, CreateException;
} // WorkerHome
]]></programlisting>
<para>We also need a remote interface</para>
<programlisting><![CDATA[
package msgbean.interfaces;
import java.rmi.RemoteException;
import javax.ejb.EJBObject;
public interface Worker extends EJBObject {
public void doWork(String work) throws RemoteException;
} // Worker
]]></programlisting>
<para>And finally we need the bean class:</para>
<programlisting><![CDATA[
package msgbean.server;
import javax.ejb.*;
import java.rmi.RemoteException;
import javax.naming.*;
public class WorkerBean implements SessionBean {
private SessionContext ctx;
public WorkerBean() {
}
public void setSessionContext(javax.ejb.SessionContext ctx) throws
RemoteException { this.ctx = ctx; }
public void unsetSessionContext() throws RemoteException { this.ctx = null; }
public void ejbActivate() throws RemoteException {}
public void ejbPassivate() throws RemoteException {}
public void ejbRemove() throws RemoteException {}
public void ejbCreate() throws CreateException {}
public void doWork(String work) {
System.out.println("WorkerBean doing work: " + work);
}
} // WorkerBean
]]></programlisting>
<para>We will write the deployment descriptor for the bean later, since we will pack
it with the listener.</para>
<para>The next step is to write the listener bean. Here we will ad basically one
thing: the ability to lookup the Worker bean and invoke it. We look up the bean
through a an JNDI reference defined via ejb-ref. This might be done in
ejbCreate().</para>
<programlisting><![CDATA[
Context initCtx = new InitialContext();
workerHome = (WorkerHome)initCtx.lookup("java:comp/env/ejb/worker");
]]></programlisting>
<para>In the onMessage() method we get what we need out of the message. Here we
could have sent an object of some kind, known to the listener. For simplicity we here
chose to send a TextMessage and send the content of the message to the worker.</para>
<programlisting><![CDATA[
Worker worker = workerHome.create();
if (message instanceof TextMessage) {
TextMessage m = (TextMessage)message;
worker.doWork(m.getText());
}
]]></programlisting>
<para>Here is the complete listing of the class:</para>
<programlisting><![CDATA[
package msgbean.server;
import javax.ejb.*;
import javax.naming.*;
import javax.jms.*;
import msgbean.interfaces.WorkerHome;
import msgbean.interfaces.Worker;
public class ListenerBean implements MessageDrivenBean, MessageListener{
private MessageDrivenContext ctx = null;
private WorkerHome workerHome = null;
public ListenerBean() {
}
public void setMessageDrivenContext(MessageDrivenContext ctx)
throws EJBException {
this.ctx = ctx;
}
public void ejbCreate() throws CreateException {
try {
Context initCtx = new InitialContext();
workerHome = (WorkerHome)initCtx.lookup("java:comp/env/ejb/worker");
}catch(Exception ex) {
throw new CreateException("Could not get worker: " + ex);
}
}
public void ejbRemove() {ctx=null;}
public void onMessage(Message message) {
try {
Worker worker = workerHome.create();
// Get the message, here we could have an ObjectMessage containg
// an object of a known class. We use Text here for simplicity
if (message instanceof TextMessage) {
TextMessage m = (TextMessage)message;
worker.doWork(m.getText());
}
}catch(Exception ex) {
throw new EJBException("Could not call worker " + ex);
}
}
}
]]></programlisting>
<para>To deploy this into JBoss we need to write two deployment descriptors, one
standard ejb, and one for JBoss. Lets begin with the standard one. For ease of use we
include both beans into one jar. For the Message Driven Bean we have to decide if its
a topic or not, and what kind of transactions it should run under. In this case we
chose a topic and container managed transaction. We also have to specify an ejb-ref so
that the listener can lookup the home of the WorkerBean:</para>
<programlisting><![CDATA[
<message-driven>
<ejb-name>ListenerBean</ejb-name>
<ejb-class>msgbean.server.ListenerBean</ejb-class>
<message-selector></message-selector>
<transaction-type>Container</transaction-type>
<ejb-ref>
<description>The Workers home</description>
<ejb-ref-name>ejb/worker</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<ejb-link>WorkerBean</ejb-link>
<home>msgbean.interfaces.WorkerHome</home>
<remote>msgbean.interfaces.Worker</remote>
</ejb-ref>
<message-driven-destination>
<destination-type>javax.jms.Topic</destination-type>
<subscription-durability>NonDurable</subscription-durability>
</message-driven-destination>
</message-driven>
]]></programlisting>
<para>We also have to ad an entry for the Worker bean:</para>
<programlisting><![CDATA[
<session>
<description>Worker bean</description>
<display-name>Worker</display-name>
<ejb-name>WorkerBean</ejb-name>
<home>msgbean.interfaces.WorkerHome</home>
<remote>msgbean.interfaces.Worker</remote>
<ejb-class>msgbean.server.WorkerBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
]]></programlisting>
<para>Here is the complete deployment descriptor, including defintions of the
transaction type.</para>
<programlisting><![CDATA[
<!DOCTYPE ejb-jar>
<ejb-jar>
<enterprise-beans>
<message-driven>
<ejb-name>ListenerBean</ejb-name>
<ejb-class>msgbean.server.ListenerBean</ejb-class>
<message-selector></message-selector>
<transaction-type>Container</transaction-type>
<ejb-ref>
<description>The Workers home</description>
<ejb-ref-name>ejb/worker</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<ejb-link>WorkerBean</ejb-link>
<home>msgbean.interfaces.WorkerHome</home>
<remote>msgbean.interfaces.Worker</remote>
</ejb-ref>
<message-driven-destination>
<destination-type>javax.jms.Topic</destination-type>
<subscription-durability>NonDurable</subscription-durability>
</message-driven-destination>
</message-driven>
<session>
<description>Worker bean</description>
<display-name>Worker</display-name>
<ejb-name>WorkerBean</ejb-name>
<home>msgbean.interfaces.WorkerHome</home>
<remote>msgbean.interfaces.Worker</remote>
<ejb-class>msgbean.server.WorkerBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>ListenerBean</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>WorkerBean</ejb-name>
<method-intf>Remote</method-intf>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
]]></programlisting>
<para>We also need to write a jboss.xml deployment descriptor. This is needed
because it the destination JNDI name must be defined somewhere. Here we can not use on
a default.</para>
<programlisting><![CDATA[
<?xml version="1.0" encoding="Cp1252"?>
<jboss>
<enterprise-beans>
<message-driven>
<ejb-name>ListenerBean</ejb-name>
<configuration-name>Standard Message Driven Bean</configuration-name>
<destination-jndi-name>topic/testTopic</destination-jndi-name>
</message-driven>
<secure>false</secure>
</enterprise-beans>
</jboss>
]]></programlisting>
<para>We then have to compile the beans. You will have to add several jar-files to
your class-path to have success with this. Among the most important, and special for
MDB, is that you will need ejb2.0 from lib/ext to be able to compile. You also need to
pack them into a jar-file. Do this as described above. Deploy by copying the jar-file
to deploy. You may use the example publisher above to publish messages to the
bean.</para>
</section>
<section><title>The adapter pattern</title>
<para>Another way to use MDB is as an adapter, for example between different
messaging systems. This is rewarding, especially if you have an J2EE Connector
resource adapter for the other system, since you then will get a very effective,
multithreaded and pooled system, without any advanced programing. Since I have written
such a resource adapter for the messaging server XmlBlaster we will here look at one
way to adapt between JMS and <ulink
url="http://www.xmlblaster.org"><citetitle>XmlBlaster</citetitle></ulink>.</para>
<para>The adapter uses an MDB to subscribe to a topic. It then republish it to an
XmlBlaster server through the XmlBlasterK2 J2EE Connector adapter.The code is pretty
straight forward. A J2EE Connector resource adapter must be deployed into the JBoss
server. It is then referenced from within the bean the same way you would reference a
JDBC resource. You would look it up this way:</para>
<programlisting><![CDATA[
factory = (BlasterConnectionFactory)new InitialContext ().lookup
("java:comp/env/xmlBlaster");
]]></programlisting>
<para>And use it this way:</para>
<programlisting><![CDATA[
con = factory.getConnection();
// Construct Blaster Headers
String key ="<key oid=\"" + message.getJMSMessageID() +"\"
contentMime=\"text/xml\"></key>";
String qos = "<qos></qos>";
con.publish( new MessageUnit(key,msg.getBytes(),qos));
]]></programlisting>
<para>The complete bean is pretty straight forward (almost the same code could be
used to write directly to a database for example):</para>
<programlisting><![CDATA[
package javaclients.j2ee.k2;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
import javax.ejb.EJBException;
import javax.jms.MessageListener;
import javax.jms.Message;
import javax.jms.TextMessage;
import javax.jms.JMSException;
import javax.resource.ResourceException;
import org.xmlBlaster.j2ee.k2.client.*;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.engine.helper.MessageUnit;
public class JmsAdapter implements MessageDrivenBean, MessageListener{
private MessageDrivenContext ctx = null;
private BlasterConnectionFactory factory = null;
public JmsAdapter() {
}
public void setMessageDrivenContext(MessageDrivenContext ctx)
throws EJBException {
this.ctx = ctx;
try {
factory = (BlasterConnectionFactory)new InitialContext ().lookup
("java:comp/env/xmlBlaster");
} catch (NamingException ex) {
throw new EJBException ("XmlBlaster not found: "+ex.getMessage ());
}catch(Throwable th) {
System.err.println("Throwable: " +th);
th.printStackTrace();
throw new EJBException("Throwable in setContext: " +th);
}
}
public void ejbCreate() {}
public void ejbRemove() {ctx=null;}
public void onMessage(Message message) {
BlasterConnection con = null;
try {
// Get message to handle
System.err.println("Got message: " + message);
if (message instanceof TextMessage) {
String msg = ((TextMessage)message).getText();
// Get connection
con = factory.getConnection();
// Construct Blaster Headers - howto hanlde key here?
String key ="<key oid=\"" + message.getJMSMessageID() +"\"
contentMime=\"text/xml\"></key>";
String qos = "<qos></qos>";
con.publish( new MessageUnit(key,msg.getBytes(),qos));
} else {
System.err.println("Got message type I cant handle");
}
}catch(ResourceException re) {
System.err.println("Resource ex: " +re);
re.printStackTrace();
} catch(XmlBlasterException be) {
System.err.println("Blaster ex: " +be);
be.printStackTrace();
}catch(JMSException je) {
System.err.println("JMSException ex: " +je);
je.printStackTrace();
}catch(Throwable th) {
System.err.println("Throwable: " +th);
th.printStackTrace();
}finally {
try {
if (con != null)
con.close ();
}
catch (Exception ex) {}
}
}
} // MessageBeanImpl
]]></programlisting>
<para>The deployment descriptors for this bean follows the normal way to write them,
except that you will have to add entries for the resource-ref. Here is the ejb
deployment descriptor:</para>
<programlisting><![CDATA[
<?xml version="1.0"?>
<!DOCTYPE ejb-jar>
<ejb-jar>
<enterprise-beans>
<message-driven>
<ejb-name>JmsAdapter</ejb-name>
<ejb-class>javaclients.j2ee.k2.JmsAdapter</ejb-class>
<message-selector></message-selector>
<transaction-type>Container</transaction-type>
<resource-ref>
<res-ref-name>xmlBlaster</res-ref-name>
<res-type>org.xmlBlaster.j2ee.k2.client.BlasterConnectionFactory</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<message-driven-destination>
<destination-type>javax.jms.Topic</destination-type>
<subscription-durability>NonDurable</subscription-durability>
</message-driven-destination>
</message-driven>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>JmsAdapter</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
]]></programlisting>
<para>And here is the jboss.xml descriptor:</para>
<programlisting><![CDATA[
<?xml version="1.0" encoding="Cp1252"?>
<jboss>
<resource-managers>
<resource-manager>
<res-name>xmlBlaster</res-name>
<res-jndi-name>java:/XmlBlasterDS</res-jndi-name>
</resource-manager>
</resource-managers>
<enterprise-beans>
<message-driven>
<ejb-name>JmsAdapter</ejb-name>
<configuration-name>Standrad Message Driven Bean</configuration-name>
<destination-jndi-name>topic/testTopic</destination-jndi-name>
<resource-ref>
<res-ref-name>xmlBlaster</res-ref-name>
<resource-name>xmlBlaster</resource-name>
</resource-ref>
</message-driven>
<secure>false</secure>
</enterprise-beans>
</jboss>
]]></programlisting>
</section>
</section>
<section><title>Advanced MDB configuration</title>
<para>The MDB implementation for JBoss have been written such that a user should not
have to know a lot about the internals of JBoss. A part from the feew necessary lines
in jboss.xml nothing else than standard knowledge of Message Driven Beans should
suffice.</para>
<para>Following the design of JBoss, the MDB implementation is also extremely
configurable, if one want to tune or totally change the default configuration and
implementation! Here follows some short notes on howto configure MDB for JBoss</para>
<section><title>EJB deployment descriptor</title>
<para>All MDB:s are quite configurable, apart from them being deployed in JBoss.
Here are the basic choices that can be made:</para>
<itemizedlist>
<listitem>
<para>A bean may be either a javax.jms.Topic or a javax.jms.Queue bean, which is
decided in the stanza "destination-type", eg.</para>
<programlisting><![CDATA[
<message-driven-destination>
<destination-type>javax.jms.Queue</destination-type>
<subscription-durability>NonDurable</subscription-durability>
</message-driven-destination>
]]></programlisting>
</listitem>
<listitem>
<para>If a bean is a Topic it may be either NonDurable or Durable, wich is described
in the stanza "subscription-durability".</para>
<programlisting><![CDATA[
<message-driven-destination>
<destination-type>javax.jms.Topic</destination-type>
<subscription-durability>Durable</subscription-durability>
</message-driven-destination>
]]></programlisting>
</listitem>
<listitem>
<para>A bean may be have transaction either as bean managed or container managed,
which is described in the stanza "transaction-type", eg.</para>
<programlisting><![CDATA[
<transaction-type>Container</transaction-type>
]]></programlisting>
</listitem>
<listitem>
<para>A bean managed bean may have an acknowledge type of either AUTO_ACKNOWLEDGE or
DUPS_OK_ACKNOWLEDGE. This is currently not supported in JBoss since the container
always are receiving messages under a transaction. But it is described in the stanza
"acknowledge-mode".</para>
<programlisting><![CDATA[
<transaction-type>Bean</transaction-type>
<acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
]]></programlisting>
</listitem>
<listitem>
<para>A container managed bean may either specify transactions as Required or
NotSupported, which is done in the container-transaction transaction part.</para>
<programlisting><![CDATA[
</container-transaction>
<container-transaction>
<method>
<ejb-name>DurableTopicBean</ejb-name>
<method-name>*</method-name>
</method>
<!-- May also be NotSupported -->
<trans-attribute>Required</trans-attribute>
</container-transaction>
]]></programlisting>
</listitem>
<listitem>
<para>An MDB may also specify a selector which follows the JMS syntax for a
selector. Which is specified in the stanza "message-selector".</para>
<programlisting><![CDATA[
<message-selector>JMSType='activityCompletion'</message-selector>
]]></programlisting>
</listitem>
</itemizedlist>
<para>In a message-driven stanza one may also have the normal stuff that may be in a
ejb deployment descriptor, such as "ejb-ref" and "resource-ref".</para>
</section>
<section><title>JBoss configuration</title>
<para>The meat of the configuration options are in the jboss.xml deployment
descriptor. This may be divided into two part. The necessary one that configures a
particular bean. And the optional one (that will be taken from standardjboss.xml if
not found and which configures the container. We will describe both here, but the
container configuration will be broken into several parts since it involves stuff
outside the jboss.xml file.</para>
<para>In the bean part one always have to specify the JNDI for the JMS destination.
In JBossMQ this always begins with either "topic/" or "queue/" followed by the name of
the destination.</para>
<programlisting><![CDATA[
<destination-jndi-name>queue/testObjectMessage</destination-jndi-name>
]]></programlisting>
<para>You also have to tell the beans name and the name of the container
configuration. To use the one in standardjboss.xml you should give the name "Standard
Message Driven Bean".</para>
<programlisting><![CDATA[
<message-driven>
<ejb-name>QueueBean</ejb-name>
<configuration-name>Standard Message Driven Bean</configuration-name>
<destination-jndi-name>queue/testQueue</destination-jndi-name>
</message-driven>
]]></programlisting>
<para>It is also possible to use a name and a password to log in to JBossMQ. These
may be used by them self. If you have specified a Durable Topic they are however
required. Then one also have to specify a client-id. All these stuff are configurable
in conf/default/jbossmq.xml. Using one of the default in that file we could have a
deployment descriptor looking like this:</para>
<programlisting><![CDATA[
<message-driven>
<ejb-name>DurableTopicBean</ejb-name>
<configuration-name>Standard Message Driven Bean</configuration-name>
<destination-jndi-name>topic/testDurableTopic</destination-jndi-name>
<mdb-user>john</mdb-user>
<mdb-passwd>needle</mdb-passwd>
<mdb-client-id>DurableSubscriberExample</mdb-client-id>
</message-driven>
]]></programlisting>
<para>The container stuff for MDB are in standardjboss.xml. It is however possible
to override this configuration by including it in the jboss.xml deployment descriptor.
I will first give you the complete look if doing like this, and then go through the
individual entries.</para>
<programlisting><![CDATA[
<?xml version="1.0" encoding="Cp1252"?>
<jboss>
<enterprise-beans>
<message-driven>
<ejb-name>ObjectMessageBean</ejb-name>
<configuration-name>My Config</configuration-name>
<destination-jndi-name>queue/testObjectMessage</destination-jndi-name>
</message-driven>
<secure>false</secure>
</enterprise-beans>
<container-configurations>
<container-configuration>
<container-name>My Config</container-name>
<call-logging>false</call-logging>
<container-invoker>org.jboss.ejb.plugins.jms.JMSContainerInvoker</container-invoker>
<container-interceptors>
<interceptor>org.jboss.ejb.plugins.LogInterceptor</interceptor>
<interceptor>org.jboss.ejb.plugins.SecurityInterceptor</interceptor>
<!-- CMT -->
<interceptor
transaction="Container">org.jboss.ejb.plugins.TxInterceptorCMT</interceptor>
<interceptor transaction="Container"
metricsEnabled="true">org.jboss.ejb.plugins.MetricsInterceptor</interceptor>
<interceptor
transaction="Container">org.jboss.ejb.plugins.MessageDrivenInstanceInterceptor</interceptor>
<!-- BMT -->
<interceptor
transaction="Bean">org.jboss.ejb.plugins.MessageDrivenInstanceInterceptor</interceptor>
<interceptor
transaction="Bean">org.jboss.ejb.plugins.MessageDrivenTxInterceptorBMT</interceptor>
<interceptor transaction="Bean"
metricsEnabled="true">org.jboss.ejb.plugins.MetricsInterceptor</interceptor>
</container-interceptors>
<instance-pool>org.jboss.ejb.plugins.MessageDrivenInstancePool</instance-pool>
<instance-cache></instance-cache>
<persistence-manager></persistence-manager>
<transaction-manager>org.jboss.tm.TxManager</transaction-manager>
<container-invoker-conf>
<JMSProviderAdapterJNDI>DefaultJMSProvider</JMSProviderAdapterJNDI>
<ServerSessionPoolFactoryJNDI>StdJMSPool</ServerSessionPoolFactoryJNDI>
<MaximumSize>15</MaximumSize>
<MaxMessages>1</MaxMessages>
<Optimized>True</Optimized>
</container-invoker-conf>
<container-pool-conf>
<MaximumSize>100</MaximumSize>
<MinimumSize>10</MinimumSize>
</container-pool-conf>
</container-configuration>
</container-configurations>
<resource-managers />
</container-configurations>
]]></programlisting>
<para>Here we go through some ways to configure the MDB container</para>
<section><title>Container invoker</title>
<para>The container invoker is what sends messages into the container system. It is
this that is responsible for handling everything that has to do with JMS. The rest of
the MDB container parts are basically agnostic to what kind of message system that the
container invoker uses. It is therefore possible to write a new container invoker for
other types of message system. Currently there are one limitation to this. The bean
class still has to implement the MessageListener interface and the invoker therefore
have to adopt to this interface. This will be made pluggable in a later release of
MDB. </para>
</section>
<section><title>Container interceptor</title>
<para>The container interceptor are an integral part of the container system and
each does something particular to fulfill the EJB container contract. All of them are
pluggable, meaning that it is possible to write new implementations of the and plug
them in for a bean with a particular need. This should be considered a very advanced
task.</para>
</section>
<section><title>Container invoker configuration</title>
<para>This is probably the most interesting part to understand. Lets look at it in
smaller pieces. First it defines a "JMSProviderAdapterJNDI":</para>
<programlisting><![CDATA[
<JMSProviderAdapterJNDI>DefaultJMSProvider</JMSProviderAdapterJNDI>
]]></programlisting>
<para>There should be a JNDI name to a JMX bean where one might lookup a provider
adapter class. This class is then used by the invoker to find the names of the
connection factories, all IntitialContext lookups are done through this class, to make
it possible to get access to a JMS provider outside of JBoss.</para>
<para>The name is by default bound to the JbossMQProvider in jboss.jcml</para>
<programlisting><![CDATA[
<mbean code="org.jboss.jms.jndi.JMSProviderLoader"
name=":service=JMSProviderLoader,name=JBossMQProvider">
<attribute name="ProviderName">DefaultJMSProvider</attribute>
<attribute
name="ProviderAdapterClass">org.jboss.jms.jndi.JBossMQProvider</attribute>
</mbean>
]]></programlisting>
<para>It is however possible to add more JMSProviders to the jboss.jcml and use them
in the container configuration. On possible reason to do this is if one want to
listen to a queue or topic in another JBoss server. The one could define another
provider and configure its context. Say we have a JBoss server on a machine
remote.com. We might then ad this to jboss.jcml:</para>
<programlisting><![CDATA[
<mbean code="org.jboss.jms.jndi.JMSProviderLoader"
name=":service=JMSProviderLoader,name=RemoteJMSProvider">
<attribute name="ProviderName">RemoteJMSProvider</attribute>
<attribute
name="ProviderAdapterClass">org.jboss.jms.jndi.JBossMQProvider</attribute>
<attribute name="ProviderUrl">remote.com:1099</attribute>
</mbean>
]]></programlisting>
<para>Note how we added a "ProviderUrl" attribute. In jboss.xml we would write this
in the JMSProviderAdapterJNDI element:</para>
<programlisting><![CDATA[
<JMSProviderAdapterJNDI>RemoteJMSProvider</JMSProviderAdapterJNDI>
]]></programlisting>
<para>OBSERVE. I have this working for non transacted connections. It has not bean
fully verified to work with the current JBossMQProvider.</para>
<para>Another way to use this configuration option is to integrate another JMS
provider into JBoss. This was actually how MDB was first implemented in JBoss through
the OpenJMS implementation. To do this one have to implement the interface
org.jboss.jms.jndi.JMSProviderAdapter. Be aware though that if the JMS provider does
not support the full JMS ASF (chapter 8 in the JMS spec) you will have to write a full
implementation of both the ProvuderAdapter and the ServerSession stuff.</para>
<para>Next we have the "ServerSessionPoolFactoryJNDI" element</para>
<programlisting><![CDATA[
<ServerSessionPoolFactoryJNDI>StdJMSPool</ServerSessionPoolFactoryJNDI>
]]></programlisting>
<para>This also points to a class loaded through jboss.jcml. It is the entry point
to the ServerSessionPool. If one needs to write a provider specific pool or do some
customization of the existing one, it would be possible to load that for a particular
bean. The existing one is defined this way in jboss.jcml:</para>
<programlisting><![CDATA[
<mbean code="org.jboss.jms.asf.ServerSessionPoolLoader"
name=":service=ServerSessionPoolMBean,name=StdJMSPool">
<attribute name="PoolName">StdJMSPool</attribute>
<attribute
name="PoolFactoryClass">org.jboss.jms.asf.StdServerSessionPoolFactory</attribute>
</mbean>
]]></programlisting>
<para>The first implementation of MDB for JBoss was based on another
ServerSessionPoolFactory, specially written for OpenJMS. This is currently not
verified to work. What do work is the pluggability of ServerSessionPool
factories.</para>
<para>The last two entries looks like this:</para>
<programlisting><![CDATA[
<MaximumSize>15</MaximumSize>
<MaxMessages>1</MaxMessages>
]]></programlisting>
<para>The first of these - "MaximumSize" - defines how large the pool will be, i.e
how many session it will have ready to serve incoming messages. The second one is used
to configure the maximum number of messages a session is allowed to handle at once. I
have never tweaked that one, and do not know if JBossMQ actually support that option.
It might enhance performance if used.</para>
</section>
</section>
</section>
</chapter>