Thursday, August 6, 2015

Spring Custom Tags with Spring's Extensible XML Authoring

Spring Custom Tags with Spring's Extensible XML Authoring

In situations where we have our custom xml in place and we want to inject(plugin) our xml configuration in spring context ,  Spring's Extensible XML Authoring makes it possible to do the same. With Spring's Extensible XML Authoring we can define our own namespace and have our own custom elements with custom attributes. I am going to show how can we configure our custom xml elements in a spring bean context. Prior to go through this blog , it is required to have basic knowledge of XML and Spring bean configurations.     

Below jars are required :

1) org.springframework.beans-3.0.0.RELEASE.jar
2) org.springframework.core-3.0.0.RELEASE.jar
3) org.springframework.context-3.0.0.RELEASE.jar
4) commons-logging-1.1.jar


Below I have defined a custom element "ce:customElement" which have custome child element "ce:component" which can occur more than once with one attribute "componentName". Below is snippet of how we write the custom element in a spring config file .



<ce:customElement id="customEl" >
        <ce:component componentName="component1"  >
        </ce:component>
</ce:customElement>


Below are the steps that we will follow to achieve our goal :
1) Define customElement XSD .
2) Write Namespace Handler for customElement .
3) Write Parser for customElement XSD which will register to Spring BeanDefinition.
4) Create spring.handlers and spring.schemas to map the schemas and handlers.
5) Write one bean which will be injected to our custom child element "component" .
6) Finally are ready to use the custom elements to configure in spring bean context file.

Define customElement XSD

I have defined an XSD below with custom element.

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://customschemanamespace.springextn.prasenjit.com/customElement"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans"
 targetNamespace="http://customschemanamespace.springextn.prasenjit.com/customElement"
 elementFormDefault="qualified" attributeFormDefault="unqualified">

 <xsd:import
  schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"
  namespace="http://www.springframework.org/schema/beans" />
 <xsd:element name="customElement">
  <xsd:complexType mixed="true">
   <xsd:complexContent>
    <xsd:extension base="beans:identifiedType">
     <xsd:sequence>
      <xsd:element ref="component" minOccurs="0" maxOccurs="unbounded"></xsd:element>
     </xsd:sequence>
    </xsd:extension>
   </xsd:complexContent>
  </xsd:complexType>
 </xsd:element>

 <xsd:element name="component">
  <xsd:complexType>
   <xsd:complexContent>
    <xsd:extension base="beans:identifiedType">
     <xsd:group ref="beans:beanElements" />
     <xsd:attributeGroup ref="beans:beanAttributes"/>
     <xsd:attribute name="componentName" type="xsd:string" use="required" />
    </xsd:extension>
   </xsd:complexContent>
  </xsd:complexType>
 </xsd:element>

</xsd:schema>

Above xsd I have 2 custom elements "customElement" and "component" . The element named "customElement" has child element "component" which can occur 0 and more times.

Namespace Handler for handling the Custom Namespace

package com.prasenjit.springextn.customschemanamespace.parser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class CustomElementNameSpaceHandler extends NamespaceHandlerSupport {
    private static final String NAME_CUSTOM_FOO_ELEMENT = "customElement";

    public void init() {
     registerBeanDefinitionParser(NAME_CUSTOM_FOO_ELEMENT, new CustomElementSchemaParser());
    }
    
}

BeanDefinitionParser for Custom Namespace

package com.prasenjit.springextn.customschemanamespace.parser;
import java.util.List;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

import com.prasenjit.springextn.customschemanamespace.Component;
import com.prasenjit.springextn.customschemanamespace.CustomElement;

public class CustomElementSchemaParser extends AbstractBeanDefinitionParser {
    private static final String NAME_CUSTOM_FOO_ELEMENT = "customElement";

    @Override
    protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
      BeanDefinitionBuilder customElement = BeanDefinitionBuilder.rootBeanDefinition(CustomElement.class);
      List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component");
         System.out.println(childElements.size());
         ManagedList<BeanDefinition> children = new ManagedList<BeanDefinition>(childElements.size());
         for (Element element2 : childElements) {
         BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class); 
         component.getBeanDefinition().getPropertyValues().addPropertyValue("componentName", element2.getAttribute("componentName"));
    System.out.println(element2.getAttribute("componentName"));
    List<Element> childElements1=DomUtils.getChildElementsByTagName(element2, "property");
    System.out.println(childElements1.size());
    for (Element element3 : childElements1) {
     parserContext.getDelegate().parsePropertyElement(element3, component.getBeanDefinition());
   }
    children.add(component.getBeanDefinition());
   }
         
        customElement.getBeanDefinition().getPropertyValues().addPropertyValue("components", children) ;
        parserContext.getRegistry().registerBeanDefinition(NAME_CUSTOM_FOO_ELEMENT, customElement.getBeanDefinition());
        return (AbstractBeanDefinition)customElement.getBeanDefinition();
    }

}

spring handlers and schemas file

Once we are done with the above steps which involves coding and defining XSD schema , we have to define two more files for spring to plugin custom namespace handler and the XSD schema file which we have written above . Create the below files spring.handlers and spring.schemas and place the files under META-INF directory of your project . Make sure META-INF is also included in your classpath or bundled in jar .

spring.handlers
http\://customschemanamespace.springextn.prasenjit.com/customElement=com.prasenjit.springextn.customschemanamespace.parser.CustomElementNameSpaceHandler

spring.schemas
http\://customschemanamespace.springextn.prasenjit.com/customElement/customElement.xsd=META-INF/CustomElement.xsd

My CustomElement  which will be created as bean while parsing will be registered to Spring Bean context.

CustomElement.java

package com.prasenjit.springextn.customschemanamespace;
import java.util.List;

public class CustomElement {
   
    private List<Component> components;
    
 public List<Component> getComponents() {
  return components;
 }

 public void setComponents(List<Component> components) {
  this.components = components;
 }

}

Component.java

package com.prasenjit.springextn.customschemanamespace;
public class Component {

    private String componentName;
    private IModule module;
 public String getComponentName() {
  return componentName;
 }
 public void setComponentName(String componentName) {
  this.componentName = componentName;
 }
 public IModule getModule() {
  return module;
 }
 public void setModule(IModule module) {
  this.module = module;
 }
   

}


Below I have created some beans which will be injected in "component" as a property. 

package com.prasenjit.springextn.customschemanamespace;

public interface IModule {

 public int getCount();

 public String getName();

}

Module.java

package com.prasenjit.springextn.customschemanamespace;

public class Module implements IModule{
    private int count;
    private String name;
 public int getCount() {
  return count;
 }

 public void setCount(int count) {
  this.count = count;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

        // getter and setters omitted
}

Final Step

We will define our new custom tag in a spring context file . Then we will write a test program to Demo the configurations .

customelements.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:ce="http://customschemanamespace.springextn.prasenjit.com/customElement"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
    http://customschemanamespace.springextn.prasenjit.com/customElement http://customschemanamespace.springextn.prasenjit.com/customElement/customElement.xsd">
     
     <bean class="com.prasenjit.springextn.customschemanamespace.Module" id="module1">
                 <property name="count" value="100" />
                 <property name="name" value="module1" />
      </bean>
            
    <ce:customElement id="customEl" >
        <ce:component componentName="component1"  >
          <property name="module" ref="module1">
         </property>
        </ce:component>
        <ce:component componentName="component2"  >
         <property name="module">
             <bean class="com.prasenjit.springextn.customschemanamespace.Module">
                 <property name="count" value="10" />
                 <property name="name" value="module2" />
             </bean>
         </property>
        </ce:component>
    </ce:customElement>
    
</beans>

Test the configurations we did using custom tag with below code :

package com.prasenjit.springextn.customschemanamespace;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestCustomSpringTag {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("classpath:com/prasenjit/springextn/customschemanamespace/customelements.xml");
        CustomElement customEl = (CustomElement) appContext.getBean("customEl");
        System.out.println("count = " + customEl.getComponents().size());
        if (!customEl.getComponents().isEmpty()) {
   for (Component component : customEl.getComponents()) {
    System.out.println(component.getComponentName());
    System.out.println(component.getModule().getCount()+"::"+component.getModule().getName());
   }
  }
    }
}

Run the above Test code to check the configuration works properly or not . I will be uploading my source codes shown in this blog soon .

Please add valuable feedback/comments about this post .

Thanks.


References:

The below articles and websites are used for reference .

  • http://docs.spring.io/spring-framework/docs/3.0.7.RELEASE/reference/extensible-xml.html
  • http://cscarioni.blogspot.in/2012/04/creating-custom-spring-3-xml-namespace.html




Sunday, November 30, 2014

2 Way SSL with example


This Blog will assist you to configure  2 way SSL in Apache 2 web server which I have hosted in Ubuntu 10.04(LUCID) . I have also tested 2 way SSL with a Java Client . The code can found here .
The certificates I have generated are  self-signed .

Introduction:
Now a days B2B integration requires integrity  and confidentiality of the data transferred. That is where SSL ( Secure Sockets Layer ) comes into picture. To achieve  integrity  and confidentiality of the data you need to enable SSL in your web server .

One-way SSL authentication allows a SSL client to confirm an identity of SSL server. However, SSL server cannot confirm an identity of SSL client. This kind of SSL authentication is used by HTTPS protocol. The SSL client authentication is done on a “application layer” of OSI model by the client entering an authentication credentials such as username and password or by using a grid card.

Two-way SSL authentication also known as mutual SSL authentication allows SSL client to confirm an identity of SSL server and SSL server can also confirm an identity of the SSL client. This type of authentication is called client authentication because SSL client shows its identity to SSL server with a use of the client certificate. Client authentication with a certificate can add yet another layer of security or even completely replace authentication method such us user name and password.

Generate Self-Signed certificate with Open SSL



As first step I have created a directory where I have generated all server and client keys and certificates.
 prasenjit@prasenjit-desktop:~$ mkdir ssl_keys  

Next we need to generate self-signed certificate CA. Once prompted for a set of values we can provide dummy values but relevant to your organisation. I have provided a screen shot below .
 openssl req -newkey rsa:2048 -nodes -keyform PEM -keyout ca.key -x509 -days 3650 -outform PEM -out ca.cer  



In your current directory a file “ca.key” with private key of certificate authority (CA) and ca.cer with its self-signed certificate.

Next step we need to generate private SSL key for the server:
 openssl genrsa -out server.key 2048  

Next we would generate Certificate Signing Request in PKCS#10 format.Once the command is entered we will asked for a set of values . I have provided a screen shot below .
 openssl req -new -key server.key -out server.req  

Next we will issue server certificate with serial number 100 with self-signed certificate authority:
 openssl x509 -req -in server.req -CA ca.cer -CAkey ca.key -set_serial 100 -extensions server -days 365 -outform PEM -out server.cer  


Currently if you list down your files in current directory you should following files generated:
 ca.key  
 ca.cer  
 server.key  
 server.req  
 server.cer  

New file server.key contains server's private key and file server.cer is a certificate itself.

Next for 2 way SSL Generete private key for SSL client:
 openssl genrsa -out client.key 2048  


 For client as we need to generate Certificate Signing Request:
  openssl req -new -key client.key -out client.req  

With the self-signed Certificate Authority that we have generated ,we will issue a client certificate with serial number 101:
  openssl x509 -req -in client.req -CA ca.cer -CAkey ca.key -set_serial 101 -extensions client -days 365 -outform PEM -out client.cer  

Next we will save client's private key and certificate in a PKCS#12 format. This certificate will be secured by a password and this password will be used in the following sections to import the certificate into HTTPS client. I have kept the password as "changeit" .
  openssl pkcs12 -export -inkey client.key -in client.cer -out client.p12  


Configure 2 Way SSL in Apache 2

First thing is copying all the server certificates , keys i.e ca.cer, server.cer, server.key files to "ssl" directory under apache2( /etc/apache2/ssl ).
Once copying is done open "default-ssl" file under "/etc/apache2/sites-available". Search for "SSLCertificateFile" . If it is commented , then uncomment and add the location of the "server.cer" file .


Do the same for "SSLCertificateKeyFile" as above.

Next search for "SSLVerifyClient" in the same file . Once found, replace with the below configuration.

 #  Client Authentication (Type):  
     #  Client certificate verification type and depth. Types are  
     #  none, optional, require and optional_no_ca. Depth is a  
     #  number which specifies how deeply to verify the certificate  
     #  issuer chain before deciding the certificate is not valid.  
     SSLVerifyClient require  
     SSLVerifyDepth 1  
     SSLCACertificateFile /etc/apache2/ssl/ca.cer  

Then save the file and restart apache web server.

 /etc/init.d/apache2 restart  

Test 2 Way SSL

We can test 2 Way SSL with following ways :
  • Web Browser . It is very easy if you have the client keystore file ( client.p12 ) with PKCS#12 format , just import the file in your browser and you will be able to launch the apache 2 default web page( https://localhost/ ).
  • Java Https Client : Most  B2B applications will use HTTPS clients to connect 2 way ssl enabled server . I have written a sample Java client which connects to my 2 Way ssl enabled server . To test the client we should have the following :
    • Copy the client keystore file ( client.p12 ) from server to a local directory( Lets say in my case I have downloaded to 'D:\ubuntu_apache_keys\apacheKeys' ). 
    • Generate trustStore file  using 'keytool' command and copy the trustStore  from server to a local directory. ( Lets say in my case I have downloaded to 'D:\ubuntu_apache_keys\apacheKeys' ).
  keytool -import -alias myapacheserver -file ca.cer -storepass changeit -keystore myapacheserver.keystore  

Once the  command is entered , it will ask as if we trust the certificate . We should answer with "yes" or "no". I have added yes to add it in my trustore. Below is a screenshot.

Replace the IP address in the code with your server's IP address.
 package com.prasenjit.samples;  
 import java.io.BufferedReader;  
 import java.io.IOException;  
 import java.io.InputStream;  
 import java.io.InputStreamReader;  
 import java.net.MalformedURLException;  
 import java.net.URL;  
 import javax.net.ssl.HostnameVerifier;  
 import javax.net.ssl.HttpsURLConnection;  
 import javax.net.ssl.SSLSession;  
 import javax.net.ssl.SSLSocketFactory;  
 public class TwoWaySSLClient {  
      public static void main(String[] args) {  
           SSLSocketFactory sslsocketfactory =(SSLSocketFactory) SSLSocketFactory.getDefault();  
           try {  
                URL url = new URL("https://192.168.0.102/");  
                HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();  
                conn.setHostnameVerifier(new HostnameVerifier()   
          {      
                     @Override  
                     public boolean verify(String arg0, SSLSession arg1) {  
                          return true;  
                     }   
          });   
                conn.setSSLSocketFactory(sslsocketfactory);  
                InputStream inputstream = conn.getInputStream();  
                InputStreamReader inputstreamreader = new InputStreamReader(inputstream);  
                BufferedReader bufferedreader = new BufferedReader(inputstreamreader);  
                String string = null;  
                while ((string = bufferedreader.readLine()) != null) {  
                  System.out.println("Received " + string);  
                }  
           } catch (MalformedURLException e) {  
                e.printStackTrace();  
           } catch (IOException e) {  
                e.printStackTrace();  
           }  
      }  
 }  

Run the above code with the below arguments.
 -Djavax.net.ssl.keyStoreType=pkcs12  
 -Djavax.net.ssl.trustStoreType=jks  
 -Djavax.net.ssl.keyStore=D:\\ubuntu_apache_keys\\apacheKeys\\client.p12  
 -Djavax.net.ssl.trustStore=D:\\ubuntu_apache_keys\\apacheKeys\\myapacheserver.keystore  
 -Djavax.net.debug=ssl  
 -Djavax.net.ssl.keyStorePassword=changeit  
 -Djavax.net.ssl.trustStorePassword=changeit  

If you have followed the above steps you should be able to connect 2 Way SSL enabled Apache2 web server without any error or exceptions. You can also trace the ssl handshake logs using the above param "-Djavax.net.debug=ssl" .  I will  soon add the Java src file to my GitHub .

Please add valuable feedback/comments about this post .

Thanks.


References:

The below articles and websites are used for reference . Please do visit for more explanation.





Saturday, March 22, 2014

Websocket Load Testing With JMETER


Apache JMeter is a load testing tool which is used to analyze and measure performance for any service that we develop. I am going to cover the usage of the tool with an example that I have developed to load test my application that I have covered in my previous post.
I have developed a websocket application using eclipse jetty . To load test my application using JMeter I have written Customized Java Request . I have followed below sequence :

  • Websocket Connection : Created  client  websocket  to connect to websocket application server.
  • Handshake Request :  Handshake request message is sent  to server with username and connection Id.
  • FriendToFriend Request :  FriendToFriend request is sent to server with a message from the current user to a random friend .

JMeter Approach 

I have divided the above the sequence into two thread groups with same no of users each . First thread group contains customized Java Sampler request which does the below :
  • Creates the websocket client to connect websocket application server. Once the connection is created , the connection object is stored in JMeter variable whose key is the username.
  • Sends the Handshake request to do login with the username .
Once the Java request is completed . I have added a BSF Postprocessor to do the following :
  •  Set the JMeter property 'ThreadValue' to current thread number of the First Thread group. The property 'ThreadValue' will be used later to activate second thread  group.
  • Set the JMeter property  current user name to connection object which is retrieved from  JMeter variable that has been stored in Java request. 
The BSF Postprocessor  will be executed for each thread of the first thread group. Once all  the threads are executed , the execution moves to second thread group . This group has If Controller which checks that all the threads from first thread group has been executed. Once the If Controller succeeds the following requests will get executed:

  • BSF PreProcessor : I have added BSF PreProcessor  . In this pre processor I am setting the JMeter variable current user name to the connection object that has been set in BSF Postprocessor of first thread group.
  • Java Sampler Request :  I have added  Java Sampler Request to send message to randomly selected users . The user's connection object will retrieved from JMeter variable that has been set in  BSF PreProcessor . 

The above requests will be executed for all the threads in second thread group.

Steps to Configure JMeter Test Plan

 1) Launch JMeter and create Test Plan . I have named the Test Plan as 'My Test Plan' .


2) In Test Plan , add thread group . I have named it to 'Login and Connection' . In Thread  properties section I have set Number of threads(users) to 10 and Ramp-Up period to 10 seconds.


3) In 'Login and Connection' thread group I have added config element CSV Data set config. I have named the data set to User Name Set. I have used csv file to store user names in ascending order i.e login1 , login2 .. etc . I have copied the file to bin directory and referred the same in FileName section.  I have kept Variable Name as 'A' which I will referred later. I have changed 'Recycle on EOF' to false and 'Sharing Mode' to current thread group.

4) Next I have added Java Sampler , named it  'Websocket Connection Request' . I have written a custom AbstractJavaSamplerClient  class WebsocketAppJavaSamplerClient . I have overridden two important methods getDefaultParameters and runTest . I have set the classname of sampler to WebsocketAppJavaSamplerClient class. I added two default parameters in the overridden method  getDefaultParameters   which I will populate in the sampler request.

5) Next I added BSF PostProcessor to Java Sampler  'Websocket Connection Request' . The post processor is executed immediately after the  Java Sampler is executed . The language is set to Beanshell .
I have already explained above about the logic that I have written in Beanshell.

6) Next I have added second thread group which is 'Message Sender Group' . I have configured same number of threads and ramp up period as with first thread group ''Login and Connection' .

7) Next I have added If controller , the purpose of adding the controller is already explained .


8) Next I  added CSV Data set config 'User Name Set 2' with the same set of user names but in different file. I have added the data set in IF controller .

9) Next I added Java Sampler ,  'Message Send Request' . I have written a custom AbstractJavaSamplerClient  class WebsocketSamplerClientTest . I have overridden two important methods getDefaultParameters and runTest . I have set the classname of sampler to WebsocketAppJavaSamplerClient class. I added one default parameter in the overridden method  getDefaultParameters   which I will populate in the sampler request.

10) Next I added BSF Pre Processor to Java Sampler ,  'Message Send Request' . The purpose to add sampler , I have explained it already .

11) I have added User Defined Variables and have defined one variable 'noOfThreads' which I am using in IF controller for comparison.

12) I have also added View Results Tree to track the responses of each threads of the thread groups 'Login and Connection' and  'Message Sender Group' .



The custom Java Samplers are wrapped in jar and placed under the JMETER_HOME\lib\ext directory. After the jar is placed the jmeter application can be started . The custom Sampler classes will be populated in the Java Samplers.

I have uploaded the Java Sampler source codes here.



Thursday, February 13, 2014

Websocket with Eclipse Jetty

Websocket is a new standard protocol which has provided a way for client application to establish a two-way full duplex connection over a single TCP connection to the server and can be used in an HTML5-compliant browsers, thus eliminating the need to either use  polling, or depend on third-party browser plugins. Well lets keep the discussions apart as the protocol is well covered here . I am here to cover an implementation of websocket based web application with eclipse Jetty . The application is a website chat , but the server side implementation can support native client as well if the client supports websocket protocol and standards.

Development environment : 

  • IDE - Eclipse IDE.
  • Web server - Eclipse jetty distribution-8.1.12.v20130726  ( Download Link )
  • OS - Ubuntu Lucid , Windows 7 
  • Client framework - Backbone.js , Require.js , Jquery  

Code Insight :

   The high level insights of code walk through will be covered . Later I will share and explain the UML diagrams that I have prepared.   The touch point of the application is WebsocketChatServlet which extends Jetty's WebSocketServlet which returns the ChatWebsocket  from WebsocketPool.

public class WebsocketChatServlet extends WebSocketServlet{

    /**
     * 
     */
    private static final long serialVersionUID = 1433567677561132601L;
    
    private ChatWebsocketFactory chatWebsocketFactory;
    
    @Override
    public WebSocket doWebSocketConnect(HttpServletRequest arg0, String arg1) {
        ApplicationContext applicationContext=WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        this.chatWebsocketFactory=(ChatWebsocketFactory)applicationContext.getBean("chatWebsocketFactory");
        return chatWebsocketFactory.getChatWebsocket();
    }
    
    public ChatWebsocketFactory getChatWebsocketFactory() {
        return chatWebsocketFactory;
    }
    public void setChatWebsocketFactory(ChatWebsocketFactory chatWebsocketFactory) {
        this.chatWebsocketFactory = chatWebsocketFactory;
    }
    

}

ChatWebSocket implements Jetty's WebSocket , OnBinaryMessage , OnTextMessage interfaces which provides call backs like onOpen , onClose , onMessage methods which are useful to manage life-cycle of websocket.



public class ChatWebsocket implements WebSocket,OnBinaryMessage,OnTextMessage{

    private Connection connection;
    private BlockingQueue<String> blockingQueue;
    private WebsocketPool pool;
    private static final Logger LOGGER=Logger.getLogger(ChatWebsocket.class);
    
    public ChatWebsocket(BlockingQueue<String> blockingQueue,WebsocketPool pool){
        this.blockingQueue=blockingQueue;
        this.pool=pool;
    }
    
    @Override
    public void onClose(int arg0, String arg1) {
        LOGGER.info("ChatWebsocket.onClose()");
        this.pool.returnToPool(this);
    }

    @Override
    public void onOpen(Connection arg0) {
        LOGGER.info("ChatWebsocket.onOpen()");
        this.connection=arg0;
        try {
            this.connection.sendMessage(AppUtils.prepareConnectInitResponse(String.valueOf(this.hashCode())));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onMessage(byte[] arg0, int arg1, int arg2) {
        LOGGER.info("ChatWebsocket.onMessage()");
        
    }

    @Override
    public void onMessage(String arg0) {
        LOGGER.info("ChatWebsocket.onMessage()::::"+arg0);
        try {
            this.blockingQueue.put(arg0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void sendMsg(String Msg) {
        try {
            this.connection.sendMessage(Msg);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

When the users login successfully , they are able to see online users in the left hand side . Once they click on the online user , chat window opens at the bottom and they can chat. Right now I am supporting ten users(username- login1 to login10 , password - 123456 ) , but It can support much more users based on the load testing done on server , which is I am currently spending time on.
I have a LoginController which validates and authenticates user login. After successful  authentication it does two things .

  • Send successful response with all currently logged in users.
  • Notify all other current logged in users about the user that has logged in.
Login is currently implemented with REST. 


@Path("/login")
public class LoginController {

    @Context
    private ServletContext context;
    
    @POST
    @Consumes({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
    @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
    public LoginResponse doLogin(LoginRequest loginRequest) {
        LoginResponse loginResponseObject=new LoginResponse();
        boolean isAuthencated=authenticateUser(loginRequest);
        if (isAuthencated) {
            loginResponseObject.setStatus(0);
            Set<String> users=Memorizers.getUserVsConnectorId().keySet();
            List<Profile> usersList=new ArrayList<Profile>();
            for (String string : users) {
                Profile userProfile=new Profile(true,string);
                usersList.add(userProfile);
            }
            loginResponseObject.setProfiles(usersList);
            notifyOnlineUsers(loginResponseObject.getProfiles());
        }else{
            loginResponseObject.setStatus(-1);
        }
        return loginResponseObject;
    }
    
    private boolean authenticateUser(LoginRequest LoginRequest){
        if (isValidLogin(LoginRequest.getUsername(), LoginRequest.getPassword())) {
            String connectorId=Memorizers.getUserVsConnectorId().get(LoginRequest.getUsername());
            if (connectorId==null) {
                Memorizers.getUserVsConnectorId().putIfAbsent(LoginRequest.getUsername(), LoginRequest.getConnectorId());
            }
            return true;
        }
        return false;
    }
    
    private void notifyOnlineUsers(List<Profile> profiles){
        ApplicationContext applicationContext=WebApplicationContextUtils.getWebApplicationContext(context);
        BlockingQueue<WebsocketResponse> outboundQ=(LinkedBlockingQueue<WebsocketResponse>)applicationContext.getBean("outBoundQ");
        Set<String> users=Memorizers.getUserVsConnectorId().keySet();
        List<Profile> usersList=new ArrayList<Profile>();
        for (String string : users) {
            Profile userProfile=new Profile(true,string);
            usersList.add(userProfile);
        }
        NotifyLoggedInUsers loggedInUsers=new NotifyLoggedInUsers(AppInfo.SUCCESS,new ArrayList<String>(Memorizers.getUserVsConnectorId().values()), profiles);
        loggedInUsers.setLoggedInUsers(usersList);
        System.out.println("Notifying online users!!");
        PoolExecutors.addTaskToPES(loggedInUsers, outboundQ);
    }
    
    private boolean isValidLogin(String userName,String password) {
        String loginRegex="(login)([1-9]|10)";
        if (userName!=null && password!=null && userName.matches(loginRegex) && "123456".equals(password)) {
            return true;
        }
        
        return false;
    }
}

For this Application I am following one life-cyle which I am going to share later as I am preparing the UML diagrams. But application code has been uploaded to my GIT hub repo.

Now some client 

I have implemented client side using Backbone.js , Require.js , Jquery , HTML5 websocket. Its a working code. Still some features are under development  , but its ready for you to start If you are looking for a service or application . 
The first we launch the url , it loads all the modules and does necessary initialization. 
  • Routing initialization for backbone views . Currently I have two views. Login View and ChatRoom View.
  • WebscoketClient  initialization. Creates HTML5 websocket and onmessage callback retrieves the connectorId which is saved to a Backbone model . connectorId is generated by server and  maintained at client for all the communications exchanged over websocket. 


requirejs.config({
    
    paths:{
        jquery: 'lib/jquery/jquery-1.7.1',
        jqueryUI:'lib/jquery/jquery-ui-1.8.23.custom.min',
        underscore:'lib/underscore/underscore-min',
        backbone:'lib/backbone/backbone-min',
        handlebar:'lib/handlebar/Handlebars',
        text:'lib/text/text',
        chatWidget:'lib/plugins/jqueryChatBox/jquery.ui.chatbox',
        chatBoxManager:'lib/plugins/jqueryChatBox/ChatBoxManager'
    }   
});

require(['scripts/BootStrap'],function(BootStrap){
    console.log('BootStrap loaded!!');
    BootStrap.initialize();
})
WebSocketChatApp.js
define( ['jquery',  'underscore', 'backbone', 'handlebar',
        'scripts/login/LoginView','scripts/chatroom/ChatroomView','scripts/common/WebsocketClient'  ], function($, _,
        Backbone, Handlebars, LoginView,ChatroomView,connector) {

    var views={};
    var AppRouter = Backbone.Router.extend( {
        routes : {

            'login' : 'login',
            'chatroom' : 'chatroom',

            '*actions' : 'defaultAction'
        }
    });

    var initialize = function() {
        var app_router = new AppRouter();
        
        app_router.on('route:login', function() {
            if (views['loginView']) {
                views['loginView'].close();
            }
            var loginView = new LoginView({el:'#appContainer'});
            views['loginView']=loginView;
            loginView.render();
        });

        app_router.on('route:chatroom', function() {
            var chatroomView = new ChatroomView({el:'#appContainer'});
            chatroomView.render();
        });
        app_router.on('route:defaultAction', function(actions) {

            console.log('No route:', actions);
        });
        
        $(document).on('routeEvent',function(event,responseObj){
            window.Map['loginResponse']=responseObj;
            app_router.navigate('chatroom',{trigger: true});
        })
        Backbone.history.start();
        connector.start('ws://localhost:8080/WebSocketChatApp/samplewebsocketchat');
    };
    
    window.Map={};
    
    return {

        initialize : initialize
    }
})
BootStrap.js
define(['scripts/common/CommonModel'],function(commonModel){
    var connectionObj=null;
    var connectorCache={};
    var listeners={};
    start=function(wsURI){
        if (connectionObj==null) {
            connectionObj=new WebSocket(wsURI);
        }
        connectionObj.onopen=function(event){handleOpen(event);};
        connectionObj.onclose=function(event){handleClose(event);};
        connectionObj.onmessage=function(event){handleMessage(event);};
        connectionObj.onerror=function(event){handleError(event);};
    }
    
    function handleOpen(event) {
        console.log('Connection Opened!!');
    }
    
    function handleClose(event) {
        console.log('Connection Closed!!');
        clearInterval(timerTask);
        connectionObj=null;
    }
    
    function handleMessage(event) {
        console.log('handleMessage::'+event.data);
        var msgObj=JSON.parse(event.data);
        if (msgObj.responseId==='ConnectInit') {
            handleConnectionInit(msgObj);
        }else if (msgObj.responseId==='HandShakeRes') {
            handleHandShake(msgObj);
        }else if (msgObj.responseId==='Pong') {
            handlePONG(msgObj);
        }else{
            dispatchToListener(msgObj);
        }
        
    }
    
    function handleConnectionInit(msgObj){
        connectorCache['connectorId']=msgObj.connectorId;
        commonModel.set('connectorId',msgObj.connectorId);
        var handShakeReq={
                requestId:'HandShakeReq',
                connectorId:connectorCache['connectorId'],
                userId:'',
                friendList:[]
        }
        connectionObj.send(JSON.stringify(handShakeReq));
    }
    
    var timerTask;
    
    function handleHandShake(msgObj){
        timerTask=setInterval(function() {
            sendPING();
        },60000);
    }
    
    function sendPING() {
        var connectorId=connectorCache['connectorId'];
        var pingReq={
                requestId:'Ping',
                connectorId:connectorCache['connectorId'],
                userId:'',
                friendList:[]
        }
        connectionObj.send(JSON.stringify(pingReq));
    }
    
    function handlePONG(msgObj){
        //console.log('Yet To Handle!!');
    }
    
    function dispatchToListener(msgObj) {
        if (listeners && listeners[msgObj.responseId]) {
            listeners[msgObj.responseId].handleMsg(msgObj);
        }
    }
    
    function handleError(event) {
        console.log('Connection Opened!!');
    }
    
    registerListener=function(serialID,handler){
        listeners[serialID]=handler;
    }
    
    sendMsg=function(reqObj){
        connectionObj.send(JSON.stringify(reqObj));
    }
    
    return {
        
        start:start,
        registerListener:registerListener,
        sendMsg:sendMsg
    }
})
WebsocketClient.js


Deployment Of Web Application

The deployment in eclipse Jetty is very similar to Apache Tomcat . You can either create a web archive(war) file or simply deploy the web application project as I did under the 'webapps' folder of Jetty's home directory(${jetty.home}). 




STEPS TO DEPLOY 
1) Download eclipse jetty from here. I am using version 8.1.12.v20130726.
2) Download following jars as well for logging. slf4j-log4j12-1.6.6.jar , slf4j-api-1.6.6.jar , log4j-1.2.17.jar 3) The app is a WebApp , so directory structure should be followed (war file can be created out of it or below deplyoment structure can be followed.).
Below can be used : Under webapps directory of ${jetty.home}:
 WebSocketChatApp---|
                                      |--App
                                      |--WEB-INF--|
                                                             |--classes
                                                             |--lib
                                                             |--applicationContext.xml
                                                             |--web.xml
4) For logging create a ${jetty.home}/lib/logging/ directory and place the three JAR files in step 2. Next you can follow this tutorial.
5) Then start the server . Go to ${jetty.home} , use command : java -jar start.jar
6) Launch the APP from browser. http://localhost:8080/WebSocketChatApp/index.html#login .

Some Screenshots from Different browser:







Download the full source Code here

Please add valuable feedback/comments about this post .

Thanks.