The need for realtime chat can’t be overemphasized. This includes realtime communication with your users which increases customer satisfaction and, as a result, make your business more credible, convenient and reduces wait times etc.
Have you ever wondered how you could add a realtime chat to your Struts 2 web application? Have you considered the number of plugins or libraries that you might need to pull in to make it work? Worry no more, Pusher got your back. In this article, I'll work you through how to build a realtime chat app in Java Struts 2 by leveraging Pusher realtime technology.
At the end of this tutorial, we’ll have an application similar to this:
Struts 2 is an excellent MVC Web application framework for developing enterprise Java web applications. It enables rapid development of Web applications and handles most of the plumbing required in large Web applications.
Pusher Channels is a hosted service that makes it super-easy to add realtime data and functionality to web and mobile applications.
PRO TIP: Pusher Channels sits as a realtime layer between your servers and your clients. It maintains persistent connections to the clients over WebSocket if possible and falling back to HTTP-based connectivity. As soon as your servers have new data that they want to push to the clients they can do, instantly via Pusher Channels.
The following tools are used in this article:
Sign up for a free Pusher account account and create a new Channels app.
Note down your app details you just created:
1app_id = "*********" 2 key = "***********************" 3 secret = "*********************" 4 cluster = "**"
A Struts 2 application is an ordinary Java Web application with a set of additional libraries.
Open your Eclipse IDE then go to File > New > Others from the menu. You should get a prompt just like the image below:
Now, Select Maven > Maven Project then click Next.
You should have another prompt window:
Now, select your project location, this is where you want your project's files to be stored. After that click on Next to proceed. In my case, I used the default location by just clicking next.
You will have another prompt to select an Archetype:
Select org.apache.maven.achetypes maven-achetype-webapp 1.0 then click on Next.
On this window, put in the **Group Id**
and **Artifact**
**Id**
then click on Finish.
💡 groupId will identify your project uniquely across all projects, so we need to enforce a naming schema. It has to follow the package name rules and you can create as many subgroups as you want. Look at More information about package names. eg. org.apache.maven, org.apache.commons
💡 artifactId is the name of the jar without version. If you created it then you can choose whatever name you want. If it's a third party jar you have to take the name of the jar as it's distributed. eg. maven, commons-math
Once done, a new project will be created for you:
Since we’ll use maven to run the application, we need to add jetty-maven-plugin
to the pom.``xml
file.
Update pom.xml with the following jetty plugin:
1<build> 2 ... 3 <plugins> 4 <plugin> 5 <groupId>org.eclipse.jetty</groupId> 6 <artifactId>jetty-maven-plugin</artifactId> 7 <version>9.4.7.v20170914</version> 8 <configuration> 9 <webApp> 10 <contextPath>/${build.finalName}</contextPath> 11 </webApp> 12 <stopKey>CTRL+C</stopKey> 13 <stopPort>8999</stopPort> 14 <scanIntervalSeconds>10</scanIntervalSeconds> 15 <scanTargets> 16 <scanTarget>src/main/webapp/WEB-INF/web.xml</scanTarget> 17 </scanTargets> 18 </configuration> 19 </plugin> 20 </plugins> 21 </build>
Now, from your Eclipse IDE, right click on the project name - **chatApp**
- or any name you have chosen. Then go to **Run As**
>> **Maven build**
.
Now type in jetty:run
in the goals then click **Apply**
and then click on **Run**
.
Visit http://localhost:8080/chatApp from your browser:
Note that chatApp
is the folder name of your project. If you have used a different name, you should change the URL accordingly.
Next, We’ll add Struts 2 to the Classpath. Now that we know we have a working Java web application, let’s add the minimal required Struts 2 framework Jar files to our web application’s class path. In pom.xml
add the following to the dependency node:
1<dependency> 2 <groupId>org.apache.struts</groupId> 3 <artifactId>struts2-core</artifactId> 4 <version>2.5.14</version> 5 </dependency>
Struts 2 libraries Jar files will be downloaded and added to our project when you save.
Next, add the Struts 2 plugin that will enable us to work with JSON. Add the following **pom.xml**
dependency node:
1<dependency> 2 <groupId>org.apache.struts</groupId> 3 <artifactId>struts2-json-plugin</artifactId> 4 <version>2.5</version> 5 </dependency>
To see what’s happening under the hood, like when errors occur which will help during debugging, let’s add a logging dependency to our application.
Add the following dependencies to **pom.xml**
dependency node:
1<dependency> 2 <groupId>org.apache.logging.log4j</groupId> 3 <artifactId>log4j-core</artifactId> 4 <version>2.8.2</version> 5 </dependency> 6 <dependency> 7 <groupId>org.apache.logging.log4j</groupId> 8 <artifactId>log4j-api</artifactId> 9 <version>2.8.2</version> 10 </dependency>
Next, setup a log4j2.xml
configuration in the src/main/resources
folder which contains the following:
1<?xml version="1.0" encoding="UTF-8"?> 2 <Configuration> 3 <Appenders> 4 <Console name="STDOUT" target="SYSTEM_OUT"> 5 <PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/> 6 </Console> 7 </Appenders> 8 <Loggers> 9 <Logger name="com.opensymphony.xwork2" level="debug"/> 10 <Logger name="org.apache.struts2" level="debug"/> 11 <Root level="warn"> 12 <AppenderRef ref="STDOUT"/> 13 </Root> 14 </Loggers> 15 </Configuration>
Pusher has a Java library that we can use to interact with it’s API. We’ll add this to our application.
Update pom.xml dependency node with the below:
1<dependency> 2 <groupId>com.pusher</groupId> 3 <artifactId>pusher-http-java</artifactId> 4 <version>1.0.0</version> 5 </dependency>
This will download and add pusher java libraries to our application.
To enable the Struts 2 framework to work with our web application we need to add a Servlet filter class and filter mapping to web.xml
. Below is the filter and filter-mapping nodes you should add.
Add the following to **webapp**
node in src/main/webapp/WEB-INF/web.xml
file:
1<filter> 2 <filter-name>struts2</filter-name> 3 <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class> 4 </filter> 5 6 <filter-mapping> 7 <filter-name>struts2</filter-name> 8 <url-pattern>/*</url-pattern> 9 </filter-mapping>
You can see this as the router for our application. Struts 2 can use either an XML configuration file or annotations (or both) to specify the relationship between a URL, a Java class, and a view page (such as index.jsp
). For our basic Struts 2 application, we’ll use a minimal XML configuration.
Create a new file as struts.xml
in the src/main/resources
folder and add the following code to it:
1<?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE struts PUBLIC 3 "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN" 4 "http://struts.apache.org/dtds/struts-2.5.dtd"> 5 6 <struts> 7 8 <constant name="struts.devMode" value="true" /> 9 10 <package name="default" namespace="/" extends="json-default"> 11 <default-action-ref name="index"/> 12 13 <action name="index"> 14 <result>/index.jsp</result> 15 </action> 16 </package> 17 18 </struts>
With the above, we now have a route of http://localhost:8080/chatApp/index.action
available in our application:
1<action name="index"> 2 <result>/index.jsp</result> 3 </action>
Open src/main/webapp/index.jsp
and add the following code to it:
1<!DOCTYPE html> 2 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> 3 <head> 4 <title>Welcome To Struts 2 chat!</title> 5 <meta charset="utf-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> 8 <link rel="stylesheet" href="assets/custom.css"> 9 <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script> 10 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> 11 <script src="https://js.pusher.com/4.1/pusher.min.js"></script> 12 13 </head> 14 <body> 15 <h1 class="text-center">Welcome To Struts 2 chat!</h1> 16 <div class="container" style="border: 2px solid gray;"> 17 <!--msgbox--> 18 <div id="msgItems" class="container-fluid"> 19 20 </div> 21 22 <!-- querybox--> 23 <div class="row text-center" id="queryText"> 24 25 <div class="hideForm"> 26 <div class="row"> 27 <div class="col-xs-9"> 28 <input type="text" class="form-control" placeholder="Type your Message Here" id="message"> 29 </div> 30 <div class="col-xs-3"> 31 <button type="button" class="btn btn-primary" id="submitMessage">Send Message</button> 32 </div> 33 </div> 34 </div> 35 36 <div id="chatName"> 37 <form class="form-inline"> 38 <div class="form-group"> 39 <input type="text" class="form-control" id="userName" placeholder="your username"> 40 </div> 41 <button type="button" class="btn btn-primary" id="startChating">Start Chating!</button> 42 </form> 43 </div> 44 45 </div> 46 47 </div> 48 49 <script src="assets/custom.js"></script> 50 </body> 51 52 </html>
Next create new files called src/main/webapp/assets/custom.css
and src/main/webapp/assets/custom.js
. Note that the assets folder is not created by default, we need to create it.
In the src/main/webapp/assets/``custom.css
file, add the following code:
1body { 2 padding-top: 50px; 3 } 4 5 #queryText { 6 position : relative; 7 bottom : 4%; 8 padding: 0.3%; 9 background : grey; 10 min-width : 200px; 11 } 12 13 #queryText input { 14 width : 100%; 15 } 16 17 #queryText form div { 18 margin-left: auto; 19 margin-right: auto; 20 } 21 22 #queryText { 23 border : 0px solid black; 24 padding: 10px; 25 } 26 27 #chat-item { 28 border-bottom : 1px solid grey; 29 } 30 31 #msgItems div img { 32 background : blue; 33 display : inline; 34 } 35 36 #msgItems { 37 height: 400px; 38 overflow: scroll; 39 } 40 .hideForm{ 41 display: none; 42 } 43 .input-large { 44 padding: 5px 150px; 45 }
In the src/main/webapp/assets/``custom.js
file, add the following code:
1// Indentify every user uniquely 2 var uniqueId = Math.random().toString(36).substring(2) + (new Date()).getTime().toString(36); 3 4 $("#startChating").click(function() { 5 6 if( $("#userName").val() ) { // if user provides username 7 $("#chatName").hide(); 8 $(".hideForm").show(); 9 } 10 });
Note that we have included Pusher’s JavaScript Library in index.jsp
which will help us listen to events so we can act on them.
Now, visit the webpage again at https://localhost:8080/chatApp/.
Here, we’ll create an action class that will serve as the server for sending data to Pusher.
Create a new folder called java
in the src/main
folder.
Then create a new file src/main/java/MessageAction.java
and add the following code to it:
1package com.menusms.chatApp.action; 2 3 import com.opensymphony.xwork2.ActionSupport; 4 5 import com.pusher.rest.Pusher; 6 7 import java.util.LinkedHashMap; 8 import java.util.Map; 9 10 public class MessageAction extends ActionSupport{ 11 12 private Map<String, String> data = new LinkedHashMap<String, String>(); 13 14 private String message, userName, uniqueId; 15 16 public String execute() { 17 18 //Pusher pusher = new Pusher("app_id", "key", "secret"); 19 Pusher pusher = new Pusher("******", "****************", "*************"); 20 pusher.setCluster("**"); // update with your pusher cluster 21 pusher.setEncrypted(true); 22 23 data.put("message", this.getMessage()); 24 data.put("userName", this.getUserName()); 25 data.put("uniqueId", this.getUniqueId()); 26 27 pusher.trigger("struts-chat", "message", data); 28 29 return SUCCESS; 30 } 31 32 }
Here, we have declared some variables - message
, userName
, uniqueId
and data
- which will be sent to Pusher. When the execute method is called, the data is sent to Pusher (make sure you change pusher details in the execute method with the details you saved earlier).
With this, we are sending the data to the struts-chat
channel and also triggering the message
event.
💡 Channels provide a great way of organizing streams of real-time data. Here, we are subscribing to the
struts-chat
channel (NB: The channel name can be any name you like). Once we are subscribed to a channel, we bind that channel to an event.
💡 Events can be seen as a notification of something happening on your system and are ideal for linking updates to changes in the View. In this case we want to bind to an event which is triggered whenever a user sends a message.
Next, lets add a setter and getter for the variables we have declared. Update src/main/java/MessageAction.java
with the below:
1... 2 public Map<String, String> getData() { 3 return data; 4 } 5 6 public void setData(Map<String, String> data) { 7 this.data = data; 8 } 9 10 public String getUniqueId() { 11 return uniqueId; 12 } 13 14 public void setUniqueId(String uniqueId) { 15 this.uniqueId = uniqueId; 16 } 17 18 public String getUserName() { 19 return userName; 20 } 21 22 public void setUserName(String userName) { 23 this.userName = userName; 24 } 25 26 public String getMessage() { 27 return message; 28 } 29 30 public void setMessage(String message) { 31 this.message = message; 32 } 33 }
Update src/main/resources/struts.xml
with the below:
1<action name="message" class="com.menusms.chatApp.action.MessageAction" method="execute"> 2 <result type="json"></result> 3 </action>
Now, we have a route available - http://localhost:8080/chatApp/message
. When this URL is visited, the execute method in the class MessageAction.java
will be invoked.
When a user submits a message from the HTML form, we’ll send this data to our Java class where it will be sent to Pusher.
Using jQuery, we’ll send this data to the message.action
route.
Update src/main/webapp/assets/custom.js
with the following code:
1$("#submitMessage").click(function() { 2 3 var userName = $("#userName").val(); 4 var message = $("#message").val(); 5 6 $.post("message.action", { 7 message: message, 8 userName: userName, 9 uniqueId: uniqueId 10 }) 11 .done(function(data) { 12 //empty the message input 13 $("#message").val(""); 14 }); 15 });
We need to listen for incoming messages from Pusher and display them when they are received. We’ll do this easily with the Pusher JavaScript library we have included earlier.
We’ll subscribe to a channel (this is the channel that we are pushing data to in our java code above) and bind that channel to an event.
Add the below code to src/main/webapp/assets/custom.js
:
1var pusher = new Pusher('***************', {// Replace with your PUSHER_APP_KEY 2 cluster: '**', // Replace with your PUSHER_APP_CLUSTER 3 encrypted: true 4 }); 5 6 var channel = pusher.subscribe('struts-chat'); 7 channel.bind('message', function(data) { 8 9 var textDirection = (data.uniqueId == uniqueId) ? " text-right" : ""; 10 11 $("#msgItems").append( 12 `<div id="chat-item" class="row` +textDirection+ `"> 13 <div class="cols-xs-4"> 14 <p> 15 <p><b>` +data.userName+ `</b></p><img src="http://placehold.it/30X30" class="img-circle img-responsive">` 16 +data.message+ ` 17 </p> 18 19 </div> 20 </div>` 21 ); 22 });
With this:
1var channel = pusher.subscribe('struts-chat'); 2 channel.bind('message', function(data) { ...
We have subscribed to the struts-chat
channel and bind it to message
event.
Pusher really makes life easy when it comes to adding realtime features to web applications. In this tutorial, we have been able to learn the basics of how to add chat to a Java Struts 2 application.
If you have any questions or observations, feel free to drop them in the comments section below. I would be happy to respond to you.