Creating a Publish/Subscribe Model with Message-Driven Beans and GlassFish Server

Overview

    Purpose

    This tutorial covers how to create a simple publish/subscribe model using Java Message Service (JMS) and message-driven beans (MDBs.)

    Time to Complete

    Approximately 1 hour

    Introduction

    In this tutorial, you will be implementing a Java Message Service Publish/Subscribe system. The user interface components, a JavaServer Faces (JSF) page and managed bean have already been developed. Your task is to create a second managed bean to produce a message from the data in the form, and publish it to a JMS topic. You will then create message-driven beans (MDBs) that subscribe to the topic and use a message selector to filter the messages depending upon the value of a message property. For the purpose of this tutorial, you will simply write the content of the messages out to the console.

    Scenario

    The Chamber of Commerce for California's Wine Valley (which includes several regions) would like you to create a set of pages to allow visitors to their web site to request hotel accommodations in one or more of the wine regions. Rather than overwhelming their guests with page after page of hotels (and take them off of the Chamber of Commerce page), they have created a simple HTML form that a visitor to their site can use to enter some data and request a response from participating hotels. The data that the hotels have asked for includes: the date the visitor is asking for, a number of nights they would like to stay for, and the number of guests in their party. The JSF-driven form that the Chamber of Commerce built is shown in Figure 1.

    Figure 1: The California Wine Country Chamber of Commerce's Hotel Reservation Request form.

    The Chamber of Commerce would like the data that is entered by the visitor to be sent only to hotels in a particular region, unless the visitor selects All, in which case the data can go to all of the hotels. Hotels in a particular region are added as subscribers to messages for their region. This design is referred to as an Observer Pattern. Messages from a source (in this case, the visitor web page) are consumed by observers interested in messages that pertain to them. Observers can be added or removed without changing the underlying code.

    There are two reasons that the Chamber of Commerce decided to go with this design. First, the design allows hotels to change over time without recompiling or changing the base code. Second, some hotels have automated registration systems and websites, and some just have e-mail. This approach allows the messages to be delivered to each hotel in a flexible way.

    The architecture of the system is shown in Figure 2.

    Figure 2: The California Wine Country Chamber of Commerce JMS publish/subscribe system architecture

    Your task in this tutorial is to implement the message topic producer and MDB subscribers.

    Software Requirements

    The following software is required to complete this tutorial in Windows platform. You must install the software in the given order.

    • Download and install Java JDK 7 from this link.
    • Download and install NetBeans IDE 7.1.2 Java EE version which includes GlassFish 3.1.2 (Java EE download bundle) from this link.
      During installation, be sure to check the box to install Glassfish. JUnit is an optional installation and not required for this tutorial.

    Prerequisites

    Before starting this tutorial, you should:

    • Have the software installed as listed under Software Requirements.
    • Start the NetBeans IDE.
    • Download and unzip the JSFHotelReservation.zip file that contains a NetBeans project you need to complete this tutorial.
      Note: It is recommended that the location where you unzip the NetBeans projects does not contain spaces or non-alphanumeric characters.

Run the JSFHotelReservation Application

    Start the NetBeans IDE, open the provided JSFHotelReservation project, and expand the Web Pages, Source Package and Libraries folders.

    Open the two JSF view pages (index.xhtml and confirm.xhtml) and the ReservationRequestBean managed bean and review the code.

    Open the Services Tab, expand Servers, right-click on GlassFish Server, and select Start.

    Back on the Projects tab, right-click on the JSFHotelApplication and select Run.

    The default browser for the platform will open with the application.
    If the application does not open automatically, open a browser, and start the application by entering http://localhost:8080/JSFHotelReservation as the URL.

    Enter some data into the form and click the Submit button to see the confirmation page.

    You can use the Back button to edit your data. The Confirmed button is not functional yet. You will make it function in this tutorial.

Create a Plain Old Java Object (POJO) to hold the request Data

    In this task, you will create a model class (ReservationRequest) to pass an the reservation request data to the message producer bean.

    Right-click on Source Packages and select New -> Java Class.

    Enter ReservationRequest as the Class Name and obe.model as the Package. Click Finish.

    Copy the all fields except region from the ReservationRequestBean class in the JSFHotelReservation class to the ReservationRequest class.

    Add a new line after numberInParty, then add a constructor by right-clicking in that line and selecting Insert Code.

    Choose Constructor from the list.

    Select all of the fields and click Generate.

    Next generate getters and setters by selecting the line after the constructor and right-clicking in that line and selecting Insert Code. Choose Getter and Setter from the list.

    Select the class name (ReservationRequest) to choose all the fields and click Generate.

Create the Request Producer

    In this task, you will create a JMS Producer bean, and add the necessary JMS resources to the project.

    Create the RequestProducerBean class

      Right-click on the obe.beans package and select New -> Other.
      Choose JavaServer Faces from Categories and JSF Managed Bean from File Types. Click Next.

      R

      Enter RequestProducerBean as the Class Name.
      Enter request as the Name.
      Select dependent as the bean Scope.
      Click Finish.

    Implement the RequestProducerBean class

      This class will also be passed as an object and therefore must implement Serializable.

      public class RequestProducerBean implements Serializable {

      Add annotations to the class to declare a topic called "jms/myTopic" and a factory for the connection called "jms/myTopicFactory".

          @Resource(mappedName = "jms/myTopic")
          private Topic myTopic;
          @Resource(mappedName = "jms/myTopicFactory")
          private ConnectionFactory myTopicFactory;

      Next, write a send method to create and send a map of the fields in the request object to the topic for subscribers to pick up.

          public void send(ReservationRequest request, String region) throws JMSException {
              Connection connection = null;
              Session session = null;
              try {
                  connection = myTopicFactory.createConnection();
                  session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                  MessageProducer messageProducer = session.createProducer(myTopic);
                  MapMessage mm = session.createMapMessage();
                  mm.setString("LastName", request.getLastName());
                  mm.setString("FirstName", request.getFirstName());
                  mm.setString("EMail", request.getEmail());
                  mm.setString("DateRequested", request.getDateRequested());
                  mm.setInt("NumberOfNights", request.getNumberOfNights());
                  mm.setInt("NumberInParty", request.getNumberInParty());
                  mm.setStringProperty("Region", region);
                  messageProducer.send(mm);
              } finally {
                  if (session != null) {
                      try {
                          session.close();
                      } catch (JMSException e) {
                          Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot close session", e);
                      }
                  }
                  if (connection != null) {
                      connection.close();
                  }
              }
          }

      In the send method, an MapMessage, rather than a TextMessage is sent to the topic destination. You also could have sent the request object as an ObjectMessage, however, there are three advantages to a MapMessage. First, if you use an ObjectMessage, then the subscribers, possibly in another server context, must also have the object class definition passed in the ObjectMessage. This would couple the publishers and subscribers a bit more tightly. The second reason to use a MapMessage is to allow the subscriber to be written in another language. If the message payload is a Java Object, the subscribers would have to be written in Java. Finally, using a map allows the message payload to change without requiring a recompilation of the subscribers - the map can contain more (or fewer) elements without affecting the subscriber code.

      Notice that the region string is passed as property of the message. The property will be used by the subscribers to filter out messages that do not apply. The send method is invoked from the ReservationRequestBean. In the next section you will add the code to the ReservationRequestBean class to create the ReservationRequest object and invoke this method.

      Add any missing import statements to the class. Press Ctrl-Shift-I key to open the Fix All Imports wizard.
      Imports that have more that one possible option have a drop down menu. Scroll to the bottom of the list and select javax.jms.Connection to satisfy the Connection object (the default is java.sql.Connection).

      Click OK to fix all of the missing import statements and save the file.

      TIP: You can "clean up" your code and reformat it by pressing the Ctrl-Shift-F key.

Modify the application to send a ReservationRequest option to the RequestProducerBean.

    Modify the ReservationRequestBean

      Open the ReservationRequestBean class.
      Inject an instance of RequestProducer into this bean. With this technique you are effectively "wiring" these two managed beans together.

          @Inject
          private RequestProducerBean producer;


      Add the import statement for javax.inject.Inject.

      Add a method called sendRequest to the ReservationRequestBean class. This method creates an instance of a ReservationRequest object, then calls the send method of the RequestProducerBean. The method also adds a string message to the current JSF view page (the confirmation screen) through the current FacesContext.

          public void sendRequest() {
              FacesContext facesContext = FacesContext.getCurrentInstance();
              ReservationRequest request = new ReservationRequest(lastName, firstName, email, dateRequested, numberOfNights, numberInParty);
              try {
                  producer.send(request, region);
                  FacesMessage facesMessage = new FacesMessage("Request sent successfully");
                  facesMessage.setSeverity(FacesMessage.SEVERITY_INFO);
                  facesContext.addMessage(null, facesMessage);
              } catch (JMSException je) {
                  FacesMessage facesMessage = new FacesMessage("Request NOT sent. Error: " + je);
                  facesMessage.setSeverity(FacesMessage.SEVERITY_ERROR);
                  facesContext.addMessage(null, facesMessage);
              }
          }

      Fix any missing import statements and save the file.

    Modify the confirm.xhtml JSF view page

      Update the Confirmed command button component to call the sendRequest method you added to ReservationRequestBean.
      Remove the attribute style="color: grey" and add an action attribute that invokes the sendRequest method.

      <h:commandButton value="Confirmed" action="#{reserve.sendRequest()}"/>

      Save the file.

    Add a topic and connection factory to the project.

      Right-click on the project and select New -> Other.
      Choose GlassFish from Categories and JMS Resource from File Types. Click Next.

      Enter jms/myTopic as the JNDI Name.
      Select javax.jms.Topic as the Admin Object Resource. Click Next.

      On the JMS Properties screen, enter myTopic in the Value field for Name. Press the Enter key.
      Click Finish.

      Repeat Step 1 above.
      Then, on the New JMS Resource screen, enter jms/myTopicFactory and select javax.jms.TopicConnectionFactory. Click Finish.

Create a subscriber message-driven bean (MDB)

    Create a new EJB Module Project

      Create a new project called NapaSubscriberMDB by clicking on New -> Project.
      Select Java EE from Categories and EJB Module from Projects. Click Next.

      Enter NapaSubscriberMDB as the Project Name.
      The Project Location should be the same directory as the JSFHotelReservation project. Click Next.

      On the next screen, GlassFish Server should be selected.
      Select Enable Contexts and Dependency Injection. Click Finish.

    Add a Message-Driven Bean to the project.

      Right-click on the Source Packages folder and select New -> Other.
      Select Enterprise JavaBean from Categories and Message-Driven Bean from File Types. Click Next.

      Enter NapaSubscriberMDB as the EJB Name.
      Enter obe.mdb as the Package.
      Select Server Destinations and make sure jms/myTopic is selected. Click Finish.

    Modify the NapaSubscriberMDB class

      The annotation @MessageDriven defines the characteristics of this MDB. Add an ActivationConfigurationProperty as the first line to filter messages sent to the topic to those that are for the "Napa Valley" or "All Regions"

      @MessageDriven(mappedName = "jms/myTopic", activationConfig = {
          @ActivationConfigProperty(propertyName = "messageSelector", propertyValue = "Region = 'Napa Valley' OR Region = 'All Regions'"),

      Modify the onMessage method to display the contents of the MapMessage object by iterating through the map key-value pairs. Fix any missing imports.

          public void onMessage(Message message) {
              if (message instanceof MapMessage) {
                  MapMessage mm = (MapMessage) message;
                  String request = null;
                  try {
                      Enumeration<String> mapNames = mm.getMapNames();
                      while (mapNames.hasMoreElements()) {
                          // Read all of the map elements as strings
                          String name = mapNames.nextElement();
                          request += name + ": " + mm.getString(name) + " | ";
                      }
                      System.out.println(this.getClass().getName() + ": Received a request for " + message.getStringProperty("Region"));
                      System.out.println(request);
                  } catch (JMSException ex) {
                      System.out.println("Failed to get request message");
                      Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
                  }
              }
          }

      The onMessage method will be called by the container when a message is sent to the topic that has a header property "Region" that matches "Napa Valley" or "All Regions".

      This method currently just prints the contents of each map element as a string, but this is where the application could send an e-mail to the registered hotels or perform additional processing.

    Deploy the NapaSubscriberMDB

      Right-click on the NapaSubscriberMDB project and select Deploy.

Run the application and test it

    In a new browser window, open the JSFHotelReservation application again by entering the URL: http://localhost:8080/JSFHotelReservation.
    Enter some data, select Napa Valley as the region and click Submit.
    On the Confirmation page, when you click Confirmed, your request should be sent successfully as an object to the topic, and a success message will appear on the page.

    In the Output Window, under the GlassFish Server tab, you will see that the NapaSubscriberMDB received a request for reservations in Napa Valley

    Enter another Reservation request, but this time choose All.


    The message is again printed by the NapaSubscriberMDB

    Try creating reservation requests for other regions. Do they print out?

Add a second subscriber MDB

    Repeat the steps in the section Create a subscriber message-driven bean (MDB), but instead of Napa, use Sonoma.

    Create a new project with the name SonomaSubscriberMDB

    Create a new package, obe.mdb.
    Right-click on the Source Packages folder and select New -> Java Package.

    Enter obe.mdb as the Package Name and click Finish.

    Perform a Refactor Copy on the NapaSubscriberMDB.java file in the NapaSubscriberMDB project. Right-click on the file and select Refactor then Copy.

    Enter SonomaSubscriberMDB as the New Name.
    Select SonomaSubscriberMDB as the project and click Refactor.

    Add the ReservationRequestLibrary to the project. Look at the task Add the ReservationRequestLibrary to the project as a reminder for how to add a library to the project.

    In the SonomaSubscriberMDB class, modify the messageSelector filter in the first ActivationConfigProperty to reflect 'Sonoma Valley'.

        @ActivationConfigProperty(propertyName = "messageSelector", propertyValue = "Region = 'Sonoma Valley' OR Region = 'All Regions'"),

    Modify the clientId and subscriptionName properties to reflect SonomaSusbcriberMDB instead of NapaSubscriberMDB.

        @ActivationConfigProperty(propertyName = "clientId", propertyValue = "SonomaSubscriberMDB"),
        @ActivationConfigProperty(propertyName = "subscriptionName", propertyValue = "SonomaSubscriberMDB")

    Save the file and deploy the SonomaSubscriberMDB project.

    Run the application again, and this time select Sonoma Valley as your destination. You will see SonomaValleyMDB receive the request, and if you select All as your destination Region, there will be two sets of messages printed, one from the NapaSubscriberMDB and the other from the SonomaSubscriberMDB. Note, the order that the messages print in may be different.

Summary

    In this tutorial, you successfully implemented the California Wine Country Chamber of Commerce Java Message Service Publish/Subscribe architecture.

    In this tutorial, you have learned how to:

    • Create a dependent managed bean and inject that bean into another JSF managed bean
    • Create a topic destination and publish a MapMessage to it
    • Create JMS subscribers as Message-driven Beans (MDB's)
    • Add a message filter property to a subscriber

    Resources

    Credits

    • Lead Curriculum Developer: Tom McGinn

To help navigate this Oracle by Example, note the following:

Hiding Header Buttons:
Click the Title to hide the buttons in the header. To show the buttons again, simply click the Title again.
Topic List Button:
A list of all the topics. Click one of the topics to navigate to that section.
Expand/Collapse All Topics:
To show/hide all the detail for all the sections. By default, all topics are collapsed
Show/Hide All Images:
To show/hide all the screenshots. By default, all images are displayed.
Print:
To print the content. The content currently displayed or hidden will be printed.

To navigate to a particular section in this tutorial, select the topic from the list.