Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Professional Java.JDK.5.Edition (Wrox)

.pdf
Скачиваний:
39
Добавлен:
29.02.2016
Размер:
12.07 Mб
Скачать

Chapter 12

possible. The Dynamic MBean interface and meta data classes of the JMX API allow you to deal with that requirement:

public interface MessageProcessorMBean {

The isRunning method will provide the status of the MBeans connection to the JMS server. The status is read-only, but the value is controlled by the start and stop methods below. The start and stop methods allow you to control the message processing:

public boolean isRunning(); public void stop();

public void start();

The remaining methods of the interface define the properties exposed through the JMX Agent. These will allow you to parameterize your component and change the source and destination queues at run time:

public void setSource(String source); public String getSource();

public void setDestination(String destination); public String getDestination();

public void setProcessor(String name); public String getProcessor();

}

JndiHelper

Since you are using JMS, you need to connect to the JMS server using JNDI context lookup. The following code is a utility class that establishes a connection with a JMS server as well as looks up the Queues in the JNDI context:

package wrox.processing.util; import java.util.Properties; import javax.jms.ConnectionFactory; import javax.jms.Destination; import javax.naming.Context; import javax.naming.InitialContext;

import javax.naming.NamingException;

public class JndiHelper { private JndiHelper() {

}

The getContext() method returns a reference to the JNDI tree associated with the JMS server:

public static synchronized Context getContext() { Context context= null;

Properties props= new Properties();

Context properties are specific to the different JMS vendors’ implementations. In this example, you are using JBOSS 4.0, so their JNDI lookup client properties must be provided. They are shown in the code for clarity. It’s also important to include the vendor-specific jar containing the NamingContextFactory class. In this case, it is fr.dyade.aaa.jndi2.client.NamingContextFactory:

556

Distributed Processing with JMS and JMX

properties.put(Context.INITIAL_CONTEXT_FACTORY,”org.jnp.interfaces.NamingContextFac tory”);

properties.put(Context.URL_PKG_PREFIXES, “org.jnp.interfaces”); properties.put(Context.PROVIDER_URL,”localhost”);

try {

context= new InitialContext(props);

}catch (NamingException e) {

throw new RuntimeException(“could not create context”, e);

}

return context;

}

This is a convenience method for looking up a destination from the JNDI context.

public static synchronized Destination getDestination(String name) { Context context= getContext();

Destination destination= null; try {

destination= (Destination)context.lookup(name);

}catch (NamingException e) { e.printStackTrace();

}

if (destination == null) {

throw new RuntimeException(“could not find destination” + name);

}

return destination;

}

This method is for looking up connection factory objects. It includes an option to specify whether transaction support is needed:

public static synchronized ConnectionFactory getConnectionFactory(boolean txSupport) {

Context context= getContext(); ConnectionFactory factory= null; try {

if (txSupport) {

factory= (ConnectionFactory)context.lookup(“XAConnectionFactory”);

}else {

factory= (ConnectionFactory)context.lookup(“ConnectionFactory”);

}

}catch (NamingException e) { e.printStackTrace();

}

if (factory == null) {

throw new RuntimeException(“Could not find connection factory. “ );

}

return factory;

}

public static synchronized ConnectionFactory getConnectionFactory( ){ return getConnectionFactory(false);

}

}

557

Chapter 12

MessageProcessor

The following shows the code for the heart of the message processing component — the message processor implementing class. The messageProcessor class implements both the MessageListener interface and the MessageProcessorMBean. The interface and implementing class must be declared in the same package. In this case, both the interface and implementing classes are declared in the wrox.processing.jmx package:

package wrox.processing.jmx;

import java.lang.reflect.Constructor;

There are several classes to import from the javax.jms package. Fortunately, these interfaces represent the unified domain of the JMS 1.1 specification. They are a vast improvement over the previous 1.0 specification. Previously, to send a message to a queue required a specific QueueConnectionFactory, QueueConnection, QueueSession, et cetera.

Other than the hassle of the programming overhead associated with queues and topics, the real problem was that you couldn’t receive from a queue and send to a topic with the same session. Since the session performs transaction management, this implies that you cannot send and receive between destination types in a transactionsafe way. JMS 1.1 corrected that problem.

It’s important to note that the JMS-specific classes are imported and used in the MessageProcessor class. This is because it is the only class that has a dependency to JMS. A good practice in application design is to localize dependency on external APIs. This minimizes the impact to an application if a change needs to be made:

import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message;

import javax.jms.MessageConsumer; import javax.jms.MessageListener; import javax.jms.MessageProducer; import javax.jms.Session;

import javax.jms.TextMessage;

The processable interface defines the link between the JMS coding and the business logic of the application:

import wrox.processing.Processable;

import wrox.processing.util.JndiHelper;

The class declaration for the MBean needs to follow the naming conversion. The Message processor can also implement or extend classes; however, only the methods specified in the MessageProcessorMBean interface will be exposed for management. In this example, the onMessage() method in the MessageListener interface will not be accessible via the MBeanServer:

558

Distributed Processing with JMS and JMX

public class MessageProcessor implements MessageListener, MessageProcessorMBean {

private boolean running= false;

private String sourceName, destinationName; private String processorName;

private Processable processable;

private ConnectionFactory factory; private Connection connection; private Session session;

private MessageConsumer consumer; private MessageProducer producer; private Destination source, destination;

Setting the destination and source queue as managed attributes allows you to configure the message processor component at run time. This will be reviewed when the component is deployed:

public String getDestination() { return destinationName;

}

public void setDestination(String name) { this.destinationName= name;

}

public String getSource() { return sourceName;

}

public void setSource(String name) { this.sourceName= name;

}

Continuing with the description of the MessageProcessor code, this is the start method that will be exposed to the JMX Agent. The start method is responsible for creating the connection with the JMS server and registering the client to receive messages as they arrive to the queue:

public void start() {

ConnectionFactory factory= null;

Before establishing a connection to the JMS server, you need to look up the connectionFactory object in the JNDI object registry. An example of using the JndiHelper class to simplify this lookup of the destination objects is listed in the following code:

JNDI plays a key role in abstracting out the vendor-specific classes from the standard interfaces defined in the javax.jms.* package. The object bound to the context is the concrete implementation. The lookup casts the object to standard interface, removing vend specifics from application developers’ code. This is a common practice in a number of J2EE APIs.

factory= JndiHelper.getConnectionFactory(); source= JndiHelper.getDestination(sourceName);

destination= JndiHelper.getDestination(destinationName); try {

559

Chapter 12

Establish a unique connection for this client:

connection= factory.createConnection( );

Create the session object. The first parameter determines transaction support. The second parameter specifies acknowledgment of messages delivered to the client. AUTO_ACKNOWLEDGE tells the JMS server to mark the message as received when the method call to onMessage returns. Another option is to explicitly call message.acknowledge() to confirm message delivery. If the connection is lost or the session is rolled back before the acknowledge method is called, the message will be resent:

session= connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

consumer= session.createConsumer(source);

Pass a reference to this object to receive a message from the server:

consumer.setMessageListener(this);

Create a producer object for sending to the next destination:

producer= session.createProducer(destination);

Starting the connection is a very important step. It tells the JMS server to begin sending messages as they are available. Without that one line of code, you will spend a great deal of time wondering why there aren’t any messages being sent from the queue:

connection.start(); running= true;

} catch (JMSException e) {

throw new RuntimeException(“could not start message processor”, e);

}

}

public boolean isRunning() { return running;

}

Stop is the operation to return resources to the JMS server and close out all connections:

public void stop() { try {

connection.stop();

producer.close();

consumer.close();

session.close();

connection.close();

}catch (JMSException e) { e.printStackTrace();

}finally { running= false;

}

}

560

Distributed Processing with JMS and JMX

The goal here is to be able to pass in the name of the class implementing the processable interface:

public void setProcessor(String name) { processorName= name;

try {

The code then uses reflection to take the class name and turn it into an object instance of that class. Note that the class described by the processorName parameter must be available in the classpath:

Class clazz=Class.forName(processorName); Constructor ct= clazz.getConstructor(null); Object obj = ct.newInstance(null);

If ( obj instanceof Processable){ processable= (Processable)obj;

} else {

throw new RuntimeException(“processor”+name+”is not an instance of Processable”, e);

}

} catch (Exception e) {

throw new RuntimeException(“could not create processor class”, e);

}

}

This is part of the MBean interface and will return the fully qualified name of the class implementing the processable interface:

public String getProcessor() { return processorName;

}

/* (non-Javadoc)

* @see javax.jms.MessageListener#onMessage(javax.jms.Message) */

The algorithm for the message processor component is really expressed in the onMessage method:

public void onMessage(Message message) { TextMessage inMessage = null, outMessage = null;

try { try {

The MessageProcessor is only designed to handle text messages. ObjectMessage and MapMessage types will be ignored:

if (message instanceof TextMessage) {

inMessage= (TextMessage)message;

Get the body of the input message and pass it to the processable interface. Create the output message from the session and send the new message to the next destination queue moving the message down the processing chain:

String body= inMessage.getText(); if (processable != null) {

String result = processable.process(body);

561

Chapter 12

outMessage = session.createTextMessage(); outMessage.setText(result);

producer.send(outMessage);

}

The process method on the Processable interface throws a ProcessingException. If this is thrown, send the input message to the error queue. This would take place if there was something functionally wrong with the current message:

}catch (ProcessingException pe) { inMessage.setObjectProperty(“exception”, pe); producer.send(JndiHelper.getDestination(JndiHelper.ERROR_QUEUE),

inMessage);

}

}catch (JMSException e) { e.printStackTrace();

}

}

}

This concludes the code for the MessageProcessor MBean implementing class. Note that the processable interface is responsible for the specific business logic. The next section shows the processable interface and an example implementing class.

Processable

The processing interface defines a single method. The method takes a string parameter. This parameter is the body of the text message to be processed. In implementing this design approach, this message body will be a string message:

package wrox.processing;

public interface Processable {

public String process( String text ) throws ProcessingException;

}

OrderProcessor

The OrderProcessor class is an example implementation of the processable interface. This would be replaced with any application-specific behavior you need to implement. OrderProcessor is the concrete implementation of the Processable interface. The goal of this component is to implement this one interface for each of the business processing steps that need to be accomplished. The implementation of the process method has been stubbed out for testing purposes:

public class OrderProcessor implements Processable {

public OrderProcessor( ){

}

/* (non-Javadoc)

* @see wrox.jmx.Processable#process(java.lang.String) */

public String process(String xml) throws ProcessingException {

562

Distributed Processing with JMS and JMX

//TODO replace dummy response

return “<order><id>100</id><item><status>instock</status></item></order>”

}

JMXAgent

The final class needed is for the JMX Agent, and the code you need to write is for the JMX Agent. The Agent is a simple application containing a main method. The Agent will create the MbeanServer and create the HttpAdaptor. The results of running the JMXAgent class as a standalone executable will be the deployment of the MessageProcessor MBean, HttpAdaptor, and the starting of the JMX Agent:

package wrox.processing.jmx;

Here are the imports required for the JMX Agent:

package wrox.processing.jmx;

import javax.management.MBeanServer;

import javax.management.MBeanServerFactory; import javax.management.ObjectName;

The next import is for the HTTP adaptor. This will allow management of the application via a Web browser over HTTP protocol. As you can see, it is not part of the standard javax.management package. At this time, adaptors are not part of the specification. This example is using an httpAdaptor, but there are numerous protocols available. Some of the protocols include RMI for remote method invocation, and SNMP for communicating with network devices such as a routers and switches.

The following code shows the main method for starting the deployment process for deploying a JMX application:

import com.sun.jdmk.comm.HtmlAdaptorServer; /**

* @author Scot

*/

public class Agent {

public static void main(String[] args) {

The first step is to create the MBean server:

MBeanServer server= MBeanServerFactory.createMBeanServer();

ObjectName adaptorName= null; try {

Next, create the adaptor and create an object name to uniquely identify the adaptor once it is registered with the MBean server:

HtmlAdaptorServer htmlAdaptor= new HtmlAdaptorServer();

adaptorName= new ObjectName(“Adaptor:name=html,port=8082”);

Then, register the adaptor with the MBean server. As you can see, the method to register the adaptor reads registerMBean. That is because, just like the MessageProcessor MBean created in the previous section, the httpadaptor also follow the JMX standard:

563

Chapter 12

server.registerMBean(htmlAdaptor, adaptorName);

System.out.println(“adaptor starting..”);

The final step is to start the HTML adaptor. This will tell the htmlAdaptor object to open a Socket connection and listening port 8082 for HTTP requests:

htmlAdaptor.start();

} catch (Exception e) { System.out.println(“Errors starting jmx agent”); e.printStackTrace();

return;

}

}

}

That is all the code that needs to be written for the message-processing component. The next section is the second of three message components: the routing component.

Creating a Component that Directs Messages through the Business Process

The second component that you will be implementing in this solution is a message routing component. Think of this component as the decision diamond of a business process. The message routing component takes a message from a source queue and — based on the message content — determines the next appropriate message Destination.

This component acts as a message control gate or traffic intersection. This is an explicit design decision to segregate the processing logic from the message routing logic. It’s just a good practice; by separating responsibility of components, you increase the level of reuse the component exhibits.

Just to clarify the motivation, the goal of building a process application in this manner is to be able to support a complex business process built with simple components loosely tied together with message queues and/or topics. For example, if you wanted to build a system that manages files on a network, you could create a component that copies a file from one directory to another and then reuse that component, without modification, whenever you needed to copy files.

This component helps simplify the overall system design by doing the following things:

1.Separating flow and processing logic, reducing the dependencies between each component.

2.Providing reuse of business logic.

3.Limiting the number of components that can modify each message. By design, the routing components will not be given access to the messages.

Figure 12-8 shows the UML design for the message routing component.

564

Distributed Processing with JMS and JMX

Figure 12-8

The message router design uses the same concepts as the MessageProcessor. There, collaboration is the same as the message processor. The only difference is the behavior specified by the Routable interface. The algorithm in the onMessage method does not modify the message, but instead determines the next queue in the processing chain. The responsibility of determining the next queue in the processing chain is left to the Routable Implementer, in this case the OrderRouter class.

The MessageRouter class also implements a standard MBean interface to expose the configuration and life-cycle methods of the component to the JMX agent. That way, the component methods will be accessible to other JMX compliant applications through any JMX-compliant adaptor.

The message routing component is similar to the message processing component in that it contains an MBean interface, an Implementing class, as well as an interface to extend the specific application behavior.

Routeable

The Routeable interface defines the behavior of the routable component. Unlike the Processable interface, the Routeable interface does not modify the incoming message. The Routeable interface consists of a single method: accepting the body of the JMS message as text and returning a string that represents the name of a queue:

package wrox.processing;

public interface Routeable {

public String route( String message);

}

MessageRouter

The MessageRouter class will implement the MessageRouterMBean and MessageListener. By implementing the MessageRouterMBean interface, its methods will be exposed for management via the JMX server. By implementing the MessageListener interface, the MessageRouter class can register with the JMS server and receive messages as they arrive at the JMS destination:

package wrox.processing.jmx;

import java.lang.reflect.Constructor;

//JMS imports omitted

import wrox.processing.Routeable; import wrox.processing.util.JndiHelper;

public class MessageRouter implements MessageRouterMBean, MessageListener { private boolean running= false;

private Routeable routeable;

565

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]