Chatroom Application using DWR Java Framework

DWR Java AJAX Applications


October 2008

$10.00

A step-by-step example-packed guide to learning professional application development with Direct Web Remoting

Starting the Project and Configuration

We start by creating a new project for our chat room, with the project name DWRChatRoom. We also need to add the dwr.jar file to the lib directory and enable DWR in the web.xml file. The following is the source code of the dwr.xml file.

<?xml ver sion="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC
"-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN"
"http://getahead.org/dwr/dwr20.dtd">
<dwr>
<allow>
<create creator="new" javascript="Login">
<param name="class" value="chatroom.Login" />
</create>
<create creator="new" javascript="ChatRoomDatabase">
<param name="class" value="chatroom.ChatRoomDatabase" />
</create>
</allow>
</dwr>

The source code for web.xml is as follows:

<?xml ver sion="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.
com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.
sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_
5.xsd" id="WebApp_ID" version="2.5">
<display-name>DWRChatRoom</display-name>
<servlet>
<display-name>DWR Servlet</display-name>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>
org.directwebremoting.servlet.DwrServlet
</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>activeReverseAjaxEnabled</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>

Developing the User Interface

The next step we do is to create files for presentation: style sheet and HTML/JSP files. The style sheet, loginFailed.html, and index.jsp files are required for the application. The source code of the style sheet is as follows:

body{
margin:0;
padding:0;
line-height: 1.5em;
}

b{font-size: 110%;}
em{color: red;}

#topsection{
background: #EAEAEA;
height: 90px; /*Height of top section*/
}

#topsection h1{
margin: 0;
padding-top: 15px;
}

#contentwrapper{
float: left;
width: 100%;
}

#contentcolumn{
margin-left: 200px; /*Set left margin to LeftColumnWidth*/
}

#leftcolumn{
float: left;
width: 200px; /*Width of left column*/
margin-left: -100%;
background: #C8FC98;
}

#footer{
clear: left;
width: 100%;
background: black;
color: #FFF;
text-align: center;
padding: 4px 0;
}

#footer a{
color: #FFFF80;
}

.innertube{
margin: 10px; /*Margins for inner DIV inside each column (to provide padding)*/
margin-top: 0;
}

Our first page is the login page. It is located in the WebContent directory and it is named index.jsp. The source code for the page is given as follows:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Book Authoring</title>

<script type='text/javascript' src='/DWRChatroom/dwr/interface/Login.js'></script>
<script type='text/javascript' src='/DWRChatroom/dwr/engine.js'></script>
<script type='text/javascript' src='/DWRChatroom/dwr/util.js'></script>  

<script type="text/javascript">

function login()
{
  var userNameInput=dwr.util.byId('userName');
  var userName=userNameInput.value;
  Login.doLogin(userName,loginResult);
}

function loginResult(newPage)
{
  window.location.href=newPage;
}

</script>
</head>
<body>
<h1>Book Authoring Sample</h1>
<table cellpadding="0" cellspacing="0">
<tr>
<td>User name:</td>
<td><input id="userName" type="text" size="30"></td>
</tr>
<tr>
<td>&nbsp;</td>
<td><input type="button" value="Login" onclick="login();return false;"></td>
</tr></table>
</body>
</html>

The login screen uses the DWR functionality to process the user login (the Java classes are presented after the web pages). The loginResults function opens either the failure page or the main page based on the result of the login operation.

If the login was unsuccessful, a very simple loginFailed.html page is shown to the user, the source code for which is as follows:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;
                                         charset=ISO-8859-1">
<title>Login failed</title>
</head>
<body>
<h2>Login failed.</h2>
</body>
</html>

The main page, mainpage.jsp, includes all the client-side logic of our ChatRoom application. The source code for the page is as follows:

<!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" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Chatroom</title>
<link href="styles.css" rel="stylesheet" type="text/css" />
<%
   if (session.getAttribute("username") == null
         || session.getAttribute("username").equals("")) {
      //if not logged in and trying to access this page
      //do nothing, browser shows empty page
      return;
   }
%>
<script type='text/javascript' src='/DWRChatRoom/dwr/interface/Login.js'></script>
<script type='text/javascript' src='/DWRChatRoom/dwr/interface/ChatRoomDatabase.js'></script>
<script type='text/javascript' src='/DWRChatRoom/dwr/engine.js'></script>
<script type='text/javascript' src='/DWRChatRoom/dwr/util.js'></script>  

<script type="text/javascript">
dwr.engine.setActiveReverseAjax(true);

function logout()
{
  Login.doLogout(showLoginScreen);
}

function showLoginScreen()
{
  window.location.href='index.jsp';
}

function showUsersOnline()
{
  var cellFuncs = [
          function(user) {
 
            return '<i>'+user+'</i>';
          }
          ];
    Login.getUsersOnline({
    callback:function(users)
    {
      dwr.util.removeAllRows('usersOnline');
      dwr.util.addRows( "usersOnline",users, cellFuncs,
                                    { escapeHtml:false });
    }
    });
}

function getPreviousMessages()
{
    ChatRoomDatabase.getChatContent({
    callback:function(messages)
    {
      var chatArea=dwr.util.byId('chatArea');
      var html="";
      for(index in messages)
      {
         var msg=messages[index];
         html+=msg;
      }
      chatArea.innerHTML=html;
      var chatAreaHeight = chatArea.scrollHeight;
      chatArea.scrollTop = chatAreaHeight;
    }
    });

}

function newMessage(message)
{
  var chatArea=dwr.util.byId('chatArea');
  var oldMessages=chatArea.innerHTML;
  chatArea.innerHTML=oldMessages+message;  
  var chatAreaHeight = chatArea.scrollHeight;
  chatArea.scrollTop = chatAreaHeight;
}

function sendMessageIfEnter(event)
{
  if(event.keyCode == 13)
  {
    sendMessage();
  }
}

function sendMessage()
{
    var message=dwr.util.byId('messageText');
    var messageText=message.value;
    ChatRoomDatabase.postMessage(messageText);
    message.value='';
}
</script>
</head>
<body onload="showUsersOnline();">
<div id="maincontainer">

<div id="topsection">
<div class="innertube">
<h1>Chatroom</h1>
<h4>Welcome <i><%=(String) session.getAttribute("username")%></i></h4>
</div>
</div>

<div id="contentwrapper">
<div id="contentcolumn">
<div id="chatArea" style="width: 600px; height: 300px; overflow: auto">
</div>
<div id="inputArea">
<h4>Send message</h4>
<input id="messageText" type="text" size="50"
  onkeyup="sendMessageIfEnter(event);"><input type="button" value="Send msg"
                                                      onclick="sendMessage();">
</div>
</div>
</div>

<div id="leftcolumn">
<div class="innertube">

<table cellpadding="0" cellspacing="0">
  <thead>
    <tr>
      <td><b>Users online</b></td>
    </tr>
  </thead>
  <tbody id="usersOnline">
  </tbody>
</table>

<input id="logoutButton" type="button" value="Logout"
  onclick="logout();return false;"></div>

</div>

<div id="footer">Stylesheet by <a
  href="http://www.dynamicdrive.com/style/">Dynamic Drive CSS
Library</a></div>
</div>
<script type="text/javascript">
getPreviousMessages();
</script>

</body>
</html>

The first chat-room-specific JavaScript function is getPreviousMessages(). This function is called at the end of mainpage.jsp, and it retrieves previous chat messages for this chat room.

The newMessage() function is called by the server-side Java code when a new message is posted to the chat room. The function also scrolls the chat area automatically to show the latest message.

The sendMessageIfEnter() and sendMessage() functions are used to send user messages to the server. There is the input field for the message text in the HTML code, and the sendMessageIfEnter() function listens to onkeyup events in the input field. If the user presses enter, the sendMessage() function is called to send the message to the server.

The HTML code includes the chat area of specified size and with automatic scrolling.

Developing the Java Code

There are several Java classes in the application.

The Login class handles the user login and logout and also keeps track of the logged-in users. The source code of the Login class is as follows:

package chatroom;

import java.util.Collection;
import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.directwebremoting.ScriptSession;
import org.directwebremoting.ServerContext;
import org.directwebremoting.ServerContextFactory;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.proxy.ScriptProxy;

public class Login {

   public Login() {
   }
   
   public String doLogin(String userName) {
      UserDatabase userDb=UserDatabase.getInstance();
      if(!userDb.isUserLogged(userName)) {
         userDb.login(userName);
         WebContext webContext= WebContextFactory.get();
         HttpServletRequest request = webContext.getHttpServletRequest();
         HttpSession session=request.getSession();
         session.setAttribute("username", userName);
         String scriptId = webContext.getScriptSession().getId();
         session.setAttribute("scriptSessionId", scriptId);
         updateUsersOnline();
         return "mainpage.jsp";
      }
      else {
         return "loginFailed.html";
      }
   }
   
   public void doLogout() {
      try {
         WebContext ctx = WebContextFactory.get();
         HttpServletRequest request = ctx.getHttpServletRequest();
         HttpSession session = request.getSession();
         Util util = new Util();
         String userName = util.getCurrentUserName(session);
         UserDatabase.getInstance().logout(userName);
         session.removeAttribute("username");
         session.removeAttribute("scriptSessionId");
         session.invalidate();
      } catch (Exception e) {
         System.out.println(e.toString());
      }
      updateUsersOnline();
   }
   
   private void updateUsersOnline() {
      WebContext webContext= WebContextFactory.get();
      ServletContext servletContext = webContext.getServletContext();
      ServerContext serverContext = ServerContextFactory.get(servletContext);
      webContext.getScriptSessionsByPage("");
      String contextPath = servletContext.getContextPath();
      if (contextPath != null) {
         Collection<ScriptSession> sessions =
                            serverContext.getScriptSessionsByPage
                                            (contextPath + "/mainpage.jsp");
         ScriptProxy proxy = new ScriptProxy(sessions);
         proxy.addFunctionCall("showUsersOnline");
      }
   }
   
   public List<String> getUsersOnline() {
      UserDatabase userDb=UserDatabase.getInstance();
      return userDb.getLoggedInUsers();
   }
}

The following is the source code of the UserDatabase class

package chatroom;

import java.util.List;
import java.util.Vector;

//this class holds currently logged in users
//there is no persistence
public class UserDatabase {
   
   private static UserDatabase userDatabase=new UserDatabase();
   
   private List<String> loggedInUsers=new Vector<String>();
   
   private UserDatabase() {
}
   
   public static UserDatabase getInstance() {
      return userDatabase;
   }
   
   public List<String> getLoggedInUsers() {
      return loggedInUsers;
   }
   
   public boolean isUserLogged(String userName) {
      return loggedInUsers.contains(userName);
   }
   
   public void login(String userName) {
      loggedInUsers.add(userName);
   }
   
   public void logout(String userName) {
      loggedInUsers.remove(userName);
   }
}

The Util class is used by the Login class, and it provides helper methods for the sample application. The source code for the Util class is as follows:

package chatroom;

import java.util.Hashtable;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;

public class Util {
   
   public Util() {
      
   }
   
   public String getCurrentUserName() {
      //get user name from session
      WebContext ctx = WebContextFactory.get();
      HttpServletRequest request = ctx.getHttpServletRequest();
      HttpSession session=request.getSession();
      return getCurrentUserName(session);
   }

   public String getCurrentUserName(HttpSession session) {
      String userName=(String)session.getAttribute("username");
      return userName;
   }
}

The logic for the server-side chat room functionality is in the ChatRoomDatabase class. The source code for the ChatRoomDatabase is as follows:

package chatroom;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Vector;

import javax.servlet.ServletContext;

import org.directwebremoting.ScriptSession;
import org.directwebremoting.ServerContext;
import org.directwebremoting.ServerContextFactory;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.proxy.ScriptProxy;

public class ChatRoomDatabase {

   private static List<String> chatContent = new Vector<String>();

   public ChatRoomDatabase() {

   }

   public void postMessage(String message) {
      String user = (new Util()).getCurrentUserName();
      if (user != null) {
         Date time = new Date();
         StringBuffer sb = new StringBuffer();
         sb.append(time.toString());
         sb.append(" <b><i>");
         sb.append(user);
         sb.append("</i></b>:  ");
         sb.append(message);
         sb.append("<br/>");
         String newMessage=sb.toString();
         chatContent.add(newMessage);
         postNewMessage(newMessage);
      }
   }

   public List<String> getChatContent() {
      return chatContent;
   }

   private ScriptProxy getScriptProxyForSessions() {
      WebContext webContext = WebContextFactory.get();
      ServletContext servletContext = webContext.getServletContext();
      ServerContext serverContext = ServerContextFactory.get(servletContext);
      webContext.getScriptSessionsByPage("");
      String contextPath = servletContext.getContextPath();
      if (contextPath != null) {
         Collection<ScriptSession> sessions = serverContext
               .getScriptSessionsByPage(contextPath + "/mainpage.jsp");
         ScriptProxy proxy = new ScriptProxy(sessions);
         return proxy;
      }
      return null;
   }

   public void postNewMessage(String newMessage) {
      ScriptProxy proxy = getScriptProxyForSessions();
      if (proxy != null) {
         proxy.addFunctionCall("newMessage",newMessage);
      }
   }
}

The Chatroom code is surprisingly simple. The chat content is stored in a Vector of Strings. The getChatContent()method just returns the chat content Vector to the browser.

The postMessage()method is called when the user sends a new chat message. The method verifies whether the user is logged in, and adds the current time and username to the chat message and then appends the message to the chat content.

The method also calls the postNewMessage() method that is used to show new chat content to all logged-in users. Note that the postMessage() method does not return any value. We let DWR and reverse AJAX functionality show the chat message to all users, including the user who sent the message.

The getScriptProxyForSessions() and postNewMessage() methods use reverse AJAX to update the chat areas of all logged-in users with the new message.

And that is it! The chat room sample is very straightforward and basic functionality is already in place, and the application is ready for further development.

Testing the Chat

We test the chat room application with three users: Smith, Brown, and Jones. We have given some screenshots of a typical scenario in a chat room here.

Both Smith and Brown log into the system and exchange some messages. Both users see empty chat rooms when they log in and start chatting.

Chatroom Application using DWR Java Framework

The empty area that is above the send message input field is reserved for chat content. Smith and Brown exchange some messages as is seen in the following screenshot:

Chatroom Application using DWR Java Framework

The third user, Jones, joins the chat and sees all the previous messages in the chat room. Jones then exchanges some messages with Smith and Brown. Smith and Brown log out from the system leaving Jones alone in the chat room (until she also logs out). This is visible in the following screenshot:

Chatroom Application using DWR Java Framework

Summary

This sample application showed how to use DWR in a chat room application. This application makes it clear that DWR makes development of these kind of collaborative applications very easy.

DWR itself does not even play a big part in the applications. DWR is just a transparent feature of the application. So developers can concentrate on the actual project and aspects such as persistence of data and a neat user interface, instead of the low-level details of AJAX.

 

 

Books to Consider

RESTful Java Web Services
$ 10.00
Java EE 6 Cookbook for Securing, Tuning, and Extending Enterprise Applications
$ 10.00
comments powered by Disqus