Skip to Content

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 😈 .

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:

3.PNG

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

To report this post you need to login first.

10 Comments

You must be Logged on to comment or reply to a post.

  1. Arshad Feeroz

    Great blog Ekansh. 🙂 I am also working on a similar one to one chat application using SAPUI5. Can you please tell me how did you get all the user details to the web socket server and how do you populate the list of logged in users to the sapui5 application?

    (0) 
    1. Ekansh Saxena Post author

      Hi Arshad,

      List of all logged in users is coming from this java servlet, you need to capture this in UI at the respective method. It is broadcasting the updated list to all the logged in users whenever a new user joins or drops off. You can enhance this functionality with the java code.

      (0) 

Leave a Reply