@Named
public class BotBean {
public String respond(String msg) { ... }
}
Java Platform, Enterprise Edition (Java EE) 8 The Java EE Tutorial |
Previous | Next | Contents |
The websocketbot
example application, located in the
tut-install`/examples/web/websocket/websocketbot/` directory,
demonstrates how to use a WebSocket endpoint to implement a chat. The
example resembles a chat room in which many users can join and have a
conversation. Users can ask simple questions to a bot agent that is
always available in the chat room.
The following topics are addressed here:
The websocketbot
example application consists of the following
elements:
The CDI Bean – A CDI bean (BotBean
) that contains
the logic for the bot agent to reply to messages
The WebSocket Endpoint – A WebSocket endpoint
(BotEndpoint
) that implements the chat room
The Application Messages – A set of classes
(Message
, ChatMessage
, InfoMessage
, JoinMessage
, and
UsersMessage
) that represent application messages
The Encoder Classes – A set of classes
(ChatMessageEncoder
, InfoMessageEncoder
, JoinMessageEncoder
, and
UsersMessageEncoder
) that encode application messages into WebSocket
text messages as JSON data
The Message Decoder – A class (MessageDecoder
) the
parses WebSocket text messages as JSON data and decodes them into
JoinMessage
or ChatMessage
objects
The HTML Page – An HTML page
(index.html
) that uses JavaScript code to implement the client for the
chat room
The CDI bean (BotBean
) is a Java class that contains the respond
method. This method compares the incoming chat message with a set of
predefined questions and returns a chat response.
@Named
public class BotBean {
public String respond(String msg) { ... }
}
The WebSocket endpoint (BotEndpoint
) is an annotated endpoint that
performs the following functions:
Receives messages from clients
Forwards messages to clients
Maintains a list of connected clients
Invokes the bot agent functionality
The endpoint specifies its deployment URI and the message encoders and
decoders using the ServerEndpoint
annotation. The endpoint obtains an
instance of the BotBean
class and a managed executor service resource
through dependency injection:
@ServerEndpoint(
value = "/websocketbot",
decoders = { MessageDecoder.class },
encoders = { JoinMessageEncoder.class, ChatMessageEncoder.class,
InfoMessageEncoder.class, UsersMessageEncoder.class }
)
/* There is a BotEndpoint instance per connection */
public class BotEndpoint {
private static final Logger logger = Logger.getLogger("BotEndpoint");
/* Bot functionality bean */
@Inject private BotBean botbean;
/* Executor service for asynchronous processing */
@Resource(name="comp/DefaultManagedExecutorService")
private ManagedExecutorService mes;
@OnOpen
public void openConnection(Session session) {
logger.log(Level.INFO, "Connection opened.");
}
...
}
The message
method processes incoming messages from clients. The
decoder converts incoming text messages into JoinMessage
or
ChatMessage
objects, which inherit from the Message
class. The
message
method receives a Message
object as a parameter:
@OnMessage
public void message(Session session, Message msg) {
logger.log(Level.INFO, "Received: {0}", msg.toString());
if (msg instanceof JoinMessage) {
/* Add the new user and notify everybody */
JoinMessage jmsg = (JoinMessage) msg;
session.getUserProperties().put("name", jmsg.getName());
session.getUserProperties().put("active", true);
logger.log(Level.INFO, "Received: {0}", jmsg.toString());
sendAll(session, new InfoMessage(jmsg.getName() +
" has joined the chat"));
sendAll(session, new ChatMessage("Duke", jmsg.getName(),
"Hi there!!"));
sendAll(session, new UsersMessage(this.getUserList(session)));
} else if (msg instanceof ChatMessage) {
/* Forward the message to everybody */
ChatMessage cmsg = (ChatMessage) msg;
logger.log(Level.INFO, "Received: {0}", cmsg.toString());
sendAll(session, cmsg);
if (cmsg.getTarget().compareTo("Duke") == 0) {
/* The bot replies to the message */
mes.submit(new Runnable() {
@Override
public void run() {
String resp = botbean.respond(cmsg.getMessage());
sendAll(session, new ChatMessage("Duke",
cmsg.getName(), resp));
}
});
}
}
}
If the message is a join message, the endpoint adds the new user to the list and notifies all connected clients. If the message is a chat message, the endpoint forwards it to all connected clients.
If a chat message is for the bot agent, the endpoint obtains a response
using the BotBean
instance and sends it to all connected clients. The
sendAll
method is similar to the example in
Sending Messages to All Peers Connected
to an Endpoint.
Asynchronous Processing and Concurrency Considerations
The WebSocket endpoint calls the BotBean.respond
method to obtain a
response from the bot. In this example, this is a blocking operation;
the user that sent the associated message would not be able to send or
receive other chat messages until the operation completes. To avoid this
problem, the endpoint obtains an executor service from the container and
executes the blocking operation in a different thread using the
ManagedExecutorService.submit
method from Concurrency Utilities for
Java EE.
The Java API for WebSocket specification requires that Java EE
implementations instantiate endpoint classes once per connection. This
facilitates the development of WebSocket endpoints, because you are
guaranteed that only one thread is executing the code in a WebSocket
endpoint class at any given time. When you introduce a new thread in an
endpoint, as in this example, you must ensure that variables and methods
accessed by more than one thread are thread safe. In this example, the
code in BotBean
is thread safe, and the BotEndpoint.sendAll
method
has been declared synchronized
.
Refer to Chapter 59, "Concurrency Utilities for Java EE" for more information on the managed executor service and Concurrency Utilities for Java EE.
The classes that represent application messages (Message
,
ChatMessage
, InfoMessage
, JoinMessage
, and UsersMessage
) contain
only properties and getter and setter methods. For example, the
ChatMessage
class looks like this:
public class ChatMessage extends Message {
private String name;
private String target;
private String message;
/* ... Constructor, getters, and setters ... */
}
The encoder classes convert application message objects into JSON text
using the Java API for JSON Processing. For example, the
ChatMessageEncoder
class is implemented as follows:
/* Encode a ChatMessage as JSON.
* For example, (new ChatMessage("Peter","Duke","How are you?"))
* is encoded as follows:
* {"type":"chat","target":"Duke","message":"How are you?"}
*/
public class ChatMessageEncoder implements Encoder.Text<ChatMessage> {
@Override
public void init(EndpointConfig ec) { }
@Override
public void destroy() { }
@Override
public String encode(ChatMessage chatMessage) throws EncodeException {
// Access properties in chatMessage and write JSON text...
}
}
See Chapter 20, JSON Processing for more information on the Java API for JSON Processing.
The message decoder (MessageDecoder
) class converts WebSocket text
messages into application messages by parsing JSON text. It is
implemented as follows:
/* Decode a JSON message into a JoinMessage or a ChatMessage.
* For example, the incoming message
* {"type":"chat","name":"Peter","target":"Duke","message":"How are you?"}
* is decoded as (new ChatMessage("Peter", "Duke", "How are you?"))
*/
public class MessageDecoder implements Decoder.Text<Message> {
/* Stores the name-value pairs from a JSON message as a Map */
private Map<String,String> messageMap;
@Override
public void init(EndpointConfig ec) { }
@Override
public void destroy() { }
/* Create a new Message object if the message can be decoded */
@Override
public Message decode(String string) throws DecodeException {
Message msg = null;
if (willDecode(string)) {
switch (messageMap.get("type")) {
case "join":
msg = new JoinMessage(messageMap.get("name"));
break;
case "chat":
msg = new ChatMessage(messageMap.get("name"),
messageMap.get("target"),
messageMap.get("message"));
}
} else {
throw new DecodeException(string, "[Message] Can't decode.");
}
return msg;
}
/* Decode a JSON message into a Map and check if it contains
* all the required fields according to its type. */
@Override
public boolean willDecode(String string) {
// Convert JSON data from the message into a name-value map...
// Check if the message has all the fields for its message type...
}
}
The HTML page (index.html
) contains a field for the user name. After
the user types a name and clicks Join, three text areas are available:
one to type and send messages, one for the chat room, and one with the
list of users. The page also contains a WebSocket console that shows the
messages sent and received as JSON text.
The JavaScript code on the page uses the WebSocket API to connect to the endpoint, send messages, and designate callback methods. The WebSocket API is supported by most modern browsers and is widely used for web client development with HTML5.
This section describes how to run the websocketbot
example application
using NetBeans IDE and from the command line.
The following topics are addressed here:
Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).
From the File menu, choose Open Project.
In the Open Project dialog box, navigate to:
tut-install/examples/web/websocket
Select the websocketbot
folder.
Click Open Project.
In the Projects tab, right-click the websocketbot
project and
select Run.
This command builds and packages the application into a WAR file,
websocketbot.war
, located in the target/
directory; deploys it to
the server; and launches a web browser window with the following URL:
http://localhost:8080/websocketbot/
See To Test the websocketbot Example Application for more information.
Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).
In a terminal window, go to:
tut-install/examples/web/websocket/websocketbot/
Enter the following command to deploy the application:
mvn install
Open a web browser window and type the following address:
http://localhost:8080/websocketbot/
See To Test the websocketbot Example Application for more information.
On the main page, type your name on the first text field and press the Enter key.
The list of connected users appears on the text area on the right. The text area on the left is the chat room.
Type a message on the text area below the login button. For example, type the messages in bold and press enter to obtain responses similar to the following:
[--Peter has joined the chat--]
Duke: @Peter Hi there!!
Peter: @Duke how are you?
Duke: @Peter I'm doing great, thank you!
Peter: @Duke when is your birthday?
Duke: @Peter My birthday is on May 23rd. Thanks for asking!
Join the chat from another browser window by copying and pasting the URI on the address bar and joining with a different name.
The new user name appears in the list of users in both browser windows. You can send messages from either window and see how they appear in the other.
Click Show WebSocket Console.
The console shows the messages sent and received as JSON text.
Previous | Next | Contents |