MessageDriven Beans l l l The messagedriven bean
Message-Driven Beans l l l The message-driven bean was introduced in EJB 2. 0 to support the processing of asynchronous messages from a JMS provider. EJB 2. 1 expanded the definition of the messagedriven bean so that it can support any messaging system, not just JMS through the JCA. EJB 3. 0 does not really expand on the feature set of earlier specification versions, but it does simplify configuration with the use of annotations. 1
JMS and Message-Driven Beans l l l All EJB 3. 0 vendors must support a JMS provider. Most vendors have a JMS provider built in and must support other JMS providers through the JCA. However, regardless of whether your vendor has its own JMS provider or allows you to integrate some other provider, a JMS provider is an absolute necessity for supporting message-driven beans. By forcing the adoption of JMS, Sun has guaranteed that EJB developers can expect to have a working JMS provider on which messages can be both sent and received. 2
JMS as a Resource l l JMS is a vendor-neutral API that can be used to access enterprise messaging systems. Enterprise messaging systems (a. k. a. messageoriented middleware) facilitate the exchange of messages between software applications over a network. Applications that use JMS are called JMS clients , and the messaging system that handles routing and delivery of messages is called the JMS provider. In EJB, enterprise beans of all types can use JMS to send messages. 3
JMS as a Resource l l l The messages are consumed by other Java applications or by message-driven beans. JMS facilitates sending messages from enterprise beans using a messaging service , sometimes called a message broker or router. Message brokers have been around for a couple of decades - the oldest and most established is IBM's MQSeries - but JMS is fairly new, and it is specifically designed to deliver a variety of message types from one Java application to another. 4
Reimplementing the Travel. Agent EJB with JMS l We can modify the Travel. Agent EJB developed earlier so that it uses JMS to alert some other Java application that a reservation has been made. @Resource(mapped. Name="Connection. Factory. Name. Goes. Here") private Connection. Factory connection. Factory; @Resource(mapped. Name="Ticket. Topic") private Topic topic; @Remove public Ticket. DO book. Passage(Credit. Card. DO card, double price) throws Incomplete. Conversational. State { if (customer == null || cruise == null || cabin == null) { throw new Incomplete. Conversational. State( ); } try { Reservation reservation = new Reservation( customer, cruise, cabin, price, new Date( )); 5
Reimplementing the Travel. Agent EJB with JMS entity. Manager. persist(reservation); process. by. Credit(customer, card, price); Ticket. DO ticket = new Ticket. DO(customer, cruise, cabin, price); Connection connect = factory. create. Connection( ); Session session = connect. create. Session(true, 0); Message. Producer producer = session. create. Producer(topic); Text. Message text. Msg = session. create. Text. Message( ); text. Msg. set. Text(ticket. Description); producer. send(text. Msg); connect. close( ); return ticket; } catch(Exception e) { throw new EJBException(e); } } 6
Connection. Factory and Topic l l l In order to send a JMS message, we need a connection to the JMS provider and a destination address for the message. A JMS connection factory makes the connection to the provider possible; the destination address is identified by a Topic object. Both the connection factory and the Topic object are obtained by using @javax. annotation. Resource to inject these objects directly into the fields of the Travel. Agent EJB: 7
Connection. Factory and Topic @Resource(mapped. Name="Connection. Factory. Name. Goes. Here") private Connection. Factory connection. Factory; @Resource(mapped. Name="Ticket. Topic") private Topic topic; l l The Connection. Factory is similar to a Data. Source in JDBC. Just as the Data. Source provides a JDBC connection to a database, the Connection. Factory provides a JMS connection to a message router. 8
Connection and Session l The Connection. Factory is used to create a Connection, which is an actual connection to the JMS provider: Connection connect = connection. Factory. create. Connection( ); Session session = connect. create. Session(true, 0); l l Once you have a Connection , you can use it to create a Session. A Session allows you to group the actions of sending and receiving messages. In this case, you need only a single Session. Using multiple Sessions is helpful if you wish to produce and consume messages in different threads. 9
Connection and Session l l Session objects use a single-threaded model, which prohibits concurrent access to a single Session from multiple threads. The create. Session( ) method has two parameters: create. Session(boolean transacted, int acknowledge. Mode) l l According to the EJB specifications, these arguments are ignored at runtime because the EJB container manages the transaction and acknowledgment mode of any JMS resource obtained from the JNDI ENC. Unfortunately, not all vendors adhere to this part of the specification. Some vendors ignore these parameters; others do not. 10
Message. Producer l The Session is used to create a Message. Producer, which sends messages from the Travel. Agent EJB to the destination specified by the Topic object. Any JMS clients that subscribe to that topic will receive a copy of the message: Message. Producer producer = session. create. Producer(topic); Text. Message text. Msg = session. create. Text. Message( ); text. Msg. set. Text(ticket. Description); producer. send(text. Msg); 11
Message types l l In JMS, a message is a Java object with two parts: a header and a message body. The header is composed of delivery information and metadata, and the message body carries the application data, which can take several forms: text, serializable objects, byte streams, etc. The JMS API defines several message types (Text. Message, Map. Message, Object. Message , and others) and provides methods for delivering messages to and receiving messages from other applications. For example, we can change the Travel. Agent EJB so that it sends a Map. Message rather than a Text. Message: 12
Message types Ticket. DO ticket = new Ticket. DO(customer, cruise, cabin, price); . . . Message. Producer producer = session. create. Producer(topic); Map. Message map. Msg = session. create. Map. Message( ); map. Msg. set. Int("Customer. ID", ticket. customer. ID. int. Value( )); map. Msg. set. Int("Cruise. ID", ticket. cruise. ID. int. Value( )); map. Msg. set. Int("Cabin. ID", ticket. cabin. ID. int. Value( )); map. Msg. set. Double("Price", ticket. price); producer. send(map. Msg); l l l In addition to Text. Message, Map. Message, and Object. Message, JMS provides two other message types: Stream. Message and Bytes. Message. Stream. Message can take the contents of an I/O stream as its payload. Bytes. Message can take any array of bytes, which it treats as opaque data. 13
JMS Application Client l To get a better idea of how JMS is used, we can create a Java application whose sole purpose is receiving and processing reservation messages. This application is a simple JMS client that prints a description of each ticket as it receives the messages. We'll assume that the Travel. Agent EJB is using Text. Message to send a description of the ticket to the JMS clients. Here's how the JMS application client might look: import import javax. jms. Message; javax. jms. Text. Message; javax. jms. Connection. Factory; javax. jms. Connection; javax. jms. Session; javax. jms. Topic; javax. jms. JMSException; javax. naming. Initial. Context; 14
JMS Application Client public class Jms. Client_1 implements javax. jms. Message. Listener { public static void main(String [] args) throws Exception { if(args. length != 2) throw new Exception("Wrong number of arguments"); new Jms. Client_1(args[0], args[1]); while(true){Thread. sleep(10000); } } public Jms. Client_1(String factory. Name, String topic. Name) throws Exception { Initial. Context jndi. Context = get. Initial. Context( ); Connection. Factory factory = (Connection. Factory) jndi. Context. lookup("Connection. Factory. Name. Goes. Here"); Topic topic = (Topic)jndi. Context. lookup("Topic. Name. Goes. Here"); Connection connect = factory. create. Connection( ); Session session = connect. create. Session(false, Session. AUTO_ACKNOWLEDGE); 15
JMS Application Client Message. Consumer consumer = session. create. Consumer(topic); consumer. set. Message. Listener(this); connect. start( ); } public void on. Message(Message message) { try { Text. Message text. Msg = (Text. Message)message; String text = text. Msg. get. Text( ); System. out. println("n RESERVATION RECEIVEDn"+text); } catch(JMSException jms. E) { jms. E. print. Stack. Trace( ); } } public static Initial. Context get. Initial. Context( ) { // create vendor-specific JNDI context here } } 16
JMS Application Client l l The Message. Consumer can receive messages directly or delegate message processing to a javax. jms. Message. Listener. We chose to have Jms. Client_1 implement the Message. Listener interface so that it can process the messages itself. Message. Listener objects implement a single method, on. Message( ), which is invoked every time a new message is sent to the subscriber's topic. In this case, every time the Travel. Agent EJB sends a reservation message to the topic, the JMS client's on. Message( ) method is invoked to receive and process a copy of the message: 17
JMS Application Client public void on. Message(Message message) { try { Text. Message text. Msg = (Text. Message)message; String text = text. Msg. get. Text( ); System. out. println("n RESERVATION RECEIVED: n"+text); } catch(JMSException jms. E) { jms. E. print. Stack. Trace( ); } } 18
JMS Is Asynchronous l l One of the principal advantages of JMS messaging is that it's asynchronous. In other words, a JMS client can send a message without having to wait for a reply. Contrast this flexibility with the synchronous messaging of Java RMI or JAX-RPC. Each time a client invokes a bean's method, it blocks the current thread until the method completes execution. This lock-step processing makes the client dependent on the availability of the EJB server, resulting in a tight coupling between the client and the enterprise bean. JMS clients send messages asynchronously to a destination (topic or queue) from which other JMS clients can also receive messages. 19
JMS Is Asynchronous l A Travel. Agent session bean can use JMS to notify other applications that a reservation has been processed, as shown in below. Using JMS with the Travel. Agent EJB 20
JMS Is Asynchronous l l In this case, the applications receiving JMS messages from the Travel. Agent EJB may be message-driven beans, other Java applications in the enterprise, or applications in other organizations that benefit from being notified that a reservation has been processed. Because messaging is inherently decoupled and asynchronous, the transactions and security contexts of the sender are not propagated to the receiver. 21
JMS Messaging Models l l JMS provides two types of messaging models: publish-and-subscribe and point-to-point. The JMS specification refers to these as messaging domains. In JMS terminology, publish-andsubscribe and point-to-point are frequently shortened to pub/sub and p 2 p (or PTP), respectively. In the simplest sense, publish-and-subscribe is intended for a one-to-many broadcast of messages, and point-to-point is intended for one-to -one delivery of messages (see Figure below). 22
JMS Messaging Models JMS messaging domains 23
Publish-and-subscribe l l l In publish-and-subscribe messaging, one producer can send a message to many consumers through a virtual channel called a topic. Consumers can choose to subscribe to a topic. Any messages addressed to a topic are delivered to all the topic's consumers. The pub/sub messaging model is largely a pushbased model , in which messages are automatically broadcast to consumers without the consumers having to request or poll the topic for new messages. 24
Point-to-point l l l The point-to-point messaging model allows JMS clients to send and receive messages both synchronously and asynchronously via virtual channels known as queues. The p 2 p messaging model has traditionally been a pull- or polling-based model, in which messages are requested from the queue instead of being pushed to the client automatically. A queue may have multiple receivers, but only one receiver may receive each message. 25
Session Beans Should Not Receive Messages l l Jms. Client_1 was designed to consume messages produced by the Travel. Agent EJB. Can another session bean receive those messages also? The answer is yes, but it's a really bad idea. For example, when the business method on the Hypothetical EJB is called, it sets up a JMS session and then attempts to read a message from a queue: @Stateless public class Hypothetical. Bean implements Hypothetical. Remote { @Resource(mapped. Name="Connection. Factory"); private Connection. Factory factory; @Resource(mapped. Name="My. Queue") private Queue queue; 26
Session Beans Should Not Receive Messages public String business. Method( ) { try{ Connection connect = factory. create. Connection( ); Session session = connect. create. Session(true, 0); Message. Consumer receiver = session. create. Consumer(queue); Text. Message text. Msg = (Text. Message)receiver. receive( ); connect. close( ); return text. Msg. get. Text( ); } catch(Exception e) { throws new EJBException(e); } }. . . } 27
Session Beans Should Not Receive Messages l l The message consumer is used to proactively fetch a message from the queue. While this operation has been programmed correctly, it is dangerous because a call to the Message. Consumer. receive( ) method blocks the thread until a message becomes available. If a message is never delivered, the thread is blocked indefinitely! If no one ever sends a message to the queue, the Message. Consumer just sits there waiting, forever. 28
JMS-Based Message-Driven Beans l l l Message-driven beans (MDBs) are stateless, server-side, transaction-aware components for processing asynchronous messages delivered via JMS. While a message-driven bean is responsible for processing messages, its container manages the component's environment, including transactions, security, resources, concurrency, and message acknowledgment. It's particularly important to note that the container manages concurrency. 29
JMS-Based Message-Driven Beans • The thread safety provided by the container gives MDBs a significant advantage over traditional JMS clients, which must be custom built to manage resources, transactions, and security in a multithreaded environment. • An MDB can process hundreds of JMS messages concurrently because numerous instances of the MDB can execute concurrently in the container. • While a message-driven bean has a bean class, it does not have a remote or local business interface. These interfaces are absent because the message-driven bean responds only to asynchronous messages. 30
The Reservation. Processor EJB l l The Reservation. Processor EJB is a messagedriven bean that receives JMS messages notifying it of new reservations. The Reservation. Processor EJB is an automated version of the Travel. Agent EJB that processes reservations sent via JMS. These messages might come from another application in the enterprise or from an application in some other organization - perhaps another travel agent. This process is illustrated in the figure below. 31
The Reservation. Processor EJB processing reservations 32
The Reservation. Processor. Bean Class l l Here is a partial definition of the Reservation. Processor. Bean class. Some methods are left empty; they will be filled in later. Notice that the on. Message( ) method contains the business logic; it is similar to the business logic developed in the book. Passage( ) method of the Travel. Agent EJB described in previous Lecture 11. Here's the code: package com. titan. reservationprocessor; import javax. jms. *; import com. titan. domain. *; import com. titan. processpayment. *; import com. titan. travelagent. *; import java. util. Date; import javax. ejb. *; import javax. annotation. *; import javax. persistence. *; 33
The Reservation. Processor. Bean Class @Message. Driven(activation. Config={ @Activation. Config. Property( property. Name="destination. Type", property. Value="javax. jms. Queue"), @Activation. Config. Property( property. Name="message. Selector", property. Value="Message. Format = 'Version 3. 4'"), @Activation. Config. Property( property. Name="acknowledge. Mode", property. Value="Auto-acknowledge")}) public class Reservation. Processor. Bean implements javax. jms. Message. Listener { @Persistence. Context(unit. Name="titan. DB") private Entity. Manager em; @EJB private Process. Payment. Local process; @Resource(mapped. Name="Connection. Factory") private Connection. Factory connection. Factory; 34
The Reservation. Processor. Bean Class public void on. Message(Message message) { try { Map. Message reservation. Msg = (Map. Message)message; int customer. Pk = reservation. Msg. get. Int("Customer. ID"); int cruise. Pk = reservation. Msg. get. Int("Cruise. ID"); int cabin. Pk = reservation. Msg. get. Int("Cabin. ID"); double price = reservation. Msg. get. Double("Price"); // get the credit card Date expiration. Date = new Date(reservation. Msg. get. Long("Credit. Card. Exp. Date")); String card. Number = reservation. Msg. get. String("Credit. Card. Num"); String card. Type = reservation. Msg. get. String("Credit. Card. Type"); Credit. Card. DO card = new Credit. Card. DO(card. Number, expiration. Date, card. Type); Customer customer = em. find(Customer. class, customer. Pk); 35
The Reservation. Processor. Bean Class Cruise cruise = em. find(Cruise. class, cruise. Pk); Cabin cabin = em. find(Cabin. class, cabin. Pk); Reservation reservation = new Reservation( customer, cruise, cabin, price, new Date( )); em. persist(reservation); process. by. Credit(customer, card, price); Ticket. DO ticket = new Ticket. DO(customer, cruise, cabin, price); deliver. Ticket(reservation. Msg, ticket); } catch(Exception e) { throw new EJBException(e); } } public void deliver. Ticket(Map. Message reservation. Msg, Ticket. DO ticket) { // send it to the proper destination } 36 }
Message. Driven. Context l Message-driven beans also have a context object that is similar in functionality to that of the javax. ejb. Session. Context described in Lecture 11. This object may be injected using the @javax. annotation. Resource annotation: @Resource Message. Driven. Context context; l l The Message. Driven. Context simply extends the EJBContext ; it does not add any new methods. Only the transactional methods that Message. Driven. Context inherits from EJBContext are available to message-driven beans. 37
Message. Driven. Context l l The home methods - get. EJBHome( ) and get. EJBLocal. Home( ) - throw a Runtime. Exception if invoked, because MDBs do not have home interfaces or EJB home objects. The security methods - get. Caller. Principal( ) and is. Caller. In. Role( ) - also throw a Runtime. Exception if invoked on a Message. Driven. Context. When an MDB services a JMS message, there is no "caller, " so there is no security context to be obtained from the caller. Remember that JMS is asynchronous and doesn't propagate the sender's security context to the receiver that wouldn't make sense, since senders and receivers tend to operate in different environments. 38
Message. Listener interface l MDBs usually implement the javax. jms. Message. Listener interface, which defines the on. Message( ) method. This method processes the JMS messages received by a bean. package javax. jms; public interface Message. Listener { public void on. Message(Message message); } l Although MDBs usually implement this interface, we will see later in this chapter that MDBs can integrate with other messaging systems that define a different interface contract. 39
Taskflow and integration for B 2 B: on. Message( ) l l The on. Message( ) method is where all the business logic goes. As messages arrive, the container passes them to the MDB via the on. Message( ) method. When the method returns, the MDB is ready to process a new message. In the Reservation. Processor EJB, the on. Message( ) method extracts information about a reservation from a Map. Message and uses that information to create a reservation in the system: 40
Taskflow and integration for B 2 B: on. Message( ) public void on. Message(Message message) { try { Map. Message reservation. Msg = (Map. Message)message; int customer. Pk = reservation. Msg. get. Int("Customer. ID"); int cruise. Pk = reservation. Msg. get. Int("Cruise. ID"); int cabin. Pk = reservation. Msg. get. Int("Cabin. ID"); double price = reservation. Msg. get. Double("Price"); // get the credit card Date expiration. Date = new Date(reservation. Msg. get. Long("Credit. Card. Exp. Date")); String card. Number = reservation. Msg. get. String("Credit. Card. Num"); String card. Type = reservation. Msg. set. String("Credit. Card. Type"); Credit. Card. DO card = new Credit. Card. DO(card. Number, expiration. Date, card. Type); 41
Taskflow and integration for B 2 B: on. Message( ) l JMS is frequently used as an integration point for business-to-business (B 2 B) applications, so it's easy to imagine the reservation message coming from one of Titan's business partners (perhaps a third-party processor or branch travel agency). 42
Sending messages from a message-driven bean l An MDB can also send messages using JMS. The deliver. Ticket( ) method sends the ticket information to a destination defined by the sending JMS client: public void deliver. Ticket(Map. Message reservation. Msg, Ticket. DO ticket) throws JMSException{ Queue queue = (Queue)reservation. Msg. get. JMSReply. To( ); Connection connect = connection. Factory. create. Connection( ); Session session = connect. create. Session(true, 0); Message. Producer sender = session. create. Producer(queue); Object. Message message = session. create. Object. Message( ); message. set. Object(ticket); sender. send(message); connect. close( ); } 43
@Message. Driven l l l MDBs are identified using the @javax. ejb. Message. Driven annotation or, alternatively, are described in an EJB deployment descriptor. An MDB can be deployed alone, but it's more often deployed with the other enterprise beans that it references. For example, the Reservation. Processor EJB uses the Process. Payment EJB as well as the Titan Entity. Manager, so it is feasible to deploy all of these beans within the same Java EE deployment. 44
@Activation. Config. Property l l We'll see later that because MDBs can receive messages from arbitrary messaging providers, the configuration must be very flexible to be able to describe the proprietary properties that different providers will have. JCA-based MDBs don't necessarily use JMS as the message service, so this requirement is very important. To facilitate this, the @Message. Driven. activation. Config( ) attribute takes an array of @Activation. Config. Property annotations. These annotations are simply a set of name/value pairs that describe the configuration of your MDB. 45
@Activation. Config. Property @Message. Driven(activation. Config={ @Activation. Config. Property( property. Name="destination. Type", property. Value="javax. jms. Queue"), @Activation. Config. Property( property. Name="message. Selector", property. Value="Message. Format = 'Version 3. 4'"), @Activation. Config. Property( property. Name="acknowledge. Mode", property. Value="Auto-acknowledge")}) public class Reservation. Processor. Bean implements javax. jms. Message. Listener {. . . } 46
Message selector l l An MDB can declare a message selector. Message selectors allow an MDB to be more selective about the messages it receives from a particular topic or queue. Message selectors use Message properties as criteria in conditional expressions. These conditional expressions use Boolean logic to declare which messages should be delivered. A message selector is declared using the standard property name, message. Selector, in an activation configuration element: Message selectors are also based on message headers, which are outside the scope of this chapter. 47
Message selector @Activation. Config. Property( property. Name="message. Selector", property. Value="Message. Format = 'Version 3. 4'"), l l The Reservation. Processor EJB uses a message selector filter to select messages of a specific format. In this case, the format is "Version 3. 4"; this is a string that Titan uses to identify messages of type Map. Message that contain the name values Customer. ID, Cruise. ID, Cabin. ID, Credit. Card, and Price. In other words, adding a Message. Format to each reservation message allows us to write MDBs that are designed to process different kinds of reservation messages. 48
Message selector l l If a new business partner needs to use a different type of Message object, Titan would use a new message version and an MDB to process it. Here's how a JMS producer would go about setting a Message. Format property on a Message: Message message = session. create. Map. Message( ); message. set. String. Property("Message. Format", "Version 3. 4"); // set the reservation named values sender. send(message); 49
Message selector l l The message selectors are based on a subset of the SQL-92 conditional expression syntax that is used in the WHERE clauses of SQL statements. They can become fairly complex, including the use of literal values, Boolean expressions, unary operators, and so on. 50
Acknowledge mode l l A JMS acknowledgment means that the JMS client notifies the JMS provider (message router) when a message is received. In EJB, it's the MDB container's responsibility to send an acknowledgment when it receives a message. Acknowledging a message tells the JMS provider that an MDB container has received and processed the message. Without an acknowledgment, the JMS provider does not know whether the MDB container has received the message, and unwanted redeliveries can cause problems. For example, once we have processed a reservation message using the Reservation. Processor EJB, we don't want to receive the same message again. 51
Acknowledge mode l The acknowledgment mode is set using the standard acknowledge. Mode activation configuration property, as shown in the following code snippet: @Activation. Config. Property( property. Name="acknowledge. Mode", property. Value="Auto-acknowledge") l Two values can be specified for acknowledgment mode: Auto-acknowledge and Dups-ok-acknowledge. Auto-acknowledge tells the container that it should send an acknowledgment to the JMS provider soon after the message is given to an MDB instance to process. 52
Acknowledge mode l l l Dups-ok-acknowledge tells the container that it doesn't have to send the acknowledgment immediately; anytime after the message is given to the MDB instance will be fine. With Dups-ok-acknowledge , it's possible for the MDB container to delay acknowledgment for so long that the JMS provider assumes the message was not received and sends a "duplicate" message. Obviously, with Dups-ok-acknowledge, your MDBs must be able to handle duplicate messages correctly. 53
Subscription durability l l When a JMS-based MDB uses a javax. jms. Topic , the deployment descriptor must declare whether the subscription is Durable or Non. Durable. A Durable subscription outlasts an MDB container's connection to the JMS provider, so if the EJB server suffers a partial failure, shuts down, or otherwise disconnects from the JMS provider, the messages that it would have received are not lost. The provider stores any messages that are delivered while the container is disconnected; the messages are delivered to the container (and from there to the MDB) when the container reconnects. This behavior is commonly referred to as store-andforward messaging. 54
Subscription durability l l MDBs are tolerant of disconnections, whether intentional or the result of a partial failure. If the subscription is Non. Durable, any messages the bean would have received while it was disconnected are lost. Developers use Non. Durable subscriptions when it is not critical for all messages to be processed. Using a Non. Durable subscription improves the performance of the JMS provider but significantly reduces the reliability of the MDBs. 55
Subscription durability @Activate. Config. Property( property. Name="subscription. Durability", property. Value="Durable") l l When the destination type is javax. jms. Queue , as is the case in the Reservation. Processor EJB, durability is not a factor because of the nature of queue-based messaging systems. With a queue, messages may be consumed only once and they remain in the queue until they are distributed to one of the queue's listeners. 56
The XML Deployment Descriptor l Here is a deployment descriptor that provides a complete annotation-alternative definition of the Reservation. Processor EJB: <? xml version="1. 0"? > <ejb-jar xmlns="http: //java. sun. com/xml/ns/javaee" xmlns: xsi="http: //www. w 3. org/2001/XMLSchema-instance" xsi: schema. Location="http: //java. sun. com/xml/ns/javaee/ejbjar_3_0. xsd" version="3. 0"> <enterprise-beans> <message-driven> <ejb-name>Reservation. Processor. Bean</ejb-name> <ejb-class> com. titan. reservationprocessor. Reservation. Processor. Bean </ejb-class> <messaging-type>javax. jms. Message. Listener</messaging-type> 57
The XML Deployment Descriptor <transaction-type>Container</transaction-type> <message-destination-type> javax. jms. Queue </message-destination-type> <activation-config> <activation-property> <activation-config-property-name>destination. Type </activation-config-property-name> <activation-config-property-value>javax. jms. Queue </activation-config-property-value> <activation-property> <activation-config-property-name>message. Selector </activation-config-property-name> <activation-config-property-value>Message. Format = 'Version 3. 4' </activation-config-property-value> <activation-property> 58
The XML Deployment Descriptor <activation-property> <activation-config-property-name>acknowledge. Mode </activation-config-property-name> <activation-config-property-value>Auto-acknowledge </activation-config-property-value> <activation-property> </activation-config> <ejb-local-ref> <ejb-ref-name>ejb/Payment. Processor</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <local>com. titan. processpayment. Process. Payment. Local</local> <injection-target-class> com. titan. reservationprocessor. Reservation. Processor. Bean </injection-target-class> <injection-target-name>process</injection-target-name> </injection-target> </resource-ref> <persistence-context-ref-name> 59
The XML Deployment Descriptor persistence/titan </persistence-context-ref-name> <persistence-unit-name>titan</persistence-unit-name> <injection-target-class> com. titan. reservationprocessor. Reservation. Processor. Be an </injection-target-class> <injection-target-name>em</injection-target-name> </injection-target> </env-entry> <resource-ref-name>jms/Connection. Factory</resource-ref-name> <resource-type>javax. jms. Connection. Factory</res-type> <res-auth>Container</res-auth> <mapped-name>Connection. Factory</mapped-name> <injection-target-class> com. titan. reservationprocessor. Reservation. Processor. Bean </injection-target-class> <injection-target-name>datasource</injection-target-name> </injection-target> </resource-ref> </message-driven> </enterprise-beans> 60 </ejb-jar>
The Reservation. Processor Clients l In order to test the Reservation. Processor EJB, we need to develop two new client applications: one to send reservation messages and the other to consume ticket messages produced by the Reservation. Processor EJB. 61
The reservation message producer l The Jms. Client_Reservation. Producer sends 100 reservation requests very quickly. The speed with which it sends these messages forces many containers to use multiple MDB instances to process them. The code for Jms. Client_Reservation. Producer looks like this: import import import javax. jms. Message; javax. jms. Map. Message; javax. jms. Connection. Factory; javax. jms. Connection; javax. jms. Session; javax. jms. Queue; javax. jms. Message. Producer; javax. jms. JMSException; javax. naming. Initial. Context; java. util. Date; com. titan. processpayment. Credit. Card. DO; 62
The reservation message producer public class Jms. Client_Reservation. Producer { public static void main(String [] args) throws Exception { Initial. Context jndi. Context = get. Initial. Context( ); Connection. Factory factory = (Connection. Factory) jndi. Context. lookup("Connection. Factory. Name. Goes. Here"); Queue reservation. Queue = (Queue) jndi. Context. lookup("Queue. Name. Goes. Here"); Connection connect = factory. create. Connection( ); Session session = connect. create. Session(false, Session. AUTO_ACKNOWLEDGE); Message. Producer sender = session. create. Producer(reservation. Queue); for(int i = 0; i < 100; i++){ Map. Message message = session. create. Map. Message( ); message. set. String. Property("Message. Format", "Version 3. 4"); 63
The reservation message producer message. set. Int("Cruise. ID", 1); message. set. Int("Customer. ID", i%10); message. set. Int("Cabin. ID", i); message. set. Double("Price", (double)1000+i); // the card expires in about 30 days Date expiration. Date = new Date(System. current. Time. Millis( )+43200000); message. set. String("Credit. Card. Num", "92383029"); message. set. Long("Credit. Card. Exp. Date", expiration. Date. get. Time( )); message. set. String("Credit. Card. Type", Credit. Card. DO. MASTER_CARD); sender. send(message); } connect. close( ); } public static Initial. Context get. Initial. Context( ) throws JMSException { // create vendor-specific JNDI context here } } 64
The ticket message consumer l l The Jms. Client_Ticket. Consumer is designed to consume all the ticket messages delivered by Reservation. Processor EJB instances to the queue. It consumes the messages and prints out the descriptions: import import import javax. jms. Message; javax. jms. Object. Message; javax. jms. Connection. Factory; javax. jms. Connection; javax. jms. Session; javax. jms. Queue; javax. jms. Message. Consumer; javax. jms. JMSException; javax. naming. Initial. Context; com. titan. travelagent. Ticket. DO; 65
The ticket message consumer public class Jms. Client_Ticket. Consumer implements javax. jms. Message. Listener { public static void main(String [] args) throws Exception { new Jms. Client_Ticket. Consumer( ); while(true){Thread. sleep(10000); } } public Jms. Client_Ticket. Consumer( ) throws Exception { Initial. Context jndi. Context = get. Initial. Context( ); Connection. Factory factory = (Connection. Factory) jndi. Context. lookup("Queue. Factory. Name. Goes. Here"); Queue ticket. Queue = (Queue)jndi. Context. lookup("Queue. Name. Goes. Here"); Connection connect = factory. create. Connection( ); Session session = connect. create. Session(false, Session. AUTO_ACKNOWLEDGE); Message. Consumer receiver = session. create. Consumer(ticket. Queue); receiver. set. Message. Listener(this); connect. start( ); } 66
The ticket message consumer public void on. Message(Message message) { try { Object. Message obj. Msg = (Object. Message)message; Ticket. DO ticket = (Ticket. DO)obj. Msg. get. Object( ); System. out. println("****************"); System. out. println(ticket); System. out. println("****************"); } catch(JMSException jms. E) { jms. E. print. Stack. Trace( ); } } public static Initial. Context get. Initial. Context( ) throws JMSException { // create vendor-specific JNDI context here } } 67
The Life Cycle of a Message-Driven Bean l l Just as session beans have well-defined life cycles, so does the MDB bean. The MDB instance's life cycle has two states: Does Not Exist and Method-Ready Pool. The Method-Ready Pool is similar to the instance pool used for stateless session beans. Figure below illustrates the states and transitions that 68
The Life Cycle of a Message-Driven Bean MDB life cycle 69
The Does Not Exist State l l When an MDB instance is in the Does Not Exist state, it is not an instance in the memory of the system. In other words, it has not been instantiated yet. 70
The Method-Ready Pool l MDB instances enter the Method-Ready Pool as the container needs them. When the EJB server is first started, it may create a number of MDB instances and enter them into the Method-Ready Pool. (The actual behavior of the server depends on the implementation. ) When the number of MDB instances handling incoming messages is insufficient, more can be created and added to the pool. 71
The Method-Ready Pool l MDBs are not subject to activation, so they can maintain open connections to resources for their entire life cycles. The @Pre. Destroy method should close any open resources before the stateless session bean is evicted from memory at the end of its life cycle. You'll read more about @Pre. Destroy later in this section. 72
Life in the Method-Ready Pool l l When a message is delivered to an MDB, it is delegated to any available instance in the Method-Ready Pool. While the instance is executing the request, it is unavailable to process other messages. The MDB can handle many messages simultaneously, delegating the responsibility of handling each message to a different MDB instance. When a message is delegated to an instance by the container, the MDB instance's Message. Driven. Context changes to reflect the new transaction context. 73
Life in the Method-Ready Pool l Once the instance has finished, it is immediately available to handle a new message. Bean instances leave the Method-Ready Pool for the Does Not Exist state when the server no longer needs them - that is, when the server decides to reduce the total size of the Method-Ready Pool by evicting one or more instances from memory. The process begins by invoking an @Pre. Destroy callback method on the bean instance. 74
Connector-Based Message-Driven Beans l l l Although the JMS-based MDB has proven very useful, it has limitations. Perhaps the most glaring limitation is that EJB vendors are able to support only a small number of JMS providers (usually one). In pre-EJB 2. 1 days, most vendors supported only their own JMS provider, and no others. Obviously, this limits your choices: if your company or a partner company uses a JMS provider that is not supported by your EJB vendor, you will not be able to process messages from that JMS provider. Here is the hypothetical email messaging interface that must be implemented by an email MDB: 75
Connector-Based Message-Driven Beans package com. vendorx. email; public interface Email. Listener { public void on. Message(javax. mail. Message message); } l The bean class that implements this interface is responsible for processing email messages delivered by the Email Connector. The following code shows an MDB that implements the Email. Listener interface and processes email: package com. titan. email; @Message. Driven(activation. Config={ @Activation. Config. Property( property. Name="mail. Server", property. Value="mail. ispx. com"), 76
Connector-Based Message-Driven Beans @Activation. Config. Property( property. Name="server. Type", property. Value="POP 3 "), @Activation. Config. Property( property. Name="message. Filter", property. Value="to='submit@titan. com'")}) public class Email. Bean implements com. vendorx. email. Email. Listener { public void on. Message(javax. mail. Message message){ javax. mail. internet. Mime. Message msg = (javax. mail. internet. Mime. Message) message; Address [] addresses = msg. get. From( ); // continue processing Email message } } 77
Connector-Based Message-Driven Beans l l l In this example, the container calls on. Message( ) to deliver a Java. Mail Message object, which represents an email message including MIME attachments. However, the messaging interfaces used by a Connectorbased MDB don't have to use on. Message( ). The method name and method signature can be whatever is appropriate to the EIS; it can even have a return type. For example, a Connector might be developed to handle request/reply-style messaging for SOAP. This connector might use the Req. Resp. Listener defined by the Java API for XML Messaging (JAXM), which is a SOAP messaging API defined by Sun Microsystems that is not part of the Java EE platform: 78
Connector-Based Message-Driven Beans package javax. xml. messaging; import javax. xml. soap. SOAPMessage; public interface Req. Resp. Listener { public SOAPMessage on. Message(SOAPMessage message); } l l In this interface, on. Message( ) has a return type of SOAPMessage. This means the EJB container and Connector are responsible for coordinating the reply message back to the sender (or to some destination defined in the deployment descriptor). In addition to supporting different method signatures, the messaging interface may have several methods for processing different kinds of messages using the same MDB. 79
Connector-Based Message-Driven Beans l The activation configuration properties used with non. JMS-based MDBs depend on the type of Connector and its requirements. Let's see an example of this: @Message. Driven(activation. Config={ @Activation. Config. Property( property. Name="mail. Server", property. Value="mail. ispx. com"), @Activation. Config. Property( property. Name="server. Type", property. Value="POP 3"), @Activation. Config. Property( property. Name="message. Filter", property. Value="to='submit@titan. com'")}) 80
Connector-Based Message-Driven Beans l l We talked about @Activation. Config. Property annotations before. As you can see from the preceding example, any name/value pair is supported within this annotation, so it can easily support the emailspecific configuration for this Connector type. 81
- Slides: 81