|[ Team LiB ]|
JMS Core Architecture
The core architecture behind JMS is depicted in Figure 12.5. Here we see that JNDI is used to create an initial context to a JMS connection factory that is then used to create connections to JMS-based service providers. Given a JMS connection, a particular session context can be retrieved to create message producers and message consumers. Messages sent from a producer to a consumer are associated with a particular endpoint destination. At the consumer end of a messaging session, filtering of messages can be achieved using a message selector String.
Figure 12.6 depicts the detailed architecture behind JMS connections. JMS connections represent a connection between a JMS client and a JMS service provider's messaging server. The JMS connection-related interfaces shown here are base interfaces that are further extended for the two messaging domain models of JMS. JMS v1.1 also allows a JMS client to use the base interfaces directly in a more domain-independent fashion.
A ConnectionFactory interface is used to create a connection to a particular JMS service provider's message service. JNDI is used to look up a handle to an initial ConnectionFactory object managed by a JMS service provider. Sub-interfaces of ConnectionFactory and objects that implement the ConnectionFactory interface provide methods for returning specific Connection object instances. New to JMS v1.1, a JMS client can also use a base ConnectionFactory object directly to create handles to generic Connection objects that ultimately may encapsulate connections to either point-to-point or publish-subscribe messaging models. The ConnectionFactory.createConnection() method can be used to create handles to Connection objects either by using a default user identity or by passing in a username and password explicitly.
A Connection interface encapsulates a JMS client's connection with a JMS service-provider instance. When a connection is created, it is in a stopped mode. That is, the consumers associated with the connection do not receive messages until Connection.start() is called. Connection.stop() can be used to return the connection to stopped mode. The Connection.close() method is called when the JMS client is finished with the connection and wants to clean up resources.
It should be noted that the JMSException class is an exception that is thrown by nearly every JMS API method call. It is also the root class for all other JMS exceptions. A JMSException can have a JMS service provider–specific error code String associated with it. A JMSException can also return its link to a lower-level exception that was responsible for the higher-level messaging exception. A JMS client can be notified of asynchronously occurring exceptions that are associated with the connection. The JMS client can register an object implementing the ExceptionListener interface with the connection by calling the Connection.setExceptionListener() method. The ExceptionListener implements the single onException() method to be notified of the JMSException occurring on the connection. As with any listener, the exception listener must be sure to provide an onException() implementation that can handle multiple client threads attempting to notify the listener. The listener may be retrieved by a call to Connection.getExceptionListener().
A JMS service provider–specific client ID associated with a connection can be retrieved with Connection.getClientID() and set with Connection.setClientID(). The client ID will typically be associated with a connection by the JMS service provider when creating the connection using the ConnectionFactory. If the JMS client attempts to set the client ID using setClientID(), an IllegalStateException will be thrown in the event that the ID set by the client is not permitted by the JMS service provider. If the client does attempt to set a client ID, it will be set immediately upon connection creation before any further actions are taken.
Metadata about the connection can also be returned from the Connection object using the getMetaData() call. The ConnectionMetaData object that is returned can provide the named version, major version numbers, and minor version numbers for both the JMS API and the service-provider code being used. Additionally, an enumeration of JMS properties associated with the connection can be returned from the ConnectionMetaData object.
Figure 12.7 depicts the core relations involved in JMS sessions. A JMS session is associated with a connection and represents a context within which messages are created. A session may be used to define a transaction within whose boundaries may exist a set of messages being sent and received within the transaction. Thus, all such messages may be contained within an atomic transaction.
The Session interface encapsulates the context within which JMS messages are created and received. The Session interface also extends the java.lang.Runnable interface, signifying that each session runs in the context of a single thread. The Connection.createSession() method can be used to create a handle to a Session object. A boolean parameter passed inside of the method indicates whether the session is to be transactional or not. As we'll discuss shortly, the mode for acknowledging received messages may also be specified in the createSession() when creating a Session object. The Session.close() method is used to close a session resource when the JMS client is finished with its use.
A set of createXXXMessage() methods on the Session interface are used to create instances of messages that are to be associated with a session. Messages that are created using the same Session object are stored in the same session context until they're committed for delivery using the Session.commit() method if the particular Session object instance is transaction-sensitive. If Session.rollback() is called, the messages created within the context of this session are not sent. Such commit and rollback semantics ensure that messages created within the same session context are either all sent or that none is sent. The Session.getTransacted() method can be called to determine whether a session is transaction-aware via the return of a boolean value.
Session objects that are not transaction-aware require an acknowledgment that a message was received. Such messages are then marked as sent and will not be redelivered. Messages in transaction-aware sessions are delivered when they are sent and do not wait for a commit or rollback command to be issued on the session. Optionally, a Session.recover() method may be called to stop the current delivery of messages and to restart the message delivery process for all messages that have yet to be acknowledged as delivered. Three static public constants designate the mode in which messages are sent:
If the session is instead transactional, the Session.SESSION_TRANSACTED constant value is associated with the acknowledgment mode.
Listeners attached to sessions may also be used to enable more asynchronous callback behavior. A MessageListener interface implementation can be associated with a Session object to enable such behavior. The MessageListener implements an onMessage() method that takes a Message object as a parameter for any message that is received by this Session object. The Session object's setMessageListener() and getMessageListener() set and get the MessageListener, respectively.
JMS Session Pools
JMS also provides a set of standard abstractions that JMS messaging servers implement to manage a pool of JMS sessions. Figure 12.8 depicts such abstractions and relevant methods on other JMS classes. Here, a connection is used to create a handle to an object that encapsulates a consumer of messages. Such a consumer is associated with a pool of JMS sessions on the messaging server.
A ServerSession object is a wrapper object used by messaging server environments to wrap a handle to a JMS Session object. The ServerSession.getSession() method is used to retrieve the handle to the wrapped Session object. A start() method on the ServerSession object is used to ultimately induce the invocation of the Session object's run() method to start the thread associated with the session. A ServerSessionPool object encapsulates a managed collection of ServerSession objects in a messaging server's pool of such objects. The ServerSessionPool.getServerSession() returns a handle to one such ServerSession object from its pool.
The ConnectionConsumer interface encapsulates a consumer of messages associated with a particular connection. A ConnectionConsumer is associated with a particular destination point to which messages are delivered and as encapsulated by the Destination marker interface. Concrete implementations of such a Destination interface represent endpoints for the particular messaging domain model being used. A ConnectionConsumer is also associated with a String representation of a message selector which signals that only those messages with a particular message property are to be processed by the ConnectionConsumer, or indicates a null/empty-string if all messages are to be processed. A ConnectionConsumer also has a maximum bound for those messages that may be handled at one time by a ServerSession. Finally, an underlying ServerSessionPool is also associated with a ConnectionConsumer.
The Connection.createConnectionConsumer() method initializes and constructs a ConnectionConsumer given those attributes just described for a ConnectionConsumer, including the destination point, message filter, server session pool, and maximum number of messages processed. After a ConnectionConsumer handle has been created, the pool of sessions associated with the ConnectionConsumer may be gotten by invoking its getServerSessionPool() method. The close() method should be invoked by clients when use of the ConnectionConsumer is no longer needed by the client to ensure that resources allocated outside of the current JVM process are released and closed.
JMS Transactional Connections and Sessions
The JMS API also defines a set of interfaces depicted in Figure 12.9 that may optionally be implemented by JMS-based messaging servers to provide transactional functionality for JMS connections and sessions. Because coding JMS clients to talk directly to these interfaces is discouraged, we only briefly discuss their relevance here. In fact, JMS clients should use whatever transactional mechanisms (often declarative) are defined for the messaging environment in which they operate.
Similar to the ConnectionFactory, the XAConnectionFactory is used to create a handle to an XAConnection object as opposed to a plain Connection object. The XAConnection object subclasses the Connection object to indicate that transactional support is implied by the particular JMS connection. Thus, messages sent and received as part of a session via such a connection are imbued with the basic transactional behavior via the Java Transaction API (JTA). In fact, the XAConnection object is used to create handles to XASession objects that subclass JMS Session objects. A JTA XAResource may be gotten from the XASession object and exposes other transactional management behavior.
JMS Generic Messages
Figure 12.10 depicts the core interfaces and conceptual relations of the base JMS Message type. The Message interface is the root interface for all messages that flow through a JMS-based messaging system. The Destination interface is a marker interface used to represent an endpoint of message delivery. Because Message objects may have relationships to a destination and reply-to location, conceptual relationships with a Destination interface are shown in the diagram. Similarly, because a delivery mode for a message also exists, a conceptual relationship to a DeliveryMode interface is also shown.
These are the major elements of a message encapsulated by the Message interface:
Header information can be gotten or set using a standard getter and setter syntax of getXXX() and setXXX(), where XXX is the name of a header property. Standard header properties defined as getters and setters on the Message interface are listed here:
Provider-specific properties of a message can also be gotten and set onto a message using a host of getXXXProperty() and setXXXProperty() methods, respectively, where XXX specifies the property type. Each property has a name identified by a String and a value of its specific type. The existence of a named property can be tested using Message.propertyExists(String). The Message.getPropertyNames() method returns an Enumeration of property names defined for this message. Property types supported are boolean, byte, short, int, long, float, double, and String.
This is the generic form of getter:
XXX getXXXProperty(String name) throws JMSException;
This is the generic form of setter:
void setXXXProperty(String name, XXX value) throws JMSException;
Property values are set before a message is sent and are in read-only mode when received by a client. A value written as a byte, short, int, or long can be read as a value of its own type or higher precision in the same type family. Thus, a short, for example, can be read as a short, an int, or a long. Values written as a float can be read as a float or double. All values can be read as a String, and String objects can possibly be read as another value of a particular type if they can be parsed into that type.
All properties can be removed from a message using the Message.clearProperties() method. When clearProperties() is called, the properties are no longer in read-only mode. If a client attempts to read a property when it is read-only, a MessageNotWriteableException is thrown.
Properties whose names begin with the JMS_<provider_name> prefix are reserved for the JMS service provider's defined properties. Properties whose names begin with the JMSX prefix are reserved for standard JMS properties. The properties JMSXGroupID and JMSXGroupSeq are required, but the remaining properties defined with the JMSX prefix are optional. The names of supported JMSX properties can be gotten from a call to ConnectionMetaData.getJMSXPropertyNames(). These are the standard JMSX properties:
On a final note about generic JMS messages, sometimes it is important for a JMS client to know whether a message is received by its intended recipient. By calling Message.acknowledge(), a client can acknowledge that the associated message and all previous messages associated with a session were received. The last message in a group of messages that is acknowledged designates that all previous messages were received in that group.
JMS Specialized Messages
The generic JMS message interface just described offers a lot of generic functionality that any message inside a messaging system may expose. However, the JMS API also defines five additional types of messages that extend the Message interface and that correspond to five types of message body data, as shown in Figure 12.11. Byte data is encapsulated by the BytesMessage, a Serializable object is encapsulated by the ObjectMessage, a String message is encapsulated by the TextMessage, key and value pairs are encapsulated by the MapMessage, and I/O streams are encapsulated by the StreamMessage. Individual methods on the message sub-interfaces define getters and setters for the type-specific data body, but a generic clearBody() method to clear the data body of a message and place it in write-only mode exists on the base Message interface.
These are the various message body specialization types:
Message Producers, Consumers, and Selectors
With all of this talk about JMS connections, sessions, and messages, you might be wondering when we'll finally get to the point of actually being able to send and receive these messages within such sessions and over such connections. Indeed, there are a lot of abstractions within JMS to enable the sending and receiving of messages. Although such abstractions are many, it does illustrate the well-thought-out object-oriented nature of the JMS API. Nevertheless, we have come to the point in our discussion where it becomes more apparent how messages are sent and received via JMS as described in this section.
JMS provides base encapsulations for message producers and consumers as shown in Figure 12.12. Message producers generate messages in a session context that are to be received by message consumers. Message consumers can filter which messages they receive by using a message selector.
The MessageProducer interface is the base interface for producers of messages. MessageProducer objects are created by passing a Destination for a message as an argument to a message producer creation method on a Session object. When utilizing the unified domain model of JMS v1.1, the Session.createProducer() method can be used to create a handle to a MessageProducer object. As you'll soon see, sub-interfaces of the Session interface that are specific to a particular messaging domain model also provide more concrete methods for returning messaging-domain–specific message producer object handles useable in a JMS v1.0 and JMS v1.1 context. Methods on the MessageProducer interface permit the setting and getting of a producer's default delivery mode, the associated message priority, and the time (in milliseconds) for which a message in the message system has to live. Setters also exist for disabling message ID and timestamps along with getters of these values. Finally, because the producer itself represents a destination point, its own Destination may be gotten using the getDestination() method.
Of primary importance when using a MessageProducer is to actually produce (that is, send) a message to a particular destination point. A series of MessageProducer.send() methods permit such behavior. If the destination is associated with a Message object, the MessageProducer object's send(Message) and send(Message, int, int, long) methods may be invoked to send that message to its intended destination. If default values for the Message object are undesirable, the message's priority number, time to live in milliseconds, and DeliveryMode setting may be submitted as parameters to the send() method. Alternatively, a Destination point may be explicitly identified by using the MessageProducer object's send(Destination, Message) or send(Destination, Message, int, int, long) methods.
The MessageConsumer interface is the base interface for consumers of messages. MessageConsumer objects are created using the unified domain model by invoking one of the createConsumer() methods on the Session object. Sub-interfaces of the Session object may also be used to create handles to domain-specific messaging model consumers, as we'll see later in this chapter. Regardless of the consumer creation method used, a Destination object must be passed as an input parameter to a message consumer creation method. The Destination object identifies that destination point with which the message consumer is associated to receive messages.
A MessageConsumer may also be created using a particular message selector String parameter passed into two of the Session.createConsumer() methods. Message selectors define a filter that is used to determine which messages should be routed to a message consumer. For example, a particular message consumer may be interested in receiving messages only from a system administrator with a JMSXUserID property of admin (for example, String exampleSelector = "JMSXUserID = admin"). A message selector String is expressed in a subset of SQL92 syntax and can refer only to header and property information in a message. A selector is expressed using the following types of expression elements:
Notice that one form of the Session.createConsumer() method also enables you to provide a boolean input parameter named NoLocal. This parameter enables you to specify whether a consumer sharing the same connection as the producer of a message should consume those messages sent to the consumer's destination point by that producer. By default, this value is false, indicating that a consumer will receive messages sent by a producer even if they share the same connection.
The MessageConsumer also allows a MessageListener to be registered with it to independently receive messages for the MessageConsumer. The MessageConsumer.close() method is used to close the resources used to implement the message consumer by the service provider. Various methods on the MessageConsumer are defined to receive messages from a destination. The MessageConsumer.receive() method blocks until a particular message is received for use by the consumer or until the MessageConsumer is closed. The MessageConsumer.receive(long) method can be used to specify a timeout in milliseconds for which the message consumer should wait to receive a message. MessageConsumer.receiveNoWait() can be called to receive a message only if it is immediately available and without blocking.
|[ Team LiB ]|