JMS Programming Issues

We now examine the typical issues to consider when building JMS-enabled clients. In particular, you should understand the merits of synchronous versus asynchronous message consumptions, the impact of transacted sessions and distributed transactions on message redelivery and acknowledgment, and the benefits of integrating JMS resources with existing J2EE applications. We also look at how to create both temporary and permanent destinations at runtime. We will examine WebLogic's XML message type, and how the proprietary XPath message selector syntax can help filter out incoming XML messages by inspecting their payloads. (Other programming issues, such as concurrent message handling and multicast topics, were covered earlier.)

8.4.1 JMS Clients

In WebLogic 7.0, if an external JMS client has to interact with WebLogic JMS, you have to include the entire weblogic.jar in the client's classpath. WebLogic 8.1 provides a thin client-side library, wljmsclient.jar, which you can distribute with your application. This library contains all of the support for WebLogic JMS, and is available in addition to the general client JAR, wlclient.jar, which contains the standard client support for JNDI, clustering, and transactions. Together, these JARs are only about 700 KB in size. You can find these client JARs in the WL_HOME/server/lib directory of your installation. Any client that uses these smaller-footprint libraries must run on the Java 1.4 JRE.

8.4.2 Synchronous Versus Asynchronous Consumption

The JMS standard supports two ways in which a consumer may receive a message:

  • A consumer could register a listener class to a JMS destination and asynchronously process incoming messages. In this case, the onMessage( ) method exposed by this listener class is invoked whenever a message arrives at the target destination.
  • In the synchronous approach a consumer proactively asks for a destination for a message. In such a case, you must invoke one of the receive methods on the consumer.

It is useful to know how these two approaches perform, especially if you want to optimize your JMS clients. Asynchronous consumers generally perform and scale better than synchronous consumers for two reasons:

  • A synchronous consumer consumes its thread for the duration of its receive call. This becomes even more significant if one of the blocking receive methods is invoked on the consumer:

     /** call blocks indefinitely until a message arrives at the destination */
     public Message receive( ) throws JMSException; 
     /** call blocks only until a message arrives within the specified time period; 
     if the timeout expires, the call returns null */
     public Message receive(long timeout) throws JMSException;
  • On the other hand, asynchronous consumers do not consume a thread while they are inactive. This is especially crucial when the consumers run on the server because too many blocked synchronous consumers could mean that the JMS server runs out of available server-side threads.
  • Asynchronous consumers create less network traffic because the incoming messages are pipelined to the consumer's session. This aggregation of multiple messages is often beneficial. Later in this section, we shall examine the role of pipelining when message selectors are used.

The size of the pipeline is, in fact, the maximum number of messages that may exist on an asynchronous session and have not yet been passed to a listener. To adjust the size of the session's pipeline, you should change the value of the Messages Maximum attribute on a connection factory. It defaults to a value of 10. A value of -1 indicates that the size of the session's pipeline is bound only by the amount of free memory available. Note that the behavior of the JMS session changes somewhat when it listens on a multicast topic.

8.4.3 Durable Subscribers

A durable subscription to a topic is one that can outlast the client's connection with the JMS server. By default, a subscription is not durable i.e., a client's subscription lasts as long as it has an active session. Any messages that are sent after a client gets disconnected are lost. A durable subscription, on the other hand, ensures that the JMS consumer still can receive the messages destined for the topic, even after the connection to the JMS server has been severed. WebLogic will retain the messages in a JMS store for any subscriber that isn't active when the messages were published, and ensure that the unexpired messages are acknowledged by the subscriber when that client later reconnects.

A JMS client cannot create a durable subscription to a distributed topic.

 

8.4.3.1 Unique clients

In order to create a durable subscription, you must set up a JMS store for the topic. In addition, you must set a client identifier on the topic connection so as to uniquely identify the durable topic subscription. When a durable client reconnects to a topic, its client ID lets the JMS server know that it is the same client that was previously connected. This enables the JMS server to correctly determine those missed messages that should be sent to the topic subscriber. There are two ways to uniquely identify the client:

  • A client can register its unique client ID with a topic connection.
  • A client can use a connection factory that is specifically preconfigured for it. This ensures that any topic connection created using the factory is set with the correct client ID automatically.

The standard approach is to assign a client ID to a newly created topic connection:

connect = factory.createTopicConnection( );
connect.setClientID(someUID);

Client IDs are supposed to be unique across connections. In fact, if a JMS client tries to set the client ID on two separate connections from a connection factory with no preset client ID, an InvalidClientIDException should be thrown.

It is your responsibility to ensure that the client ID remains unique for each subscriber. If your architecture can support this constraint easily, this scheme will suffice. If you cannot reliably guarantee uniqueness, you cannot always expect an exception to be thrown. For instance, there is a small risk that no exception is thrown when a duplicate client ID is assigned to two connections simultaneously. To avoid this problem, either restructure your clients or ensure that each durable subscriber uses its own connection factory with a preset client ID.

The Client ID attribute for a connection factory, if set, determines the client ID for any topic subscribers created using the factory. You can set the default client ID for any connection factory using the Administration Console. Of course, to use this scheme effectively, you must ensure that each unique client grabs its own connection factory, which is rather limiting. Note that you may not change a client ID on a JMS connection that has been obtained from a connection factory for which a default client ID has been set. You can use the getClientID( ) method on the connection to determine whether a client ID has been set already.

8.4.3.2 Creating and removing durable subscribers

Use the createDurableSubscriber( ) method on a topic session to create a durable subscriber. You must provide the topic to which to subscribe, and a unique name for the client's subscription:

tsession.createDurableSubscriber(theTopic, "someUniqueName");

You could use an alternative form of the same method that also lets you specify a message selector and whether the JMS client can receive messages published by itself.

It is important to remember to later unsubscribe from the topic. A common source of bugs in JMS programs is when durable subscribers do not properly unsubscribe from the topic. The server will continue to retain messages for durable subscriptions until they have expired, and therefore continue to unnecessarily eat server-side resources. To delete a durable subscription, use the unsubscribe( ) method on the topic session while passing the name of the client's subscription:

tsession.unsubscribe("someUniqueName");

Note that if the subscriber receives a message as part of an unresolved transaction, or if a message has not yet been acknowledged by the topic session, you will not be able to unsubscribe until these issues have been cleared.

8.4.4 Transactions and JMS

Using JMS in a transactional context enables you to treat a number of messages as an atomic unit. Any messages produced during a transaction will not be delivered to any consumers until a commit has been issued. A consumer, on the other hand, can receive a number of messages in a transaction. The JMS server will hold on to the messages until the consumer issues a commit. If the consumer instead rolls back the transaction, the server will attempt to redeliver the messages, in which case a redelivery flag is set on the messages. The redelivery behavior differs for topics and queues. If a topic subscriber rolls back a received message, the message is redelivered to that particular subscriber. If a queue receiver rolls back a received message, the message is redelivered to the queue so that another receiver has the opportunity to process the message.

The JMS standard supports two types of transactions: local and distributed. The concepts are analogous to those that exist in the JDBC world, as described earlier in Chapter 6. In the JMS world, local transactions manifest as transacted sessions, in which case the scope of the transaction spans the lifetime of the JMS session. A transacted session will influence only those send or receive operations that occur during the session. A transacted session will have no effect on, nor will it be affected by, JTA transactions that may be in scope. Conversely, a distributed transaction can include, for instance, EJB updates and nontransacted JMS sessions within the same scope. In this case, the two-phase commit protocol ensures the proper coordination among the multiple resources that are participating within the same transaction.

8.4.4.1 Transacted sessions

A transacted session ensures that all JMS send and receive operations occur as a single, atomic unit. To initiate a new transacted session, the createTopicSession( ) method on a TopicConnection object or the createQueueSession( ) method on a QueueConnection object to indicate whether the JMS session is transacted:

// create a transacted session for a topic
TopicSession tsession = 
 tcon.createTopicSession(true, Session.AUTO_ACKNOWLEDGE);
// create a transacted session for a queue
QueueSession qsession = 
 qcon.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);

Note that in case of transacted JMS sessions, the acknowledge mode configured for the JMS session is ignored completely. All messages are acknowledged when a commit occurs during the session's lifetime. A local transaction is then started implicitly after the first JMS send or receive operation in the session, and will continue until a session is committed or rolled back. Another transaction is started automatically, as soon as the previous transaction commits or rolls back. This process also is called transaction chaining. The following piece of code shows how to produce multiple messages within a transacted session:

QueueSession qsession = 
 qcon.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
QueueSender qSender = qsession.createSender(someQueue);
qsender.send(messageOne, java.jms.DeliveryMode.PERSISTENT,
 javax.jms.Message.DEFAULT_PRIORITY,
 2000000);
qsender.send(messageTwo, java.jms.DeliveryMode.PERSISTENT,
 javax.jms.Message.DEFAULT_PRIORITY,
 2000000);
if (noAwfulCondition) 
 qsession.commit( );
else
 qsession.rollback( );

Depending on whether the session is committed or rolled back, the two messages either will be delivered as a single unit, or will not be delivered at all. After completing this segment of code, a new transaction will be started implicitly for any other local activity within the JMS session. This behavior is consistent with the JMS specification.

Note that transactional semantics still are guaranteed if the local transaction produces or consumes messages across multiple JMS servers within a cluster. In this sense, local transactions still can be "distributed" across multiple JMS servers.

8.4.4.2 Distributed transactions

A distributed transaction lets you coordinate updates to multiple resources that may be involved in the same transaction context. Create a JTA transaction in the usual way, by accessing the UserTransaction object bound to the server's JNDI tree:

UserTransaction tx = ctx.lookup("javax.transaction.UserTransaction");
tx.begin( );
//a new transaction scope has been created...

In the JDBC world, you would need an XA-aware data source before it could participate in a JTA transaction. In the JMS world, you need to ensure that the JMS connection factory used to obtain JMS connections to the JMS server is XA-aware. One option is to use the default XA-enabled connection factory provided by WebLogic, which is bound to the server's JNDI tree under the name weblogic.jms.XAConnectionFactory.

To configure your own transactional JMS connection factory, you must modify the connection factory to produce XA-aware JMS connections. You can adjust the factory's transactional behavior using the Administration Console. Simply select a connection factory from the left pane and then navigate to the Configuration/Transactions tab. Here, you must enable the XA Connection Factory Enabled setting for the connection factory.[4] So, for instance, the following code could participate in a JTA transaction:

[4] In WebLogic 7.0, you also should enable the User Transactions Enabled setting for the JMS connection factory. If you need support for JTA transactions for server-side applications only, set the Server Side XA Enabled flag to true.

qsession = qcon.createQueueSession(false, Session.CLIENT_ACKNOWLEDGE);

// Any JMS send/receive operations will honor the JTA transaction
// Any JDBC operations using an XA-aware datasource will honor the JTA transaction

if (noAwfulCondition)
 tx.commit( );
 else
 tx.rollback( );

Note how we've created a nontransacted session to participate in a JTA transaction. This is essential because otherwise, the transacted JMS session will interfere with the scope of the global JTA transaction.

WebLogic JMS ignores any acknowledge modes that may be configured for a JMS session participating in a JTA transaction.

WebLogic does not support JTA transactions with asynchronous message delivery, as their behavior is not defined in the JMS standard. If you need transactional capabilities in this situation, then you should consider using MDBs. An MDB provides concurrent handling of JMS messages arriving at a destination in an asynchronous fashion, while being able to automatically start a transaction just prior to message delivery.

8.4.4.3 MDBs

Duplicate messages, if not properly handled, can have undesirable effects: the same operation could be repeated several times, leading to an inconsistent application state. Typically, if an MDB receives a message but fails to acknowledge it, the JMS producer resends the message. To avoid the delivery of duplicate messages, you can configure the MDB to use container-managed transactions:



 MyMDB
 org.foo.bar.MyMDBBean
 Container
 

In this way, both the message receipt and acknowledgment occur within the same transaction. If the MDB implements bean-managed transactions, you must explicitly code for duplicate messages because message acknowledgment will occur outside the scope of the transaction.

Even with container transactions, there is the possibility that the MDB may receive duplicate messages for instance, if the server crashes between the time the MDB's onMessage( ) method completes, and the container acknowledges the message.

 

8.4.5 Integrating JMS with Servlets and EJBs

WebLogic 8.1 provides a simplified way of using JMS resources from within a servlet or an EJB. These "JMS wrappers" provide a lot of additional support, including the items described next.

  • Automatic pooling of JMS connections and sessions
  • Automatically enrolling of the JMS session in a distributed transaction.[5]

    [5] Only JMS sessions created from foreign JMS connection factories require these wrappers for automatic enlistment in distributed transactions. Sessions created via connection factories hosted by WebLogic JMS do not.

  • Testing the JMS connections and reestablishing the connection in the event of a failure
  • Integrating JMS interactions with the security constraints on the EJB or servlet

To benefit from these enhanced features, you need only declare a reference to the JMS connection factory in the servlet's or EJB's deployment descriptor:



 jms/mycf
 javax.jms.QueueConnectionFactory
 Container
 Shareable

...


 jms/mycf
 weblogic.jms.ConnectionFactory

Here, the default JMS connection factory will be available to the servlet's or EJB's local JNDI context under the name java:comp/env/jms/mycf. The same approach can be used to reference a JMS connection factory on a foreign JMS server. In that case, you must map the reference by specifying the local JNDI name of the foreign JMS factory.

Similarly, you can declare a reference to a JMS destination using the resource-env-ref element in the servlet's or EJB's deployment descriptor. The following example shows how to set up a reference to a JMS queue:



 jms/myQ
 javax.jms.Queue

...


 jms/myQ
 com.oreilly.jms.someQueue

The JMS queue then is accessible to the servlet or EJB from within its local JNDI context using the name java:/comp/env/jms/myQ. The next code sample shows how an EJB or servlet could use these configured resource references to send a JMS message:

InitialContext ctx = new InitialContext( );
QueueConnectionFactory cf = 
 (QueueConnectionFactory) ctx.lookup("java:comp/env/jms/mycf");
Queue queue =
 (Queue)ctx.lookup("java:comp/env/jms/myQ");
ctx.close( );
QueueConnection con = cf.createQueueConnection( );
try {
 QueueSession session = con.createQueueSession(0, false);
 QueueSender sender = session.createSender(queue);
 TextMessage msg = session.createTextMessage("Hello World");
 sender.send(msg);
} finally {
 con.close( );
}

This piece of code is standard, and should run within any J2EE-compliant servlet or EJB container. WebLogic can offer additional benefits when a servlet or EJB uses JMS resources in this way via their references declared within the deployment descriptors.

8.4.5.1 Pooled connection objects and testing

Pooled JMS connection objects are an important benefit of references to preconfigured connection factories for servlets and EJBs. A pooled JMS connection means that WebLogic makes a pool of session objects available to a servlet or EJB when they utilize the reference to the JMS connection factory. In other words, the wrapped JMS connection factory, connection, and session objects can collaborate in such a way that the servlet or EJB merely retrieves a session object from the pool.

Another benefit of WebLogic's JMS wrapper objects is that they are able to monitor the JMS connections to the JMS provider. First, they register an exception listener on the JMS connection object. Second, the JMS connections are tested every two minutes, by sending a message to a temporary destination and then receiving it again.

8.4.5.2 Caching JNDI lookups and JMS objects

As the JMS connection factory and destination objects are thread-safe, it is sensible to look up these resources once when the servlet or EJB is initialized, and then to reuse these values whenever the servlet or EJB needs to send or receive a message to the destination. This becomes even more important when the servlet or EJB references a remote JMS provider. Typically, you would look up these objects from within the servlet's init( ) method or the EJB's ejbCreate( ) method, and then assign it to instance variables that can be used later, whenever the servlet or EJB needs to interact with the JMS destination.

Even though it may be tempting to cache other objects, such as the connection, session, or even producer objects, you should not. Your application code should create a JMS connection, set up a JMS session, and create JMS producers on demand, and later release these resources whenever you are done. Only then can the JMS wrapper objects properly pool these resources and ensure that they can be shared by other servlets or EJB instances. In addition, you lose the benefits of connection testing and re-creating the JMS connection and session objects if the servlet or EJB attempts to cache the session objects itself.

8.4.5.3 Automatic enlistment in transactions

If the actual foreign JMS provider supports XA transactions and the servlet, or if EJB uses a resource-reference wrapped JMS connection object to send or receive messages, then WebLogic can transparently enlist the JMS session in a transaction, if one exists. This occurs either when a transaction is implicitly started for instance, if the EJB method supports container-managed transactions or when the servlet or EJB explicitly creates a transaction context using the UserTransaction interface. At no point does the servlet or EJB need to rely on the XA extensions to the JMS API. If the JMS provider doesn't support XA, WebLogic throws an exception if the wrapped JMS connection object is used within a transaction context. In this case, you must either specify a transaction setting of NotSupported for the EJB method, or suspend the current transaction.

Enlisting a JMS session in a distributed transaction also is expensive. For example, if your application uses an XA-enabled connection factory to send or receive a JMS message outside the scope of a transaction, the container must still wrap the send or receive in a JTA transaction to ensure that your code works for any JMS provider. Even though it is a one-phase commit transaction, it still can slow down WebLogic. A more optimal approach would be to reference a non-XA connection factory when sending or receiving JMS messages outside the scope of a transaction.

8.4.5.4 J2EE compliance

The J2EE standard enforces certain constraints when using the JMS API within a servlet or EJB. WebLogic's JMS wrapper objects enforce these restrictions by raising exceptions when the methods listed in Table 8-2 are invoked.

Table 8-2. Restricted method calls from within a J2EE application

JMS class/interface

Restricted methods

Connection

createConnectionConsumer( )

createDurableConnectionConsumer( )

setClientID( )

setExceptionListener( )

stop( )

Session

QueueReceiver

TopicSubscriber

getMessageListener( )

setMessageListener( )

Also remember that WebLogic ignores the two parameters to the createSession( ) methodsthe acknowledge mode and the transacted flag when used from within an EJB. If a transaction context exists, the JMS session is enlisted in the transaction automatically. Otherwise, it is not enlisted in the transaction.

8.4.5.5 Container-managed security

By default, WebLogic JMS relies on the security credentials of the thread used to invoke the servlet or EJB method. In general, you should specify the res-auth subelement when setting up a resource-ref to a foreign JMS connection factory. The res-auth element can take one of the following two values:

Container

Here the J2EE container handles the authentication when obtaining a JMS connection to the remote JMS provider. In this case, WebLogic relies on the username/password attributes configured for the foreign JMS connection factory.

Application

Here the application code must supply the username and password when obtaining a connection from the foreign JMS connection factory. Any username/password set up for the foreign JMS factory is ignored by the servlet or EJB container.

If a reference to a JMS connection factory relies on container-managed security, it is illegal for the servlet or EJB to supply a username and password when obtaining a JMS connection.

8.4.6 Creating Destinations at Runtime

WebLogic allows JMS clients to create both temporary and permanent destinations at runtime. A temporary queue or topic is a transient destination, whose lifetime is bound to the JMS connection associated with the JMS session that spawned it. JMS provides a standard approach to creating temporary destinations. In addition, WebLogic's extensions let you dynamically create new destinations on the JMS server, which will live beyond the JMS connection that spawned them.

8.4.6.1 Temporary destinations

A JMS client can create a temporary destination on demand, without the administration overheads of a permanent queue or topic. Temporary destinations can be created dynamically by invoking either the createTemporaryQueue( ) method on a QueueSession object, or the createTemporaryTopic( ) method on a TopicSession object. A temporary destination can never live beyond the lifetime of the connection used to create it. In other words, if the JMS client is disconnected for whatever reason, the temporary destination can no longer be retrieved.

This implies that messages sent to a temporary destination cannot be made persistent. A JMS client has no way of reconnecting to a temporary destination once a connection has been severed it can only create a new one. Hence, even if a JMS session uses a persistent delivery mode for messages to a temporary destination, they may still be lost if the intended recipients are disconnected from the temporary destination. For this reason, WebLogic JMS silently makes all such messages nonpersistent.

Temporary destinations will not survive a server restart. JMS messages sent to a temporary destination are nonpersistent.

A JMS server must be configured properly before JMS clients can dynamically create temporary destinations. Select the particular JMS server in the Administration Console and choose from one of the predefined JMS templates from the Temporary Template option in the Configuration/General tab. Any temporary destination that is created on the JMS server will then use the settings supplied in the template.

Once a temporary destination has been created, a JMS client can publish the destination to its recipients using the JMSReplyTo header field. The next example shows how to create a temporary topic and then promote its existence to other applications using the reply-to header field:

TemporaryTopic tempTopic = session.createTemporaryTopic( );
message.setJMSReplyTo(tempTopic);

A JMS client may then subscribe to the temporary topic by retrieving this value from the message's header:

Topic myTempTopic = (Topic) message.getJMSReplyTo( );
publisher = session.createPublisher(myTempTopic);

After it serves its purpose, you can delete the destination using the delete( ) method on the TemporaryQueue or TemporaryTopic object.

8.4.6.2 Permanent destinations

A JMS client can use WebLogic's proprietary approach to dynamically create a permanent destination on the JMS server. These destinations are identical to those you create by using the Administration Console, except they are created dynamically.

The weblogic.jms.extensions.JMSHelper class provides the relevant methods:

public static void createPermanentQueueAsync(Context ctx, 
 String jmsServerName, String queueName, String jndiName) throws JMSException;

public static void createPermanentTopicAsync(Context ctx, 
 String jmsServerName, String topicName, String jndiName) throws JMSException;

These methods are asynchronous, and may incur a significant delay between when the request for a new destination is submitted and when the new destination is bound to the server's JNDI tree. They modify the domain's configuration to make the changes permanent, and do not support very good error handling. For instance, the call to the createPermanentQueueAsync( ) method can fail without throwing an exception. In addition, a thrown exception does not necessarily mean that the method call failed. For this reason, these methods should be used sparingly, if at all.

The following code shows how to create a JMS queue:

JMSHelper.createPermanentQueueAsync(
 ctx, "MyJMSServer","AnyQName", "MyPermanentQ");

The parameters include the JNDI context, the name of the JMS server that will host the queue, a name for the JMS destination, and finally, the JNDI name under which the queue will be bound.

If you do not execute this code in the appropriate security context, you will get a NoAccessRuntimeException.

Because the method call is asynchronous, a client must regularly poll to determine whether the destination has been created. The next example shows how to implement a utility method that can locate a new queue on a JMS server:

private static Queue findQueue(
 QueueSession queueSession, String jmsServerName, String queueName, 
 int retryCount, long retryInterval) throws JMSException {
 String wlsQueueName = jmsServerName + "/" + queueName;
 String command = "session.createQueue(" + wlsQueueName + ")";
 long startTimeMillis = System.currentTimeMillis( );
 for (int i=retryCount; i>=0; i--) {
 try {
 System.out.println("Trying " + command);
 Queue queue = queueSession.createQueue(wlsQueueName);
 System.out.println("Obtained queue after " +
 (retryCount - i + 1) + " tries in " +
 (System.currentTimeMillis( ) - startTimeMillis) + " millis.");
 return queue;
 } catch (JMSException je) {
 if (retryCount == 0) throw je;
 }
 try {
 System.out.println(command + "> failed, pausing " + retryInterval + " millis.");
 Thread.sleep(retryInterval);
 } catch (InterruptedException ignore) {}
 }
 throw new JMSException("out of retries");
}

Notice how instead of performing JNDI lookups on the new queue name, we have used the createQueue( ) method on a QueueSession object. If the method throws an exception, you can be reasonably sure that the destination hasn't been created yet, and you should wait a while before trying again. A JMS client can then invoke the findQueue( ) method, after a call to the JMSHelper class, to retrieve the dynamically created queue once it becomes available:

JMSHelper.createPermanentQueueAsync(ctx, "MyJMSServer","AnyQName", "MyPermanentQ");
Queue queue = findQueue(qsession, "MyJMSServer","AnyQName", 3, 5000);

8.4.7 JMS Messages and Selectors

WebLogic supports all the standard JMS message types, including JMS-defined header fields and user-defined properties. WebLogic also supports an XML message type, which lets you encapsulate XML data in a JMS message. A JMS consumer can use a message selector to filter unwanted messages arriving at a destination. A message selector defines a filtering criterion specified in terms of an SQL-like expression language using the message header and property fields. WebLogic extends this functionality with XPath selectors for XML messages.

Different message types have their own performance cost on the JMS server. Message selectors also can impact the server's performance and the efficiency with which a JMS consumer handles incoming messages. We look at these performance issues a little later in this chapter.

8.4.7.1 XML messages

The standard approach to exchanging XML data would be to wrap the XML text in a standard TextMessage object. Ideally, a JMS consumer should be able to easily identify the fact that the body of the JMS message is an XML document. WebLogic makes a new message type available through the weblogic.jms.extensions.XMLMessage class. In this way, JMS producers can exchange XML data more intuitively, and JMS consumers can correctly handle XML messages. To create an XML message, you must use WebLogic's extension WLSession interface. The following code shows how to create an XML message using a QueueSession object:

QueueSession qsession = /* ... */;
XMLMessage msg = ((WLQueueSession) qsession).createXMLMessage( );
x1.setText("yes");

Here we've created an empty XML message with no body, and then invoked setText( ) to provide the XML data. The next code sample shows an alternative way of creating an XML message:

TopicSession tsession = /* ... */;
XMLMessage msg = ((WLTopicSession) tsession).createXMLMessage("no");

This version of the createXMLMessage( ) method creates an XMLMessage object and initializes it with the supplied XML text. An XML message can be treated like any other JMS message. So, you can retrieve its header fields and create custom message properties. Its real utility, though, lies in the extended XPath selector syntax that lets you inspect the body of the XML message.

8.4.7.2 JMS selectors

Selectors allow a consumer to filter out messages that it does not want to consider. A message selector lets you define filtering criteria in terms of the message property and header fields. For example, a JMS producer could add relevant custom properties to a text message before sending it:

 TextMessage message = qsession.createTextMessage( );
 message.setText(someText); // a text message contains text body
 message.setStringProperty("category", "pets");
 message.setIntProperty("price", somePrice);
 qsender.send(message);

JMS consumers then may register their interest in the queue (or topic) while specifying a message selector for filtering out unwanted messages. So, a queue receiver could register its interest in expensive animals by using the following selector expression:

QueueReceiver qr = qsession.createReceiver(queue, 
 "price > 50 AND category = 'pets'");

A selector may be expressed only in terms of the message header and/or property fieldsit can never use the body of the message itself. This restriction is lifted for XML messages.

8.4.7.3 XML message selectors

WebLogic extends the message selector syntax by providing for an XPath expression that is evaluated over the body of the incoming XML messages. The syntax provides for a built-in function, JMS_BEA_SELECT( ), which wraps the XPath expression that you provide:

String JMS_BEA_SELECT(String type, String expression)

The type parameter must be set to the value xpath. The expression parameter may hold any valid XPath expression. The following code shows how to create a queue receiver using an XPath-based message selector:

 String selector = "JMS_BEA_SELECT('xpath', '/a/b/text( )') = 'yes'";
 QueueReceiver qr = qsession.createReceiver(queue, selector);

The message selector indicates that the queue receiver will filter out any XML messages whose payload doesn't contain the word yes. For example, the following XML will be allowed by the selector:

yes

The following XML will be rejected:

some arbitrary text

Note the use of single quotes to denote string expressions within the XPath query. The new XPath syntax slots right in with the standard message selector syntax, and you can combine the two easily, as shown in the following code sample:

String selector = "price > 50 AND 
 JMS_BEA_SELECT('xpath', '/a/b/text( )') = 'yes'";
TopicSubscriber ts = tsession.createSubscriber(topic, selector);

The JMS_BEA_SELECT( ) method returns null if the XML message does not parse, or if the specified elements in the XPath query are not present. This behavior is no different from the standard evaluation of the selector expression, where an identifier representing a property will be assigned the null value if the property does not exist.

8.4.7.4 Impact of messages on performance

The performance of a JMS server will vary depending on the type of messages that are being exchanged. You often need to balance the type of message, and the number of messages, with the following cost factors:

  • The server must be equipped with enough memory to store all messages. Recall that all messages, including their property and header fields, are held in memory, unless paging has been enabled. In this case, the message bodies are swapped out and only the property and header fields are retained in memory.
  • Message payloads must be serialized when they are sent and then deserialized before they are received by a consumer. For instance, an ObjectMessage must be serialized before it is delivered, and reconstructed at the consumer's end when it is received. Message header and property fields also must be serialized.
  • Messages also will consume network resources when they are sent to their intended destinations and when they are acknowledged by their consumers. The same holds true when the JMS server must deliver messages to a number of topic subscribers that live on different machines.
  • If the server or destination supports paging or persistent messages, you incur additional network and serialization costs when the message must be saved and retrieved from the JMS store.

Many of these cost factors are incurred when the message payload contains strings or large string properties. All of the Java string objects must be serialized and reconstructed, and they may place a significant burden on the server's memory. This cost is almost nonexistent when sending, for example, a ByteMessage object. To avoid these serialization costs, often it is preferable to use a ByteMessage or StreamMessage object, rather than a TextMessage or XMLMessage object. If large amounts of text need to be sent within a message, consider compressing the text so as to reduce the network and storage costs. Similarly, the default serialization for an ObjectMessage often can be improved by writing your own serialization logic, using the java.io.Externalizable interface.

The StreamMessage, MapMessage, ObjectMessage, and ByteMessage types are treated as simple byte buffers at the server's end. Serialization costs for these message types will occur only on the client's end.

8.4.7.5 Impact of message selectors on performance

Selectors incur additional processing cost, and it is important to know what kinds of costs are incurred and how they can be minimized. For example, WebLogic can access message header fields less expensively than message property fields. In addition, selector expressions are evaluated in a left-to-right order, together with short-circuit evaluation. Thus, for better performance, a selector expression should be written in such a way so that criteria using any header fields appear before the property fields.

All message filtering for a consumer takes place on the server, except for multicast topic subscribers, in which case the client filters the messages. Hence, selectors will not affect server performance if you are using multicast topic subscribers; they will affect only the client application's performance.

The message selector mechanism also works differently for queues and topics. For topics, the selector is evaluated when the message is published, once for each subscriber. The number of selector evaluations is then directly proportional to the number of subscribers. For queues, the selector is evaluated on every synchronous receive request. A synchronous receive request will force the server to scan each message in the queue until a matching message is found. Thus, the number of evaluations will vary depending on how fast a selector match is found. The situation is further exacerbated if the consumers are slow because this means that every time a consumer is ready for a message, the entire queue needs to be rescanned. In addition, if a message arrives at a queue and one or more synchronous receivers have been blocked, the message must be evaluated against each receiver's selector until a match is found.

Asynchronous receivers can alleviate the cost of queue selectors. Such receivers have a limit on the number of outstanding messages that may be held in the session's pipeline, as determined by the Maximum Messages attribute on the connection factory. By increasing the size of this pipeline, you allow asynchronous receivers to fall further behind without incurring the cost of the queue scans, as the messages will be delivered to the pipelines and then removed from the queue.

As you can see, selectors can be quite expensive. XPath selectors can be even more expensive. Accessing header fields and properties is always going to be cheaper than evaluating an XPath expression, so always place the XPath queries at the end of a selector expression to take advantage of short-circuit evaluation.

XPath selectors also can conflict with message paging for queues. When messages are paged, the bodies of the messages are paged out of memory, and the header and property fields are retained in memory, available for evaluating any message selectors. XPath selectors define filtering criteria using the body of a message. So, if the selector must be evaluated, the paged-out messages must be swapped back into memory. Recall that the topic selector is evaluated as soon as the message is published, before the message is paged out. Hence, topic selectors do not suffer from the same paging problems as queue selectors.

Introduction

Web Applications

Managing the Web Server

Using JNDI and RMI

JDBC

Transactions

J2EE Connectors

JMS

JavaMail

Using EJBs

Using CMP and EJB QL

Packaging and Deployment

Managing Domains

Clustering

Performance, Monitoring, and Tuning

SSL

Security

XML

Web Services

JMX

Logging and Internationalization

SNMP



WebLogic. The Definitive Guide
WebLogic: The Definitive Guide
ISBN: 059600432X
EAN: 2147483647
Year: 2003
Pages: 187

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net