Before my holidays I started this short series about WebSockets and announced more articles during February. The day I started working on this matter, my godson died. So I did (and will do) other things than writing.

I broke down my next article into two smaller pieces. One of them is the following writing. The other will be delayed. I assume your understanding.

This article describes the smallest possible chat I could write with JSF and websockets so far. JSF is only used to handle some elements within the browser. Within such a small application this might be handled with pure HTML/Javascript. But remember, this series is about JSF and websockets.

This article offers a step by step tutorial with NetBeans. If the IDE of your choice is a different one, it should be possible for you to transfer the steps. Or give NetBeans a try. You need to install the NetBeans Java EE (or the all) bundle.

From the NetBeans menu, choose File, New Project (or press Shift+Ctrl+N) to open the New Project window. Choose Maven and Web Application and click onto Next.

In the next screen provide a project name (SimpleChat) and within the last screen choose GlassFish (or Payara if installed) as application server. Then finish the wizard.

Once NetBeans created the project for you, right click the project and open the properties dialog. Add the JavaServer faces framework.

Click onto the Configuration tab and provide *.xhtml as URL pattern. Close the properties dialog.

Open the POM and add this dependenvy:

    <dependency>
      <groupId>org.glassfish.tyrus</groupId>
      <artifactId>tyrus-server</artifactId>
      <version>1.13</version>
    </dependency>

We will create another version without this dependency on Tyrus server in the next article. Using Tyrus, the endpoint will be the simplest.

For those, who don’t use NetBeans, here is the complete POM created and edited so far:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>de.muellerbruehl</groupId>
  <artifactId>SimpleChat</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>SimpleChat</name>

  <properties>
    <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
    
  <dependencies>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-web-api</artifactId>
      <version>7.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.glassfish.tyrus</groupId>
      <artifactId>tyrus-server</artifactId>
      <version>1.13</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.7</source>
          <target>1.7</target>
          <compilerArguments>
            <endorseddirs>${endorsed.dir}</endorseddirs>
          </compilerArguments>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.3</version>
        <configuration>
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>2.6</version>
        <executions>
          <execution>
            <phase>validate</phase>
            <goals>
              <goal>copy</goal>
            </goals>
            <configuration>
              <outputDirectory>${endorsed.dir}</outputDirectory>
              <silent>true</silent>
              <artifactItems>
                <artifactItem>
                  <groupId>javax</groupId>
                  <artifactId>javaee-endorsed-api</artifactId>
                  <version>7.0</version>
                  <type>jar</type>
                </artifactItem>
              </artifactItems>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

Next create a server endpoint as described in the first part of this series [1]. As name choose SimpleChat and as package name as well as endpoint URI use simplechat.

Modify the onMessage method

  @OnMessage
  public void onMessage(String message, Session session) {
    ((TyrusSession) session).broadcast(message);
  }

and fix the imports (Ctrl+Shift+I).

This method is called every time our endpoint receives a message. All it does, is to broadcast this message to all clients who have opened a websocket connection to the endpoint. Although it seems to be one broadcast, under the hood the server handles individual connections. Within the next part, we will do it by ourselves.

Next, modify the index.xhtlm page as shown below:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
  <h:head>
    <title>Chat</title>
    <h:outputScript name="simpleChat.js"/>
  </h:head>
  <h:body>

    <h1>Simple chat</h1>
    <h:form prependId="false">

      <div>
        Enter message: 
        <h:inputTextarea style="height: 1em;"  
            onkeypress="if (event.keyCode === 13) {
              acceptValue(this);
            }"
        />
      </div>
      <h:inputTextarea id="messages" 
         style="width: 100%; 
         min-height: 10em;"/>
    </h:form>
  </h:body>
</html>

This page defines two text areas. On is just a line height and is used for input. I’ve chosen a text area rather then a simple test field to stay within this field on hitting the enter key. As you can read, something happens on pressing that key. The other field is used for the common output. The glue code we need is provided within a JavaScript file simpleChat.js.

Within the projects web pages folder, create a new folder resources, and within that folder the file simpleChat.js. JSF will load this file according to the outputScript tag we have in our page. If you want to learn more about JSF resource loading, you may read my book Web Development with Java and JSF. Add this content to the script file:

var websocket;

window.onload = function () {
  invokeConnection();
}

function invokeConnection() {
  websocket = new WebSocket(obtainUri());
  websocket.onerror = function (evt) {
    onError(evt)
  };
  websocket.onmessage = function (evt) {
    onMessage(evt)
  };
  return true;
}

function obtainUri() {
  return "ws://" + document.location.host + "/SimpleChat/simplechat";
}

function onError(evt) {
  writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}


function onMessage(evt) {
  element = document.getElementById("messages");
  if (element.value.length === 0) {
    element.value = evt.data;
  } else {
    oldTexts = element.value.split("\n").slice(-19);
    element.value = oldTexts.join("\n") + evt.data;
    element.scrollTop = element.scrollHeight;
  }
  return;
}

function acceptValue(element) {
  websocket.send(element.value);
  element.value = "";
  return true;
}

Our simple chat is ready now. Compile and start the application. Enter some text and press enter: it appears in the output area. Open this app within a second browser and enter some text: it will appear in the output field within all browsers.

Let’s explain it:

When the page is loaded, invokeConnection is called. This method establishes a websocket communication channel to the server and registers two methods. onMessage is called for regular messages, whilst onError handles errors. Every time the client receives a message from the server (as send by the broadcast), it gets the text area we use for the output and appends the message. If a certain maximum is reached, it composes the message of the last few ones.

The method acceptValue is called every time the user hits the enter key within the input field. The message is send to the server and the input field is cleared to accept a fresh value.

Enjoy!

 

[1] blog.mueller-bruehl.de/jsf/using-websockets-with-java-ee-part-i