Chapter 8. Messaging and Remoting

In early 2001, the sun was shining, but I got a call from another kayaker named Steve Daniel saying that North Grape Creek was runnable, but below flood stage. It had never been run before. It required access through private property, and had to be at a perfect level: high enough to run, and low enough to navigate the many low-water crossings that dot the Texas country side. I wondered how Steve timed it perfectly.

Sending Email Messages

In Chapter 6, you learned how to attach behaviors to a bean. You built an example to write a message to a database whenever an exception was thrown. But it would be much more useful to have an email message sent to you when something breaks. Let’s use Spring’s email abstraction to do that.

How do I do that?

Spring provides a set of services to help you quickly send email messages. You’ll need to use three classes to get the support:

  • The SimpleMailMessage class is a value object that has the mail properties that you’ll need, such as subject, body, to, from, and cc properties.

  • The MailException is thrown when something breaks.

  • The MailSender interface and JavaMailSender provide Spring’s implementation to send messages. You will, of course, need to configure a mail client.

First, you’ll need to configure a simple message. Do that in the context, like in Example 8-1.

Example 8-1. RentABike-servlet.xml
<bean id="mailMessage" class="org.springframework.mail.SimpleMailMessage">
   <property name="to"><value>[email protected]</value></property>
   <property name="from">
      <value>[email protected]</value>
   </property>
   <property name="subject"><value>An error occurred</value></property>
   <property name="text">
      <value>There was a problem in the application.</value>
   </property>
</bean>

Next, you’ll want to configure a sender. You’ll use the sender that’s packaged with the Spring framework. All you need to do is configure it in the context (Example 8-2).

Example 8-2. RentABike-servlet.xml
<bean id="mailSender" 
   class="org.springframework.mail.javamail.JavaMailSenderImpl">
   <property name="host"><value>mail.rentabike.com</value></property>
</bean>

Finally, you’ll need to write a business method. Change the advisor that you made in Chapter 6 (in the Section 6.4 example) to use the new mail client, as in Example 8-3.

Example 8-3. ExceptionInterceptor.java
private MailSender mailSender;
private SimpleMailMessage mailMessage;
NOTE TO PROD: NoteWarning was here.

public MailSender getMailSender( ) {
    return mailSender;
}

public void setMailSender(MailSender mailSender) {
    this.mailSender = mailSender;
}

public SimpleMailMessage getMailMessage( ) {
    return mailMessage;
}

public void setMailMessage(SimpleMailMessage mailMessage) {
    this.mailMessage = mailMessage;
}

public class ExceptionInterceptor implements ThrowsAdvice {
    public void afterThrowing(Method m, Object[] args, 
           Object target, Exception ex) {

           try {
                  mailMessage.setText(ex.getMessage( ));
                  mailSender.send(mailMessage);
           } catch (Exception mex) {
                   // handle mail error
           }
    }
}

Next, you’ll have to configure the advisor, as in Example 8-4.

Example 8-4. RentABike-servlet.xml
<bean id="exceptionInterceptor" 
   class="com.springbook.interceptors.ExceptionInterceptor">
   <property name="mailSender"><ref local="mailSender"/></property>
   <property name="mailMessage"><ref local="mailMessage"/></property>
</bean>

<bean id="rentaBike" 
   class="org.springframework.aop.framework.ProxyFactoryBean">
  
   <property name="proxyInterfaces">
      <value>com.springbook.RentABike</value>
   </property>
   <property name="interceptorNames">
      <list>
         <value>exceptionInterceptor</value>
         <value>transactionInterceptor</value>
         <value>rentaBikeTarget</value>
      </list>
   </property>
</bean>

Now, do something that will cause an exception. For example, log on with a bad userID. The security interceptor should fire an exception, which in turn should fire an email message.

What just happened?

If you’ll recall, in Chapter 6 you created an interceptor to log a message. You’re using the same advisor. Here’s what your code did:

  • You changed your advisor to use the Spring email client. You injected the value of the email server and set up a simple message in the context.

  • You configured a proxy to intercept calls to the rentABike target. You specified three interceptors, one of which is the new exception handler.

  • The proxy calls the interceptor chain whenever a specified method throws any exception. You then configured the interceptor to point to all methods on the rentABike target, and added it to the context.

When you ran the code and coerced an exception, the proxy intercepted the exception, and then notified all of the interceptors in the chain for that method. Your interceptor got fired, and sent the email message.

Remoting

Remoting is one of the core features of EJB session beans. As a result, with declarative transactions, it’s often used to justify the use of EJB, where it may not otherwise apply. In this lab, you’ll learn how to do remoting declaratively—by writing configuration instead of any additional code—without EJB. In this example, you’ll be able to use Spring’s remoting service to access the business logic directly to get all of the bikes in the database over the internet.

Spring supports three kinds of HTTP services: Hessian, Burlap, and HTTP invokers. RMI is a favorite Java remoting technology, but you’ve got to open an additional port on your firewall, unless you’re tunneling. The nice thing about HTTP protocols is that you’re using the web’s standard communication protocol, so you need not open up your system to do anything beyond what you’ve to do anyway to support the internet. For starters, you’ll provide remote support to the façade through HTTP invokers.

How do I do that?

Most declarative remoting services work by using proxies. For this application, you’ll configure remoting so that other applications can access our façade. You’re going to need to prepare a client and a server. To prepare the server, you need to set up a second servlet for the remoting services. In the web.xml file, add the servlet definition from Example 8-5.

Example 8-5. web.xml
<servlet>
   <servlet-name>remoting</servlet-name>
   <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
   </servlet-class>
   <load-on-startup>2</load-on-startup>
</servlet>

Then, create a new servlet configuration file for the new servlet called remoting-servlet.xml, as in Example 8-6.

Example 8-6. remoting-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/
spring-beans.dtd">
<beans>
   <bean name="/RentABike" 
      class="org.springframework.httpinvoker.HttpInvokerExporter">
      <property name="service"><ref bean="rentaBike"/></property>
      <property name="serviceInterface">
         <value>com.springbook.RentABike</value>
      </property>
   </bean>
</beans>

On the client side, you’ll proxy the façade. Example 8-7 shows the code to configure the client.

Example 8-7. RentABike-servlet.xml
<bean id="rentabikeHttpProxy" 
class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
   <property name="serviceUrl">
      <value>http://YOUR_HOST:8080/RentABike</value>
   </property>
   <property name="serviceInterface">
      <value>com.springbook.RentABike</value>
   </property>
</bean>

Now, create a test case like Example 8-8. It should simply get the bikes in the store.

Example 8-8. RemotingTest.java
public class RemotingTest extends TestCase {
    private ApplicationContext ctx;
    private RentABike rental = null;

    public void setUp( ) throws Exception {
           ctx = new FileSystemXmlApplicationContext(
                  "/war/WEB-INF/bikeRentalApp-servlet.xml");
           rental = (RentABike)ctx.getBean("rentabikeHttpProxy");
    }

    public void testRemoting( ) throws Exception {
           Bike b = rental.getBike(1);
           assertNotNull(b);
           //etc.
    }
}

Run the test on the client to make sure things are working. You should be able to load and test the properties of any object available from the façade.

What just happened?

Let’s start the flow at the test case. The test case loads the rentabikeHttpProxy bean from the context. As you know by now, the bike store isn’t the true implementation. It’s just a proxy. When the test case invokes the getBikes method on the rentabikeHttpProxy, the proxy fires an interceptor, which serializes the parameters and sends the request to the server over HTTP.

On the server side, you’ve exported the service. The servlet engine forwards the request to Spring, which invokes the object under test. The server code returns a list of bikes. Spring’s remoting service serializes that list and sends them to the client proxy, which then returns them to the test case.

What about...

...other remoting services, like Hessian, Burlap, and RMI? Of course, one of the core problems with EJB remoting was that you could not decouple the remoting service from the container. For better or worse, you were stuck with RMI. That’s fine, unless you want to remote to clients written in another language, or run over a firewall without opening another port or tunneling.

Spring’s HTTP invokers are a good choice for Java over HTTP, but keep in mind that they rely on Java serialization, so they are not necessarily a good choice for cross-language remoting. Hessian and Burlap are remoting services that use a language-neutral representation of the objects. Although the non-Java support is limited in some ways (like for working with proxied objects), you generally do not need sophisticated support when you’re remoting across languages.

Working with JMS

In this example, you’ll learn how to communicate asynchronously with JMS. You’ll send a new reservation to the billing system, so that the system can bill the customer. We’ll focus on billing (the producer side of the equation), but Spring 1.2 intends to support a robust, pooled, and transactional message consumer architecture as well.

Many architects have hailed JMS as one of the best J2EE specifications. It provides a clean separation between the specification interface and pluggable implementations. It provides for a variety of payload structures, and distinct messaging models for peer-to-peer communication and publish-subscribe style communication. In this section, you’re going to integrate JMS with Spring.

How do I do that?

The first job is to configure JMS. To do so, you’ll need a JMS provider. We chose ActiveMQ (http://activemq.codehaus.org) because it is open source and its authors have provided excellent Spring support, but other implementations should work as well. Download the application, and then launch the JMS provider by navigating to the /bin folder and issuing the activemq command.

In the context, you’ll need to configure a connection factory to manage the JMS connection, as in Example 8-9.

Example 8-9. RentABike-servlet.xml
<bean id="jmsConnFactory" class="org.codehaus.activemq.ActiveMQConnectionFactory">
   <property name="brokerURL">
      <value>vm://localhost</value>
   </property>
</bean>

Next, you’ll need a JMS transaction manager, which plugs into the Spring transaction architecture (Example 8-10).

Example 8-10. RentABike-servlet.xml
<bean id="jmsTxManager" class="org.springframework.jms.connection.JmsTransactionManager">
   <property name="connectionFactory">
      <ref local="jmsConnFactory"/>
   </property>
</bean>

You’ll also need to create your consumer method. It’s going to use a JMS template. Call it BillingManager, and then front it with a façade. This class will use a JMS template (Example 8-11).

Example 8-11. RentABike-servlet.xml
<bean id="billingJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
   <property name="connectionFactory">
      <ref local="jmsConnFactory"/>
   </property>
</bean>

You’ll need to change the façade to notify the queue manager whenever you create a new reservation (Example 8-12).

Example 8-12. HibRentABike.java
public void addReservation(Reservation reservation, double amount) 
   throws AddReservationException {
   try {
      MonetaryTransaction tx = new MonetaryTransaction(amount, 
         reservation.getReservationId( ));
      getHibernateTemplate( ).saveOrUpdate(reservation);
      accountsFacade.addTx(tx);
      jmsTemplate.send("billingQueue", new MessageCreator( ) {
         public Message createMessage(javax.jms.Session session) 
            throws JMSException {
            return session.createTextMessage("New Reservation: " +
                   reservation.toString( ));
         }
       });
   } catch (Exception ex) {
      throw new AddReservationException( );
   }
}

Finally, wire it into your façade with a new method and dependency injection. Example 8-13 shows the new property on the façade.

Example 8-13. HibRentABike.java
private JmsTemplate jmsTemplate;
   
public JmsTemplate getJmsTemplate( ) {
   return jmsTemplate;
}

public void setJmsTemplate(JmsTemplate jmsTemplate) {
   this.jmsTemplate = jmsTemplate;
}

And Example 8-14 gives the modification to the façade’s configuration.

Example 8-14. RentABike-servlet.xml
<bean id="rentaBikeTarget" class="com.springbook.HibRentABike">
   <property name="storeName"><value>Bruce's Bikes</value></property>
   <property name="sessionFactory"><ref local="sessionFactory"/></property>
   <property name="transactionManager">
      <ref local="transactionManager"/>
   </property>
   <property name="jmsTemplate">
      <ref local="billingJmsTemplate"/>
   </property>
</bean>

You can build a standalone consumer (Example 8-15) so you can watch objects as the application creates them (heavily influenced by the sample receiver in the ActiveMQ download).

Example 8-15. StandaloneListener.java
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class StandaloneListener {

    public static void main(String[] args) {
        String queueName = "billingQueue";
        Context jndiContext = null;
        QueueConnectionFactory queueConnectionFactory = null;
        QueueConnection queueConnection = null;
        QueueSession queueSession = null;
        Queue queue = null;
        QueueReceiver queueReceiver = null;
        TextMessage message = null;

        try {
            jndiContext = new InitialContext( );
        }
        catch (NamingException e) {
            System.out.println("Could not create JNDI API " +
                    "context: " + e.toString( ));
            System.exit(1);
        }
        
        try {
            queueConnectionFactory = (QueueConnectionFactory)
                    jndiContext.lookup("QueueConnectionFactory");
            queue = (Queue) jndiContext.lookup(queueName);
        }
        catch (NamingException e) {
            System.out.println("JNDI API lookup failed: " +
                    e.toString( ));
            System.exit(1);
        }

        try {
            queueConnection =
                    queueConnectionFactory.createQueueConnection( );
            queueSession =
                    queueConnection.createQueueSession(false,
                            Session.AUTO_ACKNOWLEDGE);
            queueReceiver = queueSession.createReceiver(queue);
            queueConnection.start( );
            while (true) {
                Message m = queueReceiver.receive(1);
                if (m != null) {
                    if (m instanceof TextMessage) {
                        message = (TextMessage) m;
                        System.out.println("Reading message: " +
                                message.getText( ));
                    }
                    else {
                        break;
                    }
                }
            }
        }
        catch (JMSException e) {
            System.out.println("Exception occurred: " +
                    e.toString( ));
        }
        finally {
            if (queueConnection != null) {
                try {
                    queueConnection.close( );
                }
                catch (JMSException e) {
                }
            }
        }
    }
}

And you’re ready to let it rip.

What just happened?

JMS manages queues through templates. Think of a template as a default implementation for the things that you might want to do to a JMS queue, like compose a message. The changes in the context specified your JMS transaction strategy and configured your connection.

You then built the BillingManager class to create a JMS message for each reservation. The application invokes your new method whenever a reservation is created. The JMS template then creates a message. The JMS destinations get handled automatically in the template.

Testing JMS Applications

Distributed applications are hard to test. It’s especially hard to test applications that might have remote behaviors. The good news is that with Spring, you don’t have to actually invoke JMS to retrieve your messages. In this example, you’ll learn how to create a stream of messages in the context that can save you the headache of actually creating the JMS infrastructure in every test case.

How do I do that?

You’re going to create a series of messages in the Spring context. Your applications will draw these messages from an array list, instead of actually going across the wire and physically retrieving a JMS message. True, you won’t be testing JMS, but you will be able to test the elements of your application that depend on JMS. You’ll need to do the following:

  • Implement the interface that you use to deal with JMS. In our case, we’ll subclass the façade.

  • In the new test implementation, draw messages from an array list, rather than from JMS.

  • Populate this array list data within the Spring context.

First, let’s create a subclass of the façade, for test purposes only (Example 8-16). You’ll need to modify the JMS method on the façade. Draw your messages from an array list.

Example 8-16. JMSTestRentABike.java
private List testBikes;

    public List getTestBikes( ) {
        return testBikes;
    }

    public void setTestBikes(List testBikes) {
        this.testBikes = testBikes;
    }

    int curBike = 0;
    
    public Bike getNewBikeFromQueue( ) {
      try {
       ActiveMQTextMessage m = (ActiveMQTextMessage)testBikes.get(curBike);
       curBike++;
       Bike b = new Bike( );
       String s = m.getText( );
       String[] vals = s.split(":");
       b.setManufacturer(vals[0]);
       b.setModel(vals[1]);
       return b;       
      } catch (Exception ex) {
       return null;
     }

Next, create some sample data in the context (Example 8-17). Instead of distributed messages, your application will get a predictable list of bikes. This context is purely a testing context, so you’ll want to store it with your test code rather than with your production contexts.

Example 8-17. RentABike-servlet.xml
<bean id="msg1" class="org.codehaus.activemq.message.ActiveMQTextMessage">
   <property name="text">
      <value>Ventana:El Chamuco</value>
   </property>
</bean>

<bean id="msg2" class="org.codehaus.activemq.message.ActiveMQTextMessage">
   <property name="text">
      <value>Ventana:La Bruja</value>
   </property>
</bean>

<bean id="testBikes" class="java.util.ArrayList">
   <constructor-arg>
      <list>
         <ref local="msg1"/>
         <ref local="msg2"/>
      </list>
   </constructor-arg>
</bean>

<bean id="rentaBikeTarget" class="com.springbook.HibRentABike">
   <property name="storeName"><value>Bruce's Bikes</value></property>
   <property name="sessionFactory"><ref local="sessionFactory"/></property>
   <property name="transactionManager">
      <ref local="transactionManager"/>
   </property>
   <property name="jmsTemplate">
      <ref local="billingJmsTemplate"/>
   </property>
   <property name="testBikes">
      <ref local="testBikes"/>
   </property>
</bean>

Finally, create your test case (Example 8-18). Remember to add some logic to terminate the test.

Example 8-18. ControllerTest.java
private ApplicationContext ctx;
RentABike store = null;

public void setUp( ) throws Exception {
   ctx = new FileSystemXmlApplicationContext(
      "/war/WEB-INF/rentaBikeApp-servlet-forJmsTest.xml");
   store = (RentABike)ctx.getBean("rentaBikeTarget");
}

public void testGetBikesFromQueue( ) throws Exception {
    Bike b = store.getNewBikeFromQueue( );
    assertEquals("Ventana", b.getManufacturer( ));
    assertEquals("El Chamuco", b.getModel( ));
    b = store.getNewBikeFromQueue( );
    assertEquals("Ventana", b.getManufacturer( ));
    assertEquals("La Bruja", b.getManufacturer( ));
    b = store.getNewBikeFromQueue( );
    assertNull(b);
}

After adding this test code, you will likely add more tests that rely on the retrieval of these test messages: for instance, testing the notification consumer to handle the messages retrieved appropriately. The test above just demonstrates that your predetermined messages are being “delivered.”

What just happened?

Messaging systems are notoriously difficult to test. They get markedly easier to handle when you, ahem, inject the ideas of dependency injection and pull test data from a context.

In this specific case, you’ve got an interface that separates the JMS consumer interface from the implementation. Our testing implementation doesn’t go to JMS at all; instead, it pulls data from the context. JMS messages are not the only things that you can test in this way:

  • You can build a list of pregenerated random numbers, so your test case is repeatable.

  • You can pull a list of times from a list instead of using a timer. This gives you a mechanism to make a timer predictable, again for the purposes of the test.

  • You can stub out a database and feed a DAO from a list. This makes it much easier to predictably test the layers that might sit on top of a database. It’s also much easier to have separate data sets for contexts that need to test edge conditions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.55.198