Java Magazine Logo
Originally published in the March/April 2014 issue of Java Magazine. Subscribe today.

New to Java, Part 3: Three Hundred Sixty–Degree Exploration of Java EE 7

by Josh Juneau

Learn how to use the Java API for Batch Processing, utilize Java Message Service, and create a chat client using WebSockets.

This article is the third in a three-part series demonstrating how to use Java EE 7 improvements and newer web standards, such as HTML5, WebSocket, and JavaScript Object Notation (JSON) processing, to build modern enterprise applications.

In this third part, we will improve the movieplex7 application, which we started in the first two articles, by adding the ability to view ticket sales, accumulate movie points, and chat with others.

Note: The complete source code for the application can be downloaded here.

Overview of the Demo Application

In the first two articles, we learned how to download, install, and configure NetBeans and GlassFish 4, which will also be used for building and deploying the application in this article. We built the application from the ground up, using JavaServer Faces (JSF) 2.2 and making use of new technology, such as the Faces Flow for navigation. We demonstrated how to utilize Java Persistence API (JPA) 2.1, Java API for RESTful Web Services (JAX-RS), and JSON Processing (JSON-P) 1.0 for implementing business logic and data manipulation.

For the remainder of this article, please follow along using the Maven-based project that you created using NetBeans IDE in Part 1 and Part 2 of this series.

Movie Ticket Sales

Java API for Batch Processing 1.0 (JSR 352) will be used in this section to import movie ticket sales for each show and populate the database by reading cumulative sales from a comma-separated values (CSV) file.

Batch processing is a method for executing a series of tasks or jobs. Most often, these jobs do not require intervention, and they are bulk-oriented and long-running. For details regarding batch processing terminology, please refer to the “Java EE 7 Tutorial.”

Generate classes. First, let’s create the classes that will be used to process movie sales and persist them to the database.

  1. Right-click Source Packages, select New and Java Package, and then specify the name of the package as org .glassfish.movieplex7 .batch. Click Finish.
  2. Right-click the newly created package, select New and Java Class, and then provide the name SalesReader. Change the class definition to extend AbstractItemWriter, and resolve imports.


    AbstractItemReader
    implements the ItemReader interface, which defines methods for reading a stream of items.

  3. Add @Named and @Dependent as class-level annotations, and resolve imports.


    The @Named annotation allows the bean to be injected into the job XML, and @Dependent makes the bean available for injection.

  4. Add the following field to the class to declare the reader:
    
    private BufferedReader reader;
     
    
  5. Provide an implementation for reading the CSV file by adding the open method shown in Listing 1.
    
    public void open(Serializable checkpoint) throws Exception {
            reader = new BufferedReader(
                    new InputStreamReader(Thread.currentThread()
                    .getContextClassLoader().getResourceAsStream
                   ("META-INF/sales.csv")));
        }
    

    Listing 1

    For this example, the CSV file must be placed within the META-INF directory of the application, which resides at the root of the application source packages.

  6. Override the readItem method and replace with the code shown in Listing 2. Resolve imports.
    
    @Override
        public String readItem() {
            String string = null;
            try {
                string = reader.readLine();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            return string;
        }
    

    Listing 2

    The readItem method is responsible for reading the next item from the stream, with a null indicating the end of the stream.

  7. Add a new Java class to the org.glassfish.movieplex7.batch package, and name it SalesProcessor. Implement ItemProcessor by adding the following to the class definition:
    
    implements ItemProcessor
    

    Implementing ItemProcessor allows the class to operate on an input item and produce an output item.

  8. Add @Named and @Dependent class-level annotations, and resolve imports. Click the yellow lightbulb, and choose Implement all abstract methods, as shown in Figure 1.

    ntj-pt3-f1

    Figure 1
  9. Change the implementation of processItem to match that shown in Listing 3 and resolve imports.
    
    @Override
        public Sales processItem(Object s) {
            Sales sales = new Sales();
            StringTokenizer tokens = new StringTokenizer
            ((String) s, ",");
            sales.setId(Integer.parseInt(tokens.nextToken()));
            sales.setAmount(Float.parseFloat(tokens.nextToken()));
            return sales;
        }
    

    Listing 3

    The processItem method creates a new Sales object, and then it creates a StringTokenizer that is used to parse through each item within the object passed in. The Sales object is populated with the processed item value and then returned.

  10. Create a new Java class within the org.glassfish.movieplex7 .batch package, and name it SalesWriter. Extend AbstractItemWriter by adding the following to the class definition:
    
    extends AbstractItemWriter
    

     

  11. Add @Named and @Dependent class-level annotations, and then resolve imports.
  12. Click the yellow lightbulb to add the import for javax.batch .api.chunk.AbstractItemWriter. Once the import has been added, click the lightbulb and choose Implement all abstract methods to add the writeItems method to the class.
  13. To gain access to the data store, inject an EntityManager instance into the class by adding the following declaration:
    
    @PersistenceContext
    EntityManager em;
     
    
  14. Replace the code within the writeItems method with a for loop, which will be used for persisting all the Sales objects that have been aggregated from the batch runtime, as shown in Listing 4.
    
    @Override
        @Transactional
        public void writeItems(List items) throws Exception {
            for (Sales sale : (List<Sales>) items) {
                em.persist(sale);
            }
        }
    

    Listing 4

  15. Add the @Transactional annotation to the method to incorporate transaction control into your method. Resolve the imports.

Create a batch job. The next task is to implement a procedural task within XML. In this case, the job will consist of one chunk that contains three items. Those items are the <reader>, <processor>, and <writer> elements, and their respective class implementations are SalesReader, SalesProcessor, and SalesWriter. To create the task within XML, use the following procedure:

  1. Create a new folder to contain the batch XML by right- clicking META-INF and selecting New and then Folder. Name the folder batch-jobs. Click Finish.
  2. Create the XML file by right-clicking the batch-jobs folder and selecting New and then XML Document. Name the file eod-sales. Click Next and accept all default values.

    Click Finish.

    Note the following about the XML file:

    • The item-count attribute of <chunk> indicates that there are three items to be given to the writer.
    • The skip-limit attribute of <chunk> indicates the number of exceptions that will be skipped.
    • <skippable-exception-classes/> lists the set of exceptions to be skipped by chunk processing. 
  3. Add the job definition to the XML by replacing the contents of the newly created file with the code shown in Listing 5.
    
    <?xml version="1.0"?>
    <job id="endOfDaySales" xmlns=
    "http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
        <step id="populateSales" >
            <chunk item-count="3" skip-limit="5">
                <reader ref="salesReader"/>
                <processor ref="salesProcessor"/>
                <writer ref="salesWriter"/>
                <skippable-exception-classes>
                   <include class="java.lang.NumberFormatException"/>
                </skippable-exception-classes>
            </chunk>
        </step>
    </job>
    

    Listing 5

Invoke the batch job. Create the implementation for invoking the batch job.

  1. Right-click the org.glassfish .movieplex7.batch package, select New and then Session Bean, and then provide the name SalesBean. Click Finish.
  2. Add the runJob method, as shown in Listing 6, to the new session bean.
    
    public void runJob(){
            try {
                JobOperator jobOp = BatchRuntime.getJobOperator();
                long jobId = jobOp.start
                ("eod-sales", new Properties());
                System.out.println("Job Start ID: "  +jobId);
            } catch (JobStartException ex){
                ex.printStackTrace();
            }
            
        }
    

    Listing 6

    In the code, the BatchRuntime is used to obtain a new JobOperator. The new JobOperator is then used to start the batch process outlined within eod-sales, and it could also be used for stopping or restarting the job.

  3. Add the @Named annotation to the class to make it injectable. Resolve imports.
  4. Inject EntityManager into the class, as seen below:
    
    @PersistenceContext
    EntityManager em;
    

     

  5. Add a method named get SalesData to the class, which will use an @NamedQuery to return all rows within the table, as shown in Listing 7. Resolve imports.
    
    public List<Sales> getSalesData() {
            return em.createNamedQuery("Sales.findAll", Sales.class)
                   .getResultList();
        }
    

    Listing 7

  6. To create a view to show the sales, right-click Web Pages and select New and then Folder. Specify batch for the folder name. Click Finish, and right-click the newly created folder and select New and then Faces Template Client. Specify sales for the File Name, and browse to WEB-INF/template.xhtml to specify the template. Then click Finish. Replace all the content within the <ui:composition> tags with the code shown in Listing 8. Repair namespace prefix/URI mapping by clicking the yellow lightbulb.
    
    <h1>Movie Sales</h1>
     <h:form>
                <h:dataTable value="#{salesBean.salesData}" var=
    "s" border="1"> <h:column>
                        <f:facet name="header"> <h:outputText value=
    "Show ID" />
                        </f:facet>
                        #{s.id} </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText value="Sales" /> </f:facet>
                        #{s.amount} </h:column>
                </h:dataTable>
                <h:commandButton value="Run Job" action="sales" actionListener="#{salesBean.runJob()}"/>
                <h:commandButton value="Refresh" action="sales" />
     </h:form>
    

    Listing 8

    This page contains a dataTable, which will be used to display the contents of the sales file once the batch process has populated the database. It also contains two commandButtons: one to run the job and one to refresh the page.

  7. Edit the template.xhtml file to incorporate a link to the sales .xhtml view by adding the code shown in Listing 9.
    
    <h:outputLink value=
    "${facesContext.externalContext.requestContextPath}
    /faces/batch/sales.xhtml">Sales</h:outputLink>
    

    Listing 9

  8. Finally, run the project, and click the Sales link in the left navigational menu, which will open the Movie Sales view. Click the Run Job button; the batch job will be initiated, processing the CSV file and inserting the amounts into the SALES database table. Click the Refresh button to display the list of sales that have been processed, as shown in Figure 2.

    ntj-pt3-f2

    Figure 2

Movie Points

To implement a movie point system for the movieplex7 application, we’ll use Java Message Service (JMS) 2.0 and its new API. JMS 2.0 increases developer productivity by decreasing the amount of code and complexity that are necessary for sending and receiving messages compared with prior releases.

Note: In order to work with JMS, a topic or queue must be created within the application server container. This can be done with code or via the application server’s administrative utilities. In this example, you will learn how to create a queue using code.

Create a JSF managed bean. First, let’s create a JSF managed bean that will be bound to a JSF view for collecting movie points data and sending that data to the queue.

  1. Create a new package in the movieplex7 application by right-clicking Source Packages and then selecting New and Java Package. Specify the name org.glassfish.movieplex7 .points, and then click Finish.
  2. Create a class within the new package by right-clicking the package and selecting New and then Java Class. Specify the name SendPointsBean. Implement Serializable, and add the following class-level annotations to make the bean Expression Language (EL)–injectable and session-scoped. Resolve imports.
    
    @Named
    @SessionScoped
    

     

  3. Add the String field shown in Listing 10 to the class, which will be bound to a JSF inputText field to capture point data. Generate the getters/setters for the field by right-clicking the editor pane and selecting Insert Code and then Getter and Setter. Select the field, and click Generate.
    
    @NotNull
    @Pattern(regexp = "^\\d{2},\\d{2}",
       message = "Message format must be 2 digits, comma, 
    
    2 digits, e.g. 12,12")
    private String message;
    

    Listing 10

    Note: This field uses bean validation annotations to ensure that the text entered into the field adheres to the required format.

  4. Inject an instance of JMSContext and a Queue into the class by adding the following code. Then resolve imports, taking care to import javax.jms.Queue.
    
    @Inject
    JMSContext context;
        
    @Resource(mappedName =
     "java:global/jms/pointsQueue")
    Queue pointsQueue;
    

    JMSContext is a new JMS 2.0 interface that combines Connection and Session objects into a single object.

    Note: Java EE–compliant application servers contain a default JMS connection factory under the name java:comp/DefaultJMSConnectionFactory, which is used when no connection factory is specified.

  5. To send the message to the JMS queue, add the method shown in Listing 11 to the class.
    
    public void sendMessage() {
            System.out.println("Sending message: " + message);
            context.createProducer().send(pointsQueue, message);
        }
    

    Listing 11

    The JMSContext createProducer method can be used to create a JMSProducer, which provides methods to configure and send messages synchronously and asynchronously. The send method of the JMSProducer is used to send a message to the queue instance, which we will create in the next step.

  6. Right-click the org.glassfish .movieplex7.points package, select New and then Java Class, and specify the name ReceivePointsBean. Implement Serializable, and add the class-level annotations shown in Listing 12.
    
    public int getQueueSize() {
            int count = 0;
            try {
                QueueBrowser browser = context.createBrowser
                (pointsQueue);
                Enumeration elems = browser.getEnumeration();
                while (elems.hasMoreElements()) {
                    elems.nextElement();
                    count++;
                }
            } catch (JMSException ex) {
                ex.printStackTrace();
            }
            return count;
        }
    

    Listing 12

    Introduced in JMS 2.0, the @JMSDestinationDefinition annotation is used to reduce the administrative overhead of application configuration by programmatically provisioning required resources. In this case, the annotation is used to create a javax.jms.queue named java:global/jms/pointsQueue.

  7. Inject the JMSContext and Queue resources that will be used within the class by adding the following code. Then resolve imports, being sure to import the correct classes: javax.jms.Queue and so on.
    
    @Inject
    JMSContext context;
    @Resource(mappedName=
     "java:global/jms/pointsQueue")
     Queue pointsQueue;
    

    Note: Although we are creating the queue by using the @JMSDestinationDefinition annotation, we still need to inject the queue into the class via pointsQueue to make it usable.

  8. Add the receiveMessage() method shown in Listing 13.
    
    public String receiveMessage() {
            String message
                    = context.createConsumer(pointsQueue).
                            receiveBody(String.class);
            System.out.println("Received message: " + message);
            return message;
        }
    

    Listing 13

    This method uses the JMSContext to create a consumer for the pointsQueue and then synchronously receive a String message from the queue.

  9. Add a method named getQueueSize, which is shown in Listing 14, for returning the number of messages currently in the queue. Resolve imports.
    
    public int getQueueSize() {
            int count = 0;
            try {
                QueueBrowser browser = context.createBrowser
                (pointsQueue);
                Enumeration elems = browser.getEnumeration();
                while (elems.hasMoreElements()) {
                    elems.nextElement();
                    count++;
                }
            } catch (JMSException ex) {
                ex.printStackTrace();
            }
            return count;
        }
    

    Listing 14

    This method creates a QueueBrowser and then uses it to traverse through each message within the queue and add it to the total.

Create a Facelet. Next, we need to create a Facelet view that will be used for entering movie points and simulating the send/receive message functionality.

  1. Add a new folder by right-clicking Web Pages and selecting New and then Folder. Name the folder points, and then click Finish.
  2. Create a new XHTML file named points.xhtml within that folder. Add the code shown in Listing 15 to the view, replacing the code within the <ui:composition> elements. Click the yellow lightbulb to resolve the namespace prefix/URI mapping, as needed.
    
    <h1>Points</h1> 
     <h:form>
        Queue size:
        <h:outputText value="#{receivePointsBean.queueSize}"/><p/>
        <h:inputText value="#{sendPointsBean.message}"/>
        <h:commandButton value="Send Message"
            action="points"
            actionListener="#{sendPointsBean.sendMessage()}"/>
      </h:form>
      <h:form>
        <h:commandButton
            value="Receive Message"
            action="points" actionListener="#{receivePointsBean.receiveMessage()}"/>
      </h:form>
    

    Listing 15

    This view contains a field for entering a message, along with a commandButton that invokes the sendMessage method. When the method is invoked, the message contained within the inputText is added to the queue, and the queueSize is increased by 1. Another commandButton in the view invokes the receiveMessage method, which will read a message from the queue and decrement the queueSize by 1.

  3. Add the code shown in Listing 16 to template.xhtml along with the other outputLink components, exposing the points.xhtml view to the application.
    
    <p/>
    <h:outputLink value=
        "${facesContext.externalContext.requestContextPath}
    
    /faces/points/points.xhtml">
        Points</h:outputLink>
    

    Listing 16

Run the movieplex7 application.

  1. Click the Points link (see Figure 3). Currently, the queue contains zero messages.

    ntj-pt3-f3

    Figure 3
  2. Add the text 1212 to the field and click Send Message.


    An error will be displayed (see Figure 4). This message was produced by the bean validation that was added to SendPointsBean.

    ntj-pt3-f4

    Figure 4

  3. Insert a comma so the contents of the field is 12,12 and click Send Message.


    This time the error message disappears and the queue is increased by 1 (see Figure 5).

    ntj-pt3-f5

    Figure 5

  4. Click the Receive Message button, and note that the queue is decremented by 1, because a message has been read from the queue (see Figure 6).

    ntj-pt3-f6

    Figure 6

Continue to send and receive messages in any order. You should notice the queue number being incremented each time a message is sent and decremented with every message received.

Movie Chat

In this section, we will build a chat room for movieplex7 visitors using the WebSocket 1.0 API, which provides a full-duplex, bidirectional communication protocol over a single TCP connection that defines an opening handshake and data transfer.

A standard API for WebSocket, under JSR 356, has been added to Java EE 7, enabling developers to create WebSocket solutions using a common API, rather than customized frameworks.

Create a class to implement WebSocket communication. To begin, let’s create the Java class for implementation of the WebSocket communication.

  1. Create a new package by right-clicking Source Packages and selecting New and then Java Package. Name the package org.glassfish.movieplex7.chat.
  2. Create the class by right-clicking Source Packages and selecting New and then Java Class. Name the class ChatServer.
  3. Replace the code within the new class with the code shown in Listing 17. Resolve imports, being careful to ensure the import of javax.websocket.Session, among others.
    
    public class ChatServer {
    
        private static final Set<Session> peers = 
            Collections.synchronizedSet(new HashSet<Session>());
    
        @OnOpen
        public void onOpen(Session peer) {
            peers.add(peer);
        }
    
        @OnClose
        public void onClose(Session peer) {
            peers.remove(peer);
        }
    
        @OnMessage
        public void message(String message, Session client)
             throws IOException, EncodeException {
            for (Session peer : peers) {
                if (!peers.equals(client)) {
                    peer.getBasicRemote().sendObject(message);
                }
    
            }
        }
    }
    

    Listing 17

The code for the ChatServer class does the following:

  • @ServerEndpoint is used to indicate that this class is a WebSocket endpoint. The value attribute defines the URI to be used for accessing the endpoint.
  • @OnOpen and @OnClose annotate the methods that are invoked when a session is opened or closed, respectively. The Session parameter defines the client that is requesting the initiation or termination of a connection.
  • @OnMessage annotates the method that is invoked when a message is received. The first parameter is the payload of the message, and the second is the client session that defines the other end of the connection.


    This method broadcasts the received message to all the connected clients.

Generate the web view. Now, let’s generate the web view for the chat client.

  1. Right-click Web Pages and select New and then Folder. Provide the name chat, and click Finish.
  2. Create a new XHTML file, specifying chatroom for the File Name, and browse to WEB-INF/template.xhtml to specify the template. Keep the other defaults and click Finish.
  3. Replace all the content within the <ui:composition> tags with the code shown in Listing 18.
    
    <ui:define name="content"> <form action="">
       <table> <tr>
           <td>
              Chat Log<br/>
              <textarea readonly="true" rows="6" cols="50"
                id="chatlog"></textarea> </td>
           <td>
              Users<br/>
              <textarea readonly="true" rows="6" cols="20"
                id="users"></textarea> </td>
         </tr> <tr>
           <td colspan="2">
              <input id="textField" name="name" value="Duke" type="text"/> 
              <input onclick="join();" value="Join" type="button"/>
              <input onclick="send_message();" value="Send"
                type="button"/><p/>
              <input onclick="disconnect();" value="Disconnect"
                type="button"/> </td>
         </tr>
       </table>
     </form>
     <div id="output"></div>
    
     <script language="javascript" type="text/javascript" src="${facesContext.
    externalContext.requestContextPath}/chat/websocket.js">
    </script>
     </ui:define>
    

    Listing 18

    This code constructs the HTML form that will be used for the chat client, using two textareas that will be used to display the chat log and current user list and an input textField for entering a username and messages. There are three buttons: one to open a WebSocket session, another to send a message, and the last to disconnect the session. All three are bound via the onclick attribute to JavaScript functions, which implement the WebSocket communication. Near the end of the HTML is a <script> element, which includes the websocket .js JavaScript file.

  4. To create the JavaScript file that will contain the WebSocket communication implementation, right-click chat in Web Pages, select New and then Web and JavaScript File. Name the file websocket, and click Finish. Edit websocket.js so it contains the code shown in Listings 19a and 19b.
    
    var wsUri = 'ws://' + document.location.host
            + document.location.pathname.substr(0,
                    document.location.pathname.indexOf
                    ("/faces")) + '/websocket';
    console.log(wsUri);
    var websocket = new WebSocket(wsUri);
    var username;
    websocket.onopen = function(evt) {
        onOpen(evt);
    };
    websocket.onmessage = function(evt) {
        onMessage(evt);
    };
    websocket.onerror = function(evt) {
        onError(evt);
    };
    websocket.onclose = function(evt) {
        onClose(evt);
    };
    var output = document.getElementById("output");
    function join() {
        username = textField.value;
        websocket.send(username + " joined");
    }
    function send_message() {
    
        websocket.send(username + ": " + textField.value);
    }
    

    Listing 19a 

    
    function onOpen() {
        writeToScreen("CONNECTED");
    }
    function onClose() {
        writeToScreen("DISCONNECTED");
    }
    function onMessage(evt) {
        writeToScreen("RECEIVED: " + evt.data);
        if (evt.data.indexOf("joined") !== -1) {
            users.innerHTML += evt.data.substring
            (0, evt.data.indexOf(" joined")) + "\n";
        } else {
            chatlog.innerHTML += evt.data + "\n";
        }
    }
    function onError(evt) {
        writeToScreen('<span style="color: red;">ERROR:</span> ' +
                evt.data);
    }
    function disconnect() {
        websocket.close();
    }
    function writeToScreen(message) {
        var pre = document.createElement("p");
        pre.style.wordWrap = "break-word";
        pre.innerHTML = message;
        output.appendChild(pre);
    }
    

    Listing 19b 

    Learn More


     Part 1 of this series

     Part 2 of this series

    The websocket.js implementation constructs the endpoint URI by appending the URI specified in the ChatServer class. A new WebSocket object is then created, and each WebSocket event function is assigned to a JavaScript function that is implemented within the file. When a user clicks the Join button on the page, the username is captured, and the initial WebSocket send method is invoked, passing the username.

    When messages are sent, any relevant data is passed as a parameter to the send_message function, which appends the username to the message and broadcasts to all clients. The onMessage method is invoked each time a message is received, updating the list of logged-in users. The Disconnect button on the page initiates the closing of the WebSocket connection.

  5. Edit WEB-INF/template.xhtml and overwrite the outputLink element for Item 2 with the code shown in Listing 20.
  6. Run the project and navigate to the chat room by clicking the Chat Room link in the left menu, as shown in Figure 7.

    ntj-pt3-f7

    Figure 7
    
    <h:outputLink value=
      "${facesContext.externalContext.requestContextPath}
    
    /faces/chat/chatroom.xhtml">
      Chat Room
    </h:outputLink>
    

    Listing 20

    You will see the CONNECTED message presented at the bottom of the chat room (see Figure 8).

    ntj-pt3-f8

    Figure 8

  7. Open a separate browser and enter the URI localhost:8080/movieplex7, and then open the chat room in that browser. Click Join in one of the browser windows (we will refer to it as “browser 1”), and you will be joined to the room as Duke. You should see the user list updated in the other browser window (browser 2) as well. Join the session in browser 2 under a different name (for example, Duke2), and you should see the user list in each of the windows updated (see Figure 9).

    ntj-pt3-f9

    Figure 9


Note:
Chrome Developer Tools can be used to monitor WebSocket traffic.

Conclusion

In this article, we modified the movieplex7 application that was started in Part 1 and Part 2, providing the ability to calculate movie sales, assign movie points, and chat with peers. The article covered the following technologies:

  • Batch processing for Java EE
  • JMS 2.0
  • WebSocket 1.0

This three-part article series has taken you on a whirlwind tour of the new features that are available in Java EE 7. To learn more about Java EE 7, take a look at the online tutorial.


juneau-headshot

Josh Juneau is an application developer, system analyst, and DBA. He primarily develops using Java, PL/SQL, and Jython/Python. He manages the Jython Monthly newsletter, Jython Podcast, and the Jython website. He authored Java EE 7 Recipes: A Problem-Solution Approach (Apress, 2013)and Introducing Java EE 7: A Look at What’s New (Apress, 2013).