Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
EkanshCapgemini
Active Contributor

Update: The sample code for UI5 app and Java Servlet is available here.

https://github.com/ekansh005/sapui5chat

 

Hello Everyone,

To begin with the story, I was going through the OpenSAP course of Build Your Own SAP Fiori App in the Cloud and have to develop an app for the final assignment. I was wondering of the use cases to build the app. I thought of having one Fiori app where we can communicate to other logged in users in real time. In general, Fiori apps communicate to OData service but I could not use the OData service due to the obvious reason of Real Time communication. This lead me towards Websocket communication. I had two options, either to use APC & AMC of Netweaver 7.4 or to develop my own chat server in any other language. Thanks to HCP team for providing the developer trial accounts, I had the option to deploy Java apps to my HCP account. The deadline to submit the app was approaching so fast. Then came the weekend...

I read the article WebSocket on SAP HANA Cloud Platform which provided me fair idea about websocket in Java. Since I am not a java geek, I searched for some examples of websocket based chat servers. I found couple of results but all of those were providing Group Chat or Chat Room experience, not one to one chat. Going through all the codes, I got to understand that we receive session details of client endpoint at the server endpoint which I can use to send the message to particular user (or client endpoint).

The idea is to store the username against the session id in a hash map in my java servlet. I am pushing the session id and username when the websocket connection is opened and extracting the session id of recipient at the time of sending the message to that particular user. The app will send the logged in username at the time of connecting the websocket connection. So my servlet goes like this:
package chat;
import java.io.IOException;
import java.util.HashMap;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
//declaring server endpoint with query parameter
@ServerEndpoint("/chatApp/{username}")
public class oneToOneChat {
//a HashMap to store the online users with their session id and username
static HashMap<String, String> usersMap = new HashMap<String, String>();
//a set to store the session parameters of all connected clients
private static final Set<oneToOneChat> connections = new CopyOnWriteArraySet<>();
private String username;
private Session session;
public oneToOneChat() {
username = null;
}
@OnOpen
public void start(Session session, @PathParam("username") String user) {
this.session = session;
connections.add(this); //adding this session to connections set
this.username = user;
addUserInMap(session.getId(), username); //adding username and session id in hashmap
newJoinUpdateAll(username); //broadcasting new user joined notification to all connected clients
}
@OnClose
public void end(Session session) {
connections.remove(this); //removing the session from connections set
removeUserInMap(session.getId(), username); //removing the username and session id from hashmap
closeUpdateAll(username); //broadcasting user closed notification to all connected clients
}
@OnMessage
public void incoming(Session session, String message) {
String action = extractAction(message); //extracting action from user's msg
switch (action) {
case "GET_USERS_LIST":
String usersList = getOnlineUsersList(); //getting list of all online users
broadcast(usersList, session.getId()); //broadcasting this list to connected client
break;
case "CHAT":
String from = extractFrom(message); //extracting sender
String to = extractTo(message); //extracting recipient
String actualMessage = extractActualMessage(message); //extracting actual message
sendMessageToUser(to, from, actualMessage); //sending this message to recipient
break;
default:
break;
}
}
private void sendMessageToUser(String to, String from, String actualMessage) {
String toSessionId = getSessionIdOfUser(to); //getting sessionid of recipient
String messageToSend = prepareMessage(to, from, actualMessage); //preparing proper format of msg
broadcast(messageToSend, toSessionId); //sending the message to recipient
}
private void broadcast(String messageToSend, String toSessionId) {
for (oneToOneChat client : connections) {
try {
synchronized (client) {
//comparing the session id
if (client.session.getId().equals(toSessionId)) {
client.session.getBasicRemote().sendText(messageToSend); //send message to the user
}
}
} catch (IOException e) {
connections.remove(client);
try {
client.session.close();
} catch (IOException e1) {
}
String message = String.format("* %s %s",
client.username, "has been disconnected.");
broadcast(message);
}
}
}
private static void broadcast(String msg) {
for (oneToOneChat client : connections) {
try {
synchronized (client) {
client.session.getBasicRemote().sendText(msg); //broadcasting to all connected clients
}
} catch (IOException e) {
connections.remove(client);
try {
client.session.close();
} catch (IOException e1) {
}
String message = String.format("* %s %s",
client.username, "has been disconnected.");
broadcast(message);
}
}
}
private String prepareMessage(String to, String from, String actualMessage) {
String msg = "RESPONSE~CHAT|FROM~"+from+"|TO~"+to+"|MSG~"+actualMessage; //formatting msg
return msg;
}
//retrieving the session id of user
private String getSessionIdOfUser(String to) {
if (usersMap.containsValue(to)) {
for (String key : usersMap.keySet()) {
if (usersMap.get(key).equals(to)) {
return key;
}
}
}
return null;
}
private void newJoinUpdateAll(String user) {
String message = String.format("%s%s", "JOINED~", user); //formatting msg
broadcast(message); //broadcasting this message to all connected clients
}
private void addUserInMap(String id, String username) {
usersMap.put(id, username); //adding session id and username to hashmap
}
private void removeUserInMap(String id, String user) {
usersMap.remove(id); //removing session id and username from hashmap
}
private void closeUpdateAll(String user) {
String message = String.format("%s%s", "CLOSED~", user); //formatting msg
broadcast(message); //broadcasting this message to all connected clients
}
private String extractActualMessage(String message) {
String[] firstSplit = message.split("\\|");
String[] secondSplit = firstSplit[3].split("~");
String match = "MSG";
if(secondSplit[0].equals(match)){
return secondSplit[1];
}
return null;
}
private String extractTo(String message) {
String[] firstSplit = message.split("\\|");
String[] secondSplit = firstSplit[2].split("~");
String match = "TO";
if(secondSplit[0].equals(match)){
return secondSplit[1];
}
return null;
}
private String extractFrom(String message) {
String[] firstSplit = message.split("\\|");
String[] secondSplit = firstSplit[1].split("~");
String match = "FROM";
if(secondSplit[0].equals(match)){
return secondSplit[1];
}
return null;
}
private String extractAction(String message) {
String[] firstSplit = message.split("\\|");
String[] secondSplit = firstSplit[0].split("~");
String match = "ACTION";
if(secondSplit[0].equals(match)){
return secondSplit[1];
}
return null;
}
//getting list of users from hashmap
private String getOnlineUsersList() {
String usersList = new String();
for(Entry<String, String> m:usersMap.entrySet()){
String iUser = m.getValue();
if(usersList.toLowerCase().contains(iUser.toLowerCase())){
continue;
}else{
usersList = iUser+"|"+usersList;
}
}
usersList = "ONLINEUSERS~"+usersList;
return usersList;
}
}



P.S. - The above code is the result of my initial development of servlet and may have bugs :twisted: .

Known Issues:




  • It transmits the raw message. We can resolve this by encrypting the message.

  • There is no authentication at the Server Endpoint.

  • Not standardized


Testing:


I deployed this code to my HCP account. I used "Old Websocket Teminal" extension in chrome to test my servlet. The URI to connect is
wss://websocketp12345678trial.hanatrial.ondemand.com/WebSocket/chatApp/USERNAME

Remember to replace the red colored part with your p-user and username. (For testing, you can use any name as username)

I am communicating to my server in following fashion:

 

To get list of online users:


my message to server is:
"ACTION~GET_USERS_LIST|"

Response will be like:
"ONLINEUSERS~IronMan|Thor|Hulk|"

To send a message to a particular user:


my message to server is:
"ACTION~CHAT|FROM~IronMan|TO~Hulk|MSG~hi this is only for Hulk"

the recipient will get:
"RESPONSE~CHAT|FROM~IronMan|TO~Hulk|MSG~hi this is only for Hulk"

Notification about new user coming online and going offline:


Every client will get this notification as users come online or go offline in real time
"JOINED~IronMan"

"CLOSED~Thor"

We built an UI5 app to showcase this. You can find the screenshot here:



Hope this helps in kickstarting for so many other scenarios where you need sending message to particular client and not broadcasting to all.

Regards,
Ekansh

10 Comments
Labels in this area