CHAPTER 17

image

WebSockets and JSON-P

The Java EE 7 platform aims to provide a common ground for developing Java Enterprise solutions that incorporate HTML5 technology. As such, there are a few core features that have been added to Java EE 7, allowing for better bidirectional support of HTML5. The Java EE 7 platform eases communication between the client and the server via a technology named WebSockets, enabling more parity with the HTML5 standard. WebSockets is a full-duplex communication mechanism that allows both textual and binary messages to be sent between clients and servers, without the HTTP request/response life cycle. WebSockets allow either the client or the server to send a message at any time, providing an asynchronous solution for working with data while the user is performing a task.

HTML5 has become the mainstream markup language for developing content that can be presented via the World Wide Web. It defines a standard, which can be used to produce both HTML and XHTML documents. Along with standardization, HTML5 also brings forth semantic features that were previously only possible on desktop application platforms. For example, elements such as <video> and <audio> allow media content to be embedded directly in web pages, without the need to embed a media player solution. There is no doubt that HTML5, the fifth revision of the HTML standard, is opening the doors to new possibilities in web application development.

The universally supported JSON (JavaScript Object Notation) object has become a widely adopted solution for sending data between points. HTML5-based web applications can utilize JSON to transport data, using WebSockets, Ajax, or other transport technologies. The Java EE 7 platform provides the JSON-P API, which introduces utilities that make it easier to build and work with JSON objects within the Java language.

This chapter will focus on recipes that demonstrate these HTML5 APIs. You will learn how to make use of WebSockets and JSON-P so that your application’s client-server communication can become seamless, whether the user interface is written with HTML5, JSF, or another markup language.

17-1. Creating a WebSocket Endpoint

Problem

You wish to create a WebSocket endpoint that can be used to receive messages asynchronously.

Solution

Create a WebSocket endpoint by annotating a server-side POJO class and a method within that class, accordingly. In the following example, a simple POJO class, named org.javaeerecipes.chapter17.recipe17_01.BookChatEndpoint, is annotated to indicate that it should be accessible via the web as a WebSocket endpoint. The class contains a method named messageReceiver, which is annotated to make it accessible to a client as a callable message consumer.

import javax.websocket.OnMessage;
import javax.websocket.server.ServerEndpoint;
...
@ServerEndpoint(path="/bookChatEndpoint")
public class BookChatEndpoint {

 @OnMessage
 public String messageReceiver(String message) {
     return "Message Received: " + message;
 }
}

The WebSocket endpoint will be accessible to clients at the URL ws://localhost:8080/JavaEERecipes/bookChatEndpoint. When a message is sent from a client to the endpoint, it is sent to the messageReceiver method, where it is processed accordingly. In this case, a simple String message is returned to the client.

How It Works

A server-side class can accept messages from clients by configuring it as a WebSocket endpoint. To develop a WebSocket endpoint, create a Java POJO, and annotate it with @ServerEndpoint. The @ServerEndpoint annotation accepts a String-based path attribute, which is used to indicate the URI at which the server is available to accept client messages. Therefore, when the server is started, the value of the path attribute would be appended to the end of the context path and application name in which the WebSocket resides. By initiating a call to that URL, one method, annotated with @OnMessage, will be invoked to process the message that is sent.

In the example, a class named BookChatEndpoint is annotated as a WebSocket, so it is accessible to clients as an endpoint for receiving messages, and returning a response. When initiating communication with the WebSocket endpoint, the client must utilize a URL that contains a URI scheme of “ws,” rather than “http.” The “ws” URI scheme was introduced by the WebSocket protocol, and as such, indicates that the URL is used for communication with a WebSocket. In this example, a client can send a message to the server via the bookChatEndpoint WebSocket, and the server can send a message back at the same time, because WebSockets allow for full-duplex communication. Full-duplex communication is an HTML5 standard, rather than standard HTTP, which utilizes a request-response communication.

17-2. Sending Messages to a WebSocket Endpoint

Problem

You would like to send a message from a client to a WebSocket endpoint that is available on a server.

Solution

Engineer a JavaScript solution that can be used to send messages from a client browser to a WebSocket endpoint. Invoke the JavaScript function via an action event that is bound to an HTML input tag within the view. In the following example, a button contains an onclick attribute that will invoke a JavaScript function named bookChatRelay. The bookChatRelay function is responsible for opening a session with a WebSocket endpoint so that messages can be sent. The following listing is an excerpt from the recipe17_02.xhtml JSF view, which is located within the web/chapter17 directory of the JavaEERecipes source bundle.

...
<html>
    <head>
        <script type="text/javascript">
            var ws;
            function bookChatRelay()
            {
                if ("WebSocket" in window)
                {
                    alert("WebSocket is supported by your Browser!");
                    
                    if (ws == null){
                        alert("Creating new websocket connection");
                        ws = new WebSocket("ws://localhost:8080/JavaEERecipes/bookChatEndpoint");
                    } else {
                        ws.send("Another message");
                    }
                    ws.onopen = function()
                    {
                        // Web Socket is connected, send data using send()
                        ws.send("Message to send");
                        alert("Message is sent...");
                    };
                    ws.onmessage = function (evt)
                    {
                        var received_msg = evt.data;
                        alert("Message from server: " + received_msg);
                    };
                    ws.onclose = function()
                    {
                        // websocket is closed.
                        alert("Connection is closed...");
                    };
                }
                else
                {
                    // The browser doesn't support WebSocket
                    alert("WebSocket NOT supported by your Browser!");
                }
            }
            function closeConnection(){
                if (ws !== null){
                    ws.close();
                    ws = null;
                }
            }
            
        </script>
    </head>
    <body>
        
        <input id="wsRelay" type="button" value="WebSocket Test Message"
               onclick="bookChatRelay();"/>
        <input id="closeConn" type="button" value="Close Connection"
               onclick="closeConnection();"/>
    </body>

</html>

When the button is pressed, the message will be sent from the browser client to the WebSocket endpoint, and a message will be returned from the endpoint to the client.

image Note   The JavaScript code in this test creates a new WebSocket connection each time the button on the page is pressed. This is okay for testing purposes, but in a real-life scenario, you will want to retain and reuse the connection, if possible.

How It Works

The ability to asynchronously send messages (text or binary) from a client to a server defines the foundation of Ajax and HTML5 capability. The WebSockets API allows developers to send messages to the server via JavaScript calls to a WebSocket endpoint. Conversely, the API allows clients to receive messages and process them accordingly via a series of JavaScript functions. The example for this recipe demonstrates how to send a message to a WebSocket endpoint by clicking on a button in a web page. When the button is clicked, a JavaScript function named bookChatRelay is invoked, which embodies the processing implementation.

To send a message to a WebSocket endpoint via a JavaScript function, the first task is to confirm whether the user’s browser is capable of working with WebSockets (HTML5 compliant). This confirmation can be done by using a conditional statement to verify if the “WebSocket” object is available within the client by using the following if-statement:

if("WebSocket" in window){
...
} else {
...
}

If the client browser is capable of working with WebSockets, then the implementation inside the if block is invoked; otherwise, the implementation within the else block is invoked. To process the WebSocket message, a new WebSocket object must be instantiated to establish the server connection, which is done by passing the URL to the WebSocket endpoint to a new WebSocket object.

var ws = new WebSocket("ws://localhost:8080/JavaEERecipes/bookChatEndpoint");

The constructor for creating a WebSocket takes either one or two parameters. The first parameter is the URL of the server to which the WebSocket will connect, and the optional second parameter is a String of protocols that can be used for message transmission. The WebSocket object contains a handful of events that are utilized to help implement message processing. Table 17-1 lists the different events that can occur in the life cycle a WebSocket object, along with a description of what they do.

Table 17-1. JavaScript WebSocket Object Events

Event Handler Method Description
open onOpen Occurs when the WebSocket connection is established.
close onClose Occurs when the WebSocket connection is closed.
error onError Occurs when there is a communication error.
message onMessage Occurs when data is received from the server.

After the WebSocket object has been instantiated successfully, a connection to the server will be established, which will cause the open event to occur. To process this event, assign a function to the onOpen handler, and process events accordingly within that function. Messages are usually sent to the server when the open event occurs, and this is demonstrated within the example.

ws.onopen = function()
{
   // Web Socket is connected, send data using send()
   ws.send("Message to send");
   alert("Message is sent...");
};

Similarly, you can listen for any other events to occur, and then process tasks accordingly when they do. In the example, when a message is received from the server, it is printed within an alert dialog. Also in the example, when the WebSocket is closed, an alert dialog is presented to the user.

The example does not demonstrate all the possible ways that the WebSocket object in JavaScript can be utilized. For instance, you could send messages to the server by invoking the send() method, and passing the data that you wish to send as a parameter. The close() method can be called on a WebSocket to manually terminate the existing connection. WebSocket objects also contain the helpful attributes, readyState and bufferedAmount, which can be used for obtaining information about a connection. The readyState attribute will advise the current state of the WebSocket connection via a returned number, and bufferedAmount attribute value represents the number of bytes of UTF-8 text that have been queued using the send() method. Table 17-2 displays the different possible values for the readyState attribute, along with a description of each.

Table 17-2. JavaScript WebSocket readyState Values

Value Description
0 Connection not yet established.
WebSocket.CONNECTING
1 Connection established, and communication is possible.
WebSocket.OPEN
2 Connection going through closing handshake.
WebSocket.CLOSING
3 Connection closed and cannot be opened.
WebSocket.CLOSED

17-3. Building a JSON Object

Problem

You would like to build a JSON object that can be passed from a client to a server, or vice versa.

Solution

Make use of the JsonObjectBuilder to build a JSON object using Java code. The following example demonstrates how to utilize a JsonObjectBuilder() instance to create a new JsonObject. In this example class, multiple JsonObjects are created from reading the contents of a database table. Once the object is built, the sections of the object assigned to a String that will eventually be displayed or persisted.

import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonWriter;
import org.javaeerecipes.jpa.entity.BookAuthor;
import org.javaeerecipes.jpa.session.BookAuthorFacade;

@ManagedBean(name = "jsonController")
public class JsonController {

    @EJB
    BookAuthorFacade bookAuthorFacade;
    private String authorJson;

    public void buildAuthors() {
        List<BookAuthor> authors = bookAuthorFacade.findAll();
        JsonObjectBuilder builder = Json.createObjectBuilder();
        StringBuilder json = new StringBuilder();
        try (StringWriter sw = new StringWriter();) {
            for (BookAuthor author : authors) {
                System.out.println("author" + author.getLast());
                builder.add("author", Json.createObjectBuilder()
                       .add("authorId", author.getId())
                       .add("first", author.getFirst())
                       .add("last", author.getLast())
                       .add("bio", author.getBio()));

            }
            JsonObject result = builder.build();

            try (JsonWriter writer = Json.createWriter(sw)) {
                writer.writeObject(result);
            }
            json.append(sw.toString());
            authorJson = json.toString();
        } catch (IOException ex) {
            System.out.println(ex);
        }
    }
...

Once created, the JsonObject can be passed to a client for processing, or in this case, it can be persisted to disk.

How It Works

The JavaScript Object Notation (JSON-P) API was added to the Java Enterprise platform with the release of Java EE 7. JSON-P, also referred to as “JSON with padding,” has become the standard way to build JSON objects using Java. The JSON-P API includes a helper class that can be used to create JSON objects using the builder pattern. Using the JsonObjectBuilder class, JSON objects can be built using a series of method calls, each building upon each other—hence, the builder pattern. Once the JSON object has been built, the JsonObjectBuilder build method can be called to return a JsonObject.

In the example to this recipe, you construct a JSON object that provides details regarding book authors. The JsonObjectBuilder.beginObject() method is used to denote that a new object is being created. The add method is used to add more a name/value properties, much like that of a Map. Therefore, the following line adds a property named authorId with a value of author.getId():

.add("authorId", author.getId())

Objects can be embedded inside of each other, creating a hierarchy of different sections within one JsonObject. In the example, after the first call to add(), another object named author is embedded inside the initial JsonObject by calling beginObject(), and passing the name of the embedded object. Embedded objects can also contain properties; so to add properties to the embedded object, call the add() method within the embedded object. JsonObjects can embody as many embedded objects as needed. The following lines of code demonstrate the beginning and end of an embedded object definition:

.beginObject("author")
.add("first", "Josh")
.add("last", "Juneau")
.endObject()

It is also possible that a JsonObject may have an array of related subobjects. To add an array of subobjects, call the beginArray() method, passing the name of the array as an argument. Arrays can consist of objects, and even hierarchies of objects, arrays, and so forth. Once a JsonObject has been created, it can be passed to a client. WebSockets work well for passing JsonObjects back to a client, but there are a bevy of different technologies available for communicating with JSON.

17-4. Writing a JSON Object to Disk

Problem

You would like to write a JSON object to the file system.

Solution

Utilize the JSON-P API to build a JSON object, and then store it to the file system. The JsonWriter class makes it possible to create a file on disk, and then write the JSON to that file. In the following example, the JsonObject that was generated in Recipe 17-3 is written to disk using this technique.

public void writeJson() {
        try {
            JsonObject jsonObject = jsonController.buildAuthorsJson();

            javax.json.JsonWriter jsonWriter = Json.createWriter(new FileWriter("Authors.json"));
            
            jsonWriter.writeObject(jsonObject);
            jsonWriter.close();

            FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(
                FacesMessage.SEVERITY_INFO, "JSON Built",
                "JSON Built"));
        } catch (IOException ex) {
            System.out.println(ex);
        }
    }

How It Works

The JsonWriter class can be utilized to write a JsonObject to a Java writer object. A JsonWriter is instantiated by passing a Writer object as an argument. Instantiating a JsonWriter will write to the Writer object that had been passed as an argument, using JSON format. After that Writer has been created, the JsonWriter writeObject() method can be invoked, passing the JsonObject that is to be written. Once the JsonObject has been written, the JsonWriter can be closed by calling its close() method. These are the only steps that are necessary for writing a JSON object to a Java Writer class type.

17-5. Reading JSON from an Input Source

Problem

You would like read a JSON object that has been built, or persisted to a file.

Solution

Obtain a JSON object that you would like to read, and then read it using the javax.json.Json createReader utility. In the following example, a JSON file is read from disk, and then parsed to determine the hierarchy of events within. Each of the events is printed to the server log as the JSON is being parsed.

public String readObject() {
        InputStream in = new ByteArrayInputStream(controller.buildAndReturnAuthors().getBytes());
         // or
        //Reader fileReader = new InputStreamReader(getClass().getResourceAsStream("AuthorObject.json"));
        //JsonReader reader = Json.createReader(fileReader);
        JsonReader reader = Json.createReader(in);
        JsonObject obj = reader.readObject();
        return obj.toString();
              
    }

How It Works

Once a JSON object has been persisted to disk, it will later need to be read back in for utilization. The JsonReader object takes care of this task. To create a JsonReader object, call the Json.createReader() method, passing either an InputStream or Reader object. Once a JsonReader object has been created, it can produce a JsonObject by calling its readObject method.

Parsing Content

In order to perform some tasks, a JSON object must be searched to find only the content that is desired and useful for the current task. Utilizing a JSON parser can make jobs such as these easier, as a parser is able to break the object down into pieces so that each different piece can be examined as needed, to produce the desired result.

The javax.json.Json class contains a static factory method, createParser(), that accepts a bevy of input and returns an iterable JsonParser. Table 17-3 lists the different possible input types that are accepted via the createParser() method.

Table 17-3. createParser Method Input Types

Input Type Method Call
InputStream createParser(InputStream in)
JsonArray createParser(JsonArray arr)
JsonObject createParser(JsonObject obj)
Reader createParser(Reader reader)

Once a JsonParser has been created, it can be made into an Iterator of Event objects. Each Event correlates to a different structure within the JSON object. For instance, when the JSON object is created, a START_OBJECT event occurs, adding a namevalue pair will trigger both a KEY_NAME and VALUE_STRING event. These events can be utilized to obtain the desired information from a JSON object. In the example, the event names are merely printed to a server log. However, in a real-life application, a conditional would most likely test each iteration to find a particular event and then perform some processing. Table 17-4 lists the different JSON events, along a description of when each occurs.

Table 17-4. JSON Object Events

Event Occurrence
START_OBJECT Start of an object.
END_OBJECT End of an object.
START_ARRAY Start of an array.
END_ARRAY End of an array.
KEY_NAME Name of a key.
VALUE_STRING Value of a namevalue pair in String format.
VALUE_NUMBER Value of a namevalue pair in numeric format.
VALUE_TRUE Value of a namevalue pair in Boolean format.
VALUE_FALSE Value of a namevalue pair in Boolean format.
VALUE_NULL Value of a namevalue pair as NULL.
..................Content has been hidden....................

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