Pages

Friday, May 25, 2012

Enable https:// on tomcat in two steps.

With this tutorial, I will explain, how to enable SSL (Secure Socket Layer) on tomcat 7 in tow steps.
Step 01: Create .keystore file.

Run the following command to generate .keystore file from $JAVA_HOME/bin.

On windows :

%JAVA_HOME%\bin\keytool -genkey -alias tomcat -keyalg RSA 

On linux:

$JAVA_HOME/bin/keytool -genkey -alias tomcat -keyalg RSA  

Step 02: Uncomment "SSL HTTP/1.1 Connector on port 8443" on $CATALINA_HOME/conf/server.xml and modify it as follows.

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
              maxThreads="150" scheme="https" secure="true"
              clientAuth="false" sslProtocol="TLS" 
              keystoreFile="${user.home}/.keystore" keystorePass="123@com"/>


The "keystorePass" is the password which you provided when generating .keystore file. Now restart the tomcat.

By default port 8443 keeps listening https requests. Open a browser and check the following URL.

https://localhost:8443

If you see tomcat's home page, you are done.

Tuesday, May 22, 2012

How to expose an exsisting service as a web service ?

Introduction

This tutorial addresses a most practical scenario which is being faced by a developer. Most of the time, We may need to expose some of our existing services as web services. This situation can be encountered in different stages of project life cycle. If It is the initial stage, then You are almost safe and You can well prepare for that. But, What will happen, this requirement comes just after half of the development has finished or the system is running in production environment. 

This will be little tricky if the web services have not been taken into consideration for initial project architecture. You may involve with different kind of project architectures and use different kind of technologies. As a developer, You are not allowed to change some architectural stuff and configurations since there may be lots of dependencies. 

Most of the tutorials on the Internet explains the basic stuff of creating a web service. Some time, 'Hello world' application or sometime it may be simple calculator like that. These tutorials are good to have the basic understanding about the web services. But the real world scenario's are ten time complex than that and have to face difficulties when following those kind of tutorials. 

Practical scenario

With this tutorial, I am going to explain, How We really address a real world requirement that came through your supervisor. I am going to explain the same kind of scenario, which I faced recently. 

A health care organisation is running a plenty of pharmacies all around the island. They have a web application which handles all the inventories, pricing and billing, issuing pharmacy items etc. They needed to expose their pharmacy items prices through a web service so that their client application in the pharmacy can access those via the web service. 

Their web application has been developed within struts2, spring and hibernated integrated environment. It has all the spring managed DAO classes and also the service classes. The application uses spring's auto wiring technique, component scanning, transaction management etc. With these kind of background, I needed to expose pharmacy item prices as a web service. That is some of the methods from our current pharmacy service are needed to be exposed to outside via a web service.

I will show you, How to achieve this kind of requirement with a minimal modification to our existing project. 

Additional Libraries

I am going to implement the web service with JAX-WS. I have used JAX-WS 2.2 for my project. You can download the required JAX-WS version from here. This provides few tools that can be used to generate web service and it's client stuff. After downloading the required version of library, extract it some where in your local machine. I have placed it in my home folder. 

ie: /home/semika/jaxws-ri-2.2

Implementing web service

I already have spring managed service classes and DAO classes for pharmacy item which are being used internally by the web application. Those are not exposed to out side. Suppose, We need to expose findAll() method which returns a list of 'PharmacyItem' of 'PharmacyService' interface as a web service.

For Your convenience, I will show you the "PharmacyServiceImpl" java class which is used to perform normal pharmacy item operations. This is a usual spring mange bean. Keep in mind, methods of this class can only be used for internal operations of our web application currently. Those are not exposed as web services. 

PharmacyServiceImpl.java

/**
 * 
 */
package com.slauto.service.pharmacy.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.slauto.exceptions.ServiceException;
import com.slauto.model.pharmacy.PharmacyItem;
import com.slauto.persist.pharmacy.api.PharmacyItemDao;
import com.slauto.service.pharmacy.api.PharmacyItemService;

/**
 * @author semika
 *
 */
@Service("pharmacyItemService") 
public class PharmacyItemServiceImpl implements PharmacyItemService {

  @Autowired
  private PharmacyItemDao pharmacyItemDao;
 
  @Override
  public List<Pharmacyitem> findAll() throws ServiceException {
     return pharmacyItemDao.findAllPharmacyItems();
  }
}

As You can see, I have auto wired instance of 'PharmacyItemDao' with in the implementation class. As We all know, this is a spring managed service implementation class.

Next, We will implement the web service end point class for above spring managed service bean to expose it's methods as a web service methods. For the clarity, I created a separate class as web service end point.

PharmacyItemServiceEndPoint.java

package com.slauto.service.ws;
import javax.jws.WebMethod;
import javax.jws.WebService;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.slauto.exceptions.DataAccessException;
import com.slauto.model.pharmacy.PharmacyItem;
import com.slauto.service.pharmacy.api.PharmacyItemService;

/**
 * @author semika
 *
 */
@WebService(serviceName="pharmacyItemService") 
public class PharmacyItemServiceEndPoint {
 
  @WebMethod
  public List<Pharmacyitem> findAll() throws DataAccessException {
     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); 
     PharmacyItemService pharmacyItemService = (PharmacyItemService)context.getBean("pharmacyItemService");
     return pharmacyItemService.findAll();
  }
}

As You can see here, I am using "pharmacyItemService" bean which is used by our web application to access the relevant service method. The "pharmacyItemService" is a usual spring mange bean which is used to perform day to day pharmacy item operation. Nothing special on that. Specially note that @WebService and @WebMethod annotation which indicates this class works as a web service end point.

Here, I am getting service class instance via application context. Further, We can auto wire web service end point classes with spring by extending end point classes from "SpringBeanAutowiringSupport"  provided by spring. In that case, We do not need to create application context instance as I have done above. I could not make that work, that is why, I used the above technique.

With "SpringBeanAutowiringSupport", when deploying web service (explain bellow), I encountered an exception which was hard  for me to resolve that. So I choose this technique. Any way, I do not like, what I have used above :) .

Generating web service

I am using a apt, wsgen and wsimport tools provided by JAX-WS to generate the portable artefacts used in JAX-WS services. The relevant ant targets for the 'build.xml' file will be as follows. 

Your may need following property declared at the top of the 'build.xml' file.

Property:

<property name="tomcat.home"        value="/home/semika/apache-tomcat-7.0.25" />
<property name="jaxws.home"         value="/home/semika/jaxws-ri-2.2" />
<property name="build.classes.home" value="${basedir}/WEB-INF/classes" />
<property name="java.home"          value="/home/semika/java/jdk1.6.0_30">

Class path:

<path id="project.class.path">
   <pathelement location="${java.home}/../lib/tools.jar" />
   <fileset dir="${jaxws.home}/lib">
        <include name="*.jar" />
   </fileset>
   <pathelement location="${basedir}/WEB-INF/classes" />
   <fileset dir="${basedir}/WEB-INF/lib" includes="*.jar" />
</path>

JAX-WS apt tool target:

<target name="apt" depends="javac">
   <taskdef name="apt" classname="com.sun.tools.ws.ant.Apt">
       <classpath refid="project.class.path" />
   </taskdef>
   <apt fork="true"
        debug="true"
        verbose="true"
        destdir="${basedir}/WEB-INF/classes"
        sourcedestdir="${basedir}/WEB-INF/src"
        sourcepath="${basedir}/WEB-INF/src">
        <classpath>
            <path refid="project.class.path" />
        </classpath>
        <option key="r" value="${basedir}/WEB-INF" />
        <source dir="${basedir}/WEB-INF/src">
            <include name="**/*.java"/>
        </source>
   </apt>
</target>

If you want to know further about apt tool provided by JAX-WS, look into this. When running apt target, it scans the source path (src folder) and generates the required *.class and *.java files for the classes annotated with @WebService. In this case, for 'PharmacyItemServiceEndPoint.java'. If You look into the package where 'PharmacyItemServiceEndPoint' is in, You can see, it has a new package called 'jaxws' created. Inside that package, I could see following three java files. 

DataAccessExceptionBean.java
FindAll.java
FindAllResponse.java

These classes are generated by the tool and it varies based on your service implementation and dependent classes which you are involving to your web service. Actually, you don't need to much worry about these generated stuff.

Similarly, You can see the relevant *.class files under /WEB-INF/classes folder.

JAX-WS wsgen tool target:

<target name="wsgen" depends="apt"> 
    <taskdef name="wsgen" classname="com.sun.tools.ws.ant.WsGen">
         <classpath path="project.class.path"/>
    </taskdef>
  
    <wsgen 
          xendorsed="true"
          sei="com.slauto.service.ws.PharmacyItemServiceEndPoint"
          destdir="${basedir}/WEB-INF/classes"      
          resourcedestdir="${wsdl.dir}"
          sourcedestdir="${basedir}/WEB-INF/src"      
          keep="true"
          verbose="true"
          genwsdl="true">
          <classpath refid="project.class.path"/>
    </wsgen>
</target>

The wsgen tool will generate the WSDL file for our end point web service class. After running this target, have a look on ${wsdl.dir} location. You can see our WSDL file has been generated. If you want to know further about wsgen tool provided by JAX-WS, look into this

Deploying Web service

I wanted to deploy the web service with the usual server start up. SoI had to add the following configuration into the web.xml file.

<listener>
      <listener-class>
              com.sun.xml.ws.transport.http.servlet.WSServletContextListener
      </listener-class>
</listener>
<servlet>
      <servlet-name>pharmacyItemService</servlet-name>
      <servlet-class>
         com.sun.xml.ws.transport.http.servlet.WSServlet
      </servlet-class>
      <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
      <servlet-name>pharmacyItemService</servlet-name>
      <url-pattern>/pharmacyItemService</url-pattern>
</servlet-mapping>

As I told you before when deploying the web service with the end point class extended from "SpringBeanAutowiringSupport", it gives an exception. That is why, I decided to get the service bean via application context. If you manage this situation, please just post it.

And also, You need to create sun-jaxws.xml under the WEB-INF folder and declare the web service end point as follows.

<?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0">    
  <endpoint
      name="pharmacyItemService"
      implementation="com.slauto.service.ws.PharmacyItemServiceEndPoint"
      url-pattern="/pharmacyItemService"/>
</endpoints>

I am using apache tomcat 7.0.25 to deploy the web service. You will need to tell tomcat where it can find JAX-WS libraries when the tomcat is starting up. You can edit the 'catalina.properties' file located in CATALINA_HOME/conf folder. Look for the common.loader property. It will mostly look like follows.

common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar

Modify it as follows.

common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar,/home/semika/jaxws-ri-2.2/lib/*.jar

As You can see, I have added my JAX-WS library path at the end of comma separated list. These classes are made visible to both tomcat internal classes and to all web applications deployed in the tomcat container. Now, You can copy your .war file into tomcat's webapps folder and start the tomcat. Your web service should be deployed. To confirm your web service is deployed properly, You can check for it's WSDL. For WSDL file, You should check the URL in following format.

http://localhost:8080/<your context name>/pharmacyItemService?wsdl

If You can see your WSDL file on the browser, You are done, You have success fully deployed the web service.

Generating Web service client

Now, We have a deployed web service. Next, We will see how to write client class to access the web service with java program. You can create a simple java project and use the following build.xml file there. I have placed the complete build.xml file for client generation. I am using wsimport tool coming with JAX-WS to generate the web service client artefacts.
<?xml version="1.0" encoding="utf-8" ?>
<project name="WS-client" default="wsimport" basedir=".">
 
 <property name="jaxws.home"  value="/home/semika/jaxws-ri-2.2" />
 <property name="java.home" value="/home/semika/java/jdk1.6.0_30"/>
 
 <path id="jaxws.classpath">
  <pathelement location="${java.home}/../lib/tools.jar" />
  <fileset dir="${jaxws.home}/lib">
        <include name="*.jar" />
     </fileset>
 </path>
 
 <target name="wsimport"> 
  <taskdef name="wsimport" classname="com.sun.tools.ws.ant.WsImport">
        <classpath path="jaxws.classpath"/>
  </taskdef>
  <wsimport   xendorsed="true"
              debug="true"
       verbose="true"
              keep="true"
       destdir="src"
       package="com.slauto.service"
       wsdl="http://localhost:8080/slautomanage/pharmacyItemService?wsdl">
  </wsimport>
 </target>
</project>

If You want to know further about wsimport tool, you can look into this. After running the above target, just have a look on the package where you have specified under wsimport attributes. You will see set of java files generated. You can compile and bundle these into a single client.jar file and can be used with any of the java project which need this web service. 

I have created very simple java class to fetch the pharmacy items information through the web service. 

Client.java

package com.slauto.client;

import java.util.List;

import com.slauto.service.PharmacyItem;
import com.slauto.service.PharmacyItemService;
import com.slauto.service.PharmacyItemServiceEndPoint;

/**
 * @author semika
 *
 */
public class Client {

 /**
  * @param args
  */
  public static void main(String[] args) {
  
        PharmacyItemService p = new PharmacyItemService();
        PharmacyItemServiceEndPoint ep = p.getPharmacyItemServiceEndPointPort();
        List<PharmacyItem> pharmacyItems = ep.findAll();
  
        for (PharmacyItem pharmacyItem : pharmacyItems) {
           System.out.println(pharmacyItem.getCode()); 
        }
     }
  }

It is wonderful isn't it ?. You will see list of pharmacy item codes displayed just after running this class. These pharmacy information are coming through web service deployed by a web application developed with heavily complicated environment.
That is it from this tutorial. I hope this will be helpful for you and if you pick something up from this tutorial, don't forget to put a comment. 

Monday, May 14, 2012

Use primitives over boxed primitives whenever you have the choice - Java

Just with the reading of this post's title, some of you may be confused because, some of the java developers have used to use boxed primitives (Wrapper class objects of primitives) without a specific reason. But, We should be care of using primitives rather than the boxed primitives when We have a choice. I will explain Why, We should choose primitives.

There are differences between primitives and boxed primitives though You did not pay attention too much on that.

Primitives are simpler and faster, and they have only their values. Primitives are generally more time- and space-efficient than boxed primitives. But the boxed primitives have different identities though they have same value. Consider the following code which most of the java developers are well known and understandable.

 Integer first = new Integer(41);
 Integer second = new Integer(41);
  
 if (first == second) {
      System.out.println("Equal");  
 }

As You well known, the above code will not print the "Equal" since it performs an identity comparison on the two object references. Applying == operator on boxed primitives always gives wrong output. To fix the above problem, either You can use Integer's equal() method or getting integer values from Integer's intValue() method. Now, You consider the following code.

Integer first = new Integer(41);
Integer second = new Integer(41);
       
if (first > second) {
     System.out.println("First is greater than Second");  
} else {
     System.out.println("First is not greater than Second");
}

Can you spot the output of above code? The above code will print the else condition. In this case, it did not perform the identity comparison like == comparison. It compared the actual values and gave the correct output. So When evaluating this kind of comparison, Integer instances are auto-unboxed;that is, it extracts their primitive values. I know, You have used this kind of comparison plenty of times. Think, You really knew about this. So We must choose primitives or boxed primitives when We have a choice. 

Next, We will move to another difference. You know that, When We declare a variable with primitive type, it always has functional values. But boxed primitives can have 'null' values which sometime can raise exception. Consider the following example.


package com.semika.mail;

public class BoxPrimitiveTest {
 
static Integer x;
static int y;

public static void main(String[] args) {

   if (x == 0) {
        System.out.println("X is Zero");  
   }
  
   if (y == 0) {
        System.out.println("Y is Zero");  
   }
}
}

What will be the output of the above program. The above code will not run and it will throw an exception. As You can see, variable 'x' is declared with boxed primitive type. It has 'null' value when evaluating the x==0 expression. When a null object reference is auto-unboxed,You get a NullPointerException

Next, I will explain the harm of mixing both box primitives and primitives in a single operation. When You mix primitives and boxed primitives in a single operation, the boxed primitive is auto-unboxed which results severe performance problems. Consider the following class.

class LineItem {
 
double price;

public double getPrice() {
    return price;
}

public void setPrice(double price) {
    this.price = price;
}
}

Suppose, You want to iterate list of 'LineItem' and calculate the total value. Note that 'price' variable is declared with a primitive type. Look into the following code of calculating total cost.

Double totoalCost = 0.0; 
  
for (LineItem lItem : lineItemList) {
    totoalCost += lItem.getPrice();
}

The above code makes a server performance problem. Since, You have declared 'totoalCost' variable to be of the boxed primitive type Double instead of primitive type double. The program compiles and run without an error or warning.But the variable is repeatedly boxed and unboxed, causing the observed performance degradation.

Finally, the developer should never ignore the distinction between primitives and boxed primitives. You have to choose what to use.

Thursday, April 26, 2012

How to use jQuery grid with struts 2 without plugin ?

When using jQuery with struts 2, the developers are persuaded to use struts2-jQuery plug-in. Because most of the forums and other Internet resources support struts2 jQuery plug in. I have this experience. I wanted to use jQuery grid plug-in with struts 2,but without using struts2 jQuery plug-in. It was very hard for me to find a tutorial or any good resource to implement struts 2 action class to create the jQuery grid without using struts2 jQuery plug-in. Finally, I got through this by myself and intended to post for your convenience.
This tutorial explains, How to create jQuery grid with struts2 without using the plug-in. I filtered this code out from my existing project. The architecture of the project is based on strts2, spring and hibernate integrated environment. I am sure, You can customise these code so that it suits to your environment.

Step 01: Creating entity class for 'Province' master screen. 

I use JPA as a persistence technology and hibernate data access support given by spring (HibernateDaoSupport). I am not going to explain these stuff in detail. My major concern is, How to create the struts 2 action class that supports jQuery grid. Here is my entity class.

Province.java 

/**
 * 
 */
package com.shims.model.maintenance;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

import org.hibernate.annotations.Cascade;

import com.shims.model.Audited;

/**
 * @author Semika Siriwardana
 *
 */
 @Entity
 @Table(name="PROVINCE") 
 public class Province extends Audited implements Serializable {

 private static final long serialVersionUID = -6842726343310595087L;
 
 @Id
 @SequenceGenerator(name="province_seq", sequenceName="province_seq")
 @GeneratedValue(strategy = GenerationType.AUTO, generator = "province_seq")
 private Long id;
 
 @Column(name="description", nullable = false)
 private String name;
 
 @Column(name="status", nullable = false)
 private char status;
 
 /**
  * 
  */
 public Province() {
      super();
 }

 /**
  * @param id
  */
 public Province(Long id) {
      super();
      this.id = id;
 }

 /**
  * @return the id
  */
 public Long getId() {
      return id;
 }

 /**
  * @param id the id to set
  */
 public void setId(Long id) {
      this.id = id;
 }

 /**
  * @return the name
  */
 public String getName() {
      return name;
 }

 /**
  * @param name the name to set
  */
 public void setName(String name) {
      this.name = name;
 }

 /**
  * @return the status
  */
 public char getStatus() {
      return status;
 }

 /**
  * @param status the status to set
  */
 public void setStatus(char status) {
      this.status = status;
 }
}

Step 02: Creating JSP file for 'Province' master screen grid.

Keep in mind that jQuery grid is a plug-in for jQuery. So You need to download the relevant CSS file and JS file for the jQuery grid plug-in. You may need to include following resources in the head part of the JSP file.

<link type="text/css" rel="stylesheet" media="screen" href="<%=request.getContextPath()%>/css/jquery/themes/redmond/jquery-ui-1.8.16.custom.css">
<link type="text/css" rel="stylesheet" media="screen" href="<%=request.getContextPath()%>/css/ui.jqgrid.css">
<script src="<%=request.getContextPath()%>/js/jquery-1.6.2.min.js" type="text/javascript"></script>
<script src="<%=request.getContextPath()%>/js/grid.locale-en.js" type="text/javascript"></script>
<script src="<%=request.getContextPath()%>/js/jquery.jqGrid.src.js" type="text/javascript"></script>
<script src="<%=request.getContextPath()%>/js/jquery-ui-1.8.16.custom.min.js" type="text/javascript"></script>

Then, We will create the required DOM contents in JSP file to render the grid. For this, You need only a simple TABLE and DIV elements placed with in the JSP file with the given ID's as follows.

<table id="list"></table> 
<div id="pager"></div>

The 'pager' DIV tag is needed to render the pagination bar of the jQuery grid.

Step 03: Creating JS file for 'Province' master screen grid. 

jQuery grid is needed to be initiated with javascript. I am going to initiate the grid with page on load. There are so many functionalities like adding new records, updating records, deleting records, searching supported by jQuery grid. I guess, You can familiar with those stuff, If You can create the initial grid. This javascript contains only the code that initiating the grid.

var SHIMS = {}
var SHIMS.Province = {
 
 onRowSelect: function(id) {
      //Handle event
 },
 
 onLoadComplete: function() {
      //Handle grid load complete event.
 },

 onLoadError: function() {
      //Handle when data loading into grid failed. 
 },

 /**
  * Initialize grid
  */
 initGrid: function(){
  
  jQuery("#list").jqGrid({ 
         url:CONTEXT_ROOT + '/secure/maintenance/province!search.action', 
         id:"gridtable", 
         caption:"SHIMS:Province Maintenance",
         datatype: "json", 
         pager: '#pager', 
         colNames:['Id','Name','Status'], 
         pagerButtons:true,
         navigator:true,
         jsonReader : {
                      root: "gridModel", 
                      page: "page",
                      total: "total",
                      records: "records",
                      repeatitems: false,      
                      id: "0"      
         }, 
         colModel:[ {
                     name:'id',
                     index:'id', 
                     width:200, 
                     sortable:true, 
                     editable:false, 
                     search:true
                    },{
                     name:'name',
                     index:'name', 
                     width:280, 
                     sortable:true, 
                     editable:true, 
                     search:true, 
                     formoptions:{elmprefix:'(*)'},
                     editrules :{required:true}
                    },{
                     name:'provinceStatus',
                     index:'provinceStatus', 
                     width:200, 
                     sortable:false, 
                     editable:true, 
                     search:false, 
                     editrules:{required:true}, 
                     edittype:'select', 
                     editoptions:{value:'A:A;D:D'}
                 }], 
         rowNum:30, 
         rowList:[10,20,30], 
         width:680,
         rownumbers:true,
         viewrecords:true, 
         sortname: 'id', 
         viewrecords: true, 
         sortorder: "desc", 
         onSelectRow:SHIMS.Province.onRowSelect, 
         loadComplete:SHIMS.Province.onLoadComplete,
         loadError:SHIMS.Province.onLoadError,
         editurl:CONTEXT_ROOT + "/secure/maintenance/province!edit.action" 
         });
 },
 
 /**
  * Invoke this method with page on load.
  */
        
 onLoad: function() {
     this.initGrid();
 }
};

I wish to highlight some code snips from the above code. The 'jsonReader' attribute of the grid initialisation object was the key point where it was difficult to find and spent plenty of times to make the grid work.

root - This should be list of objects.
page - Current page number.
total - This is the total number of pages. For example, if You have 1000 records and the page size is 10, the 'total' value will be 100.

records - The total number of records or count of records.

If You are creating grid with JSON data, the response of the specified 'url' should be JSON response in this format. Sample JSON response is shown bellow.

{"gridModel":[
          {"id":15001,"name":"Western","provinceStatus":"A"},
          {"id":14001,"name":"North","provinceStatus":"A"},
          {"id":13001,"name":"North Central","provinceStatus":"A"},
          {"id":12002,"name":"East","provinceStatus":"A"},
          {"id":12001,"name":"Southern","provinceStatus":"A"}
             ],
"page":1,
"records":11,
"rows":30,
"sidx":"id",
"sord":"desc",
"total":2}

Above fields should be declared in the action class and updated appropriately. I will come to that later. If You intend to use operations like add record, delete record, update record, search etc, You must specify 'editurl'.

With in the JSP file, just above the close body tag, I have placed the following script to call the grid initialisation code.

 var CONTEXT_ROOT = "<%=request.getContextPath()%>";
 jQuery(document).ready(function(){
      SHIMS.Province.onLoad();
 }); 
Step 04: Creating Struts2 action class for 'Province' master screen grid. 

This is the most important part of this tutorial.

ProvinceAction.java 

/**
 * 
 */
package com.shims.web.actions.maintenance.province;

import java.util.List;

import org.apache.log4j.Logger;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

import com.opensymphony.xwork2.ModelDriven;
import com.shims.dto.Page;
import com.shims.dto.ProvinceDto;
import com.shims.model.maintenance.Province;
import com.shims.service.maintenance.api.ProvinceService;
import com.shims.support.SHIMSSoringSupport;
import com.shims.web.actions.common.BaseAction;
import com.shims.web.common.WebConstants;

/**
 * @author Semika Siriwardana
 *
 */
 @Controller
 @Namespace(WebConstants.MAINTENANCE_NAMESPACE) 
 @ParentPackage(WebConstants.MAINTENANCE_PACKAGE)
 @Results({@Result(name=ProvinceAction.SUCCESS, location="/jsp/maintenance/province.jsp"), 
       @Result(name = "json", type = "json")}) 
 public class ProvinceAction extends BaseAction<Province> implements ModelDriven<Province> {

 private static final long serialVersionUID = -3007855590220260696L;

 private static Logger logger = Logger.getLogger(ProvinceAction.class);
 
 @Autowired
 private ProvinceService provinceService;
 
 private List<Province> gridModel = null;
 
 private Province model = new Province();
 
 private Integer rows = 0;
 private Integer page = 0;
 private String sord;
 private String sidx;
 private Integer total = 0;
 private Integer records = 0;
 
 @Override
 public String execute() {
      return SUCCESS;
 }
 
 /**
  * Search provinces
  * @return
  */
 public String search() throws Exception {
  
      Page<Province> resultPage = provinceService.findByCriteria(model, getRequestedPage(page));
      List<Province> provinceList = resultPage.getResultList(); 
      setGridModel(provinceList); 
      setRecords(resultPage.getRecords());
      setTotal(resultPage.getTotals());
   
      return JSON;
 }

 /**
  * @return the gridModel
  */
 public List<Province> getGridModel() {
      return gridModel;
 }

 /**
  * @param gridModel the gridModel to set
  */
 public void setGridModel(List<Province> gridModel) {
      this.gridModel = gridModel;
 }

 /**
  * @return the page
  */
 public Integer getPage() {
      return page;
 }

 /**
  * @param page the page to set
  */
 public void setPage(Integer page) {
      this.page = page;
 }

 /**
  * @return the rows
  */
 public Integer getRows() {
      return rows;
 }

 /**
  * @param rows the rows to set
  */
 public void setRows(Integer rows) {
      this.rows = rows;
 }

 /**
  * @return the sidx
  */
 public String getSidx() {
      return sidx;
 }

 /**
  * @param sidx the sidx to set
  */
 public void setSidx(String sidx) {
      this.sidx = sidx;
 }

 /**
  * @return the sord
  */
 public String getSord() {
      return sord;
 }

 /**
  * @param sord the sord to set
  */
 public void setSord(String sord) {
      this.sord = sord;
 }

 /**
  * @return the total
  */
 public Integer getTotal() {
      return total;
 }

 /**
  * @param total the total to set
  */
 public void setTotal(Integer total) {
      this.total = total;
 }

 /**
  * @return the records
  */
 public Integer getRecords() {
      return records;
 }

 /**
  * @param records the records to set
  */
 public void setRecords(Integer records) {
      this.records = records;
 }

 @Override
 public Province getModel() {
      return model;
 }
 
}

The getRequestedPage() method is a generic method implemented with in the 'BaseAction' class which returns a 'Page' instance for a given type. 

BaseAction.java

/**
 * 
 */
package com.shims.web.actions.common;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.interceptor.ServletRequestAware;
import org.apache.struts2.interceptor.SessionAware;

import com.opensymphony.xwork2.ActionSupport;
import com.shims.dto.Page;
import com.shims.dto.security.UserDto;
import com.shims.web.common.WebConstants;

import flexjson.JSONSerializer;

/**
 * @author Semika Siriwardana
 *
 */
 public abstract class BaseAction<T> extends ActionSupport implements ServletRequestAware, SessionAware {

 private static final long serialVersionUID = -8209196735097293008L;
 
 protected static final Integer PAGE_SIZE = 10;

 protected HttpServletRequest request;
 
 protected Map<String, Object> session; 
 
 protected String JSON = "json";
 
 public abstract String execute(); 
 
 public HttpServletRequest getRequest() {
      return request;
 }

 @Override
 public void setServletRequest(HttpServletRequest request) {
      this.request = request;
 }

 protected void setRequestAttribute(String key, Object obj) {
      request.setAttribute(key, obj);
 }
 
 /**
  * Returns generic Page instance.
  * @param domain
  * @param employeeDto
  * @return
  */
 protected Page<T> getRequestedPage(Integer page){
      Page<T> requestedPage = new Page<T>(); 
      requestedPage.setPage(page);
      requestedPage.setRows(PAGE_SIZE);
      return requestedPage;
 }
 
 /**
  * @return the session
  */
 public Map<String, Object> getSession() { 
      return session;
 }

 /**
  * @param session the session to set
  */
 public void setSession(Map<String, Object> session) { 
      this.session = session;
 }
 
}

I already explained 'gridModel', 'page', 'total' and 'records'. 'sord' and 'sidx' which are referenced as 'sorting order' and 'sorting index', are passed by the jQuery grid when We sort the data in the grid with some column. To fetch those tow fields, We should declare it with in the action class and provide setter and getter methods. Later, We can sort our data list based on those tow parameters.

Step 05: Implementing service methods. 

From here onwards, most of the techniques are specific to my current project framework. I will explain those so that You can understand, How I developed the related service and DAO methods.
Since, jQuery grid supports for paging, It was need to have a proper way of exchanging grid information from front-end to back-end and then back-end to front-end. I implemented generic 'Page' class for this purpose.

/**
 * 
 */
package com.shims.dto;

import java.util.ArrayList;
import java.util.List;

/**
 * @author semikas
 *
 */
 public class Page<T> {

 /**
  * Query result list.
  */
 private List<T> resultList = new ArrayList<T>(); 
 
 /**
  * Requested page number.
  */
 private Integer page = 1;
 
 /**
  * Number of rows displayed in a single page.
  */
 private Integer rows = 10;
 
 /**
  * Total number of records return from the query.
  */
 private Integer records;
 
 /**
  * Total number of pages.
  */
 private Integer totals;

 /**
  * @return the resultDtoList
  */
 public List<T> getResultList() {
      return resultList;
 }

 /**
  * @param resultDtoList the resultDtoList to set
  */
 public void setResultList(List<T> resultList) {
      this.resultList = resultList;
 }

 /**
  * @return the page
  */
 public Integer getPage() {
      return page;
 }

 /**
  * @param page the page to set
  */
 public void setPage(Integer page) {
      this.page = page;
 }

 /**
  * @return the rows
  */
 public Integer getRows() {
      return rows;
 }

 /**
  * @param rows the rows to set
  */
 public void setRows(Integer rows) {
      this.rows = rows;
 }

 /**
  * @return the records
  */
 public Integer getRecords() {
      return records;
 }

 /**
  * @param records the records to set
  */
 public void setRecords(Integer records) {
      this.records = records;
 }

 /**
  * @return the totals
  */
 public Integer getTotals() {
      return totals;
 }

 /**
  * @param totals the totals to set
  */
 public void setTotals(Integer totals) {
      this.totals = totals;
 }
 
}

Also, with some search criteria, We can fetch the data and update the grid accordingly.

My service interface and implementation classes are as follows.

ProvinceService.java 

/**
 * 
 */
package com.shims.service.maintenance.api;

import java.util.List;

import com.shims.dto.Page;
import com.shims.exceptions.ServiceException;
import com.shims.model.maintenance.Province;

/**
 * @author Semika Siriwardana
 *
 */
public interface ProvinceService {

 /**
  * Returns list of provinces for a given search criteria.
  * @return
  * @throws ServiceException
  */
  public Page<Province> findByCriteria(Province searchCriteria, Page<Province> page) throws ServiceException;
 
}

ProvinceServiceImpl.java 

/**
 * 
 */
package com.shims.service.maintenance.impl;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.shims.dto.Page;
import com.shims.exceptions.ServiceException;
import com.shims.model.maintenance.Province;
import com.shims.persist.maintenance.api.ProvinceDao;
import com.shims.service.maintenance.api.ProvinceService;


/**
 * @author Semika Siriwardana
 *
 */
 @Service
 public class ProvinceServiceImpl implements ProvinceService {

 @Autowired
 private ProvinceDao provinceDao; 
 
 /**
  * {@inheritDoc} 
  */
 @Override
 public Page<Province> findByCriteria(Province searchCriteria, Page<Province> page) throws ServiceException {
  
      Page<Province> resultPage = provinceDao.findByCriteria(searchCriteria, page); 
  
      return resultPage;
 }

}

I am using 'page' instance to exchange the grid information in between front end and back end.

Next, I will explain the other important part of this tutorial.

Step 06: Implementing data access method. 

In the DAO method, We should filtered only the records of the requested page by the user and also should be updated the 'page' instance attributes so that those should be reflected to the grid. Since, I am using hibernated, I used 'Criteria' to retrieve the required data from the database. You implement this in your way, But it should update the grid information properly.

ProvinceDao.java 
 
/**
 * 
 */
package com.shims.persist.maintenance.api;

import com.shims.dto.Page;
import com.shims.exceptions.DataAccessException;
import com.shims.model.maintenance.Province;
import com.shims.persist.common.GenericDAO;

/**
 * @author Semika Siriwardana
 *
 */
public interface ProvinceDao extends GenericDAO<Province, Long> { 

 /**
  * Returns search results for a given search criteria.
  * @param searchCriteria
  * @param page
  * @return
  * @throws DataAccessException
  */
  public Page<Province> findByCriteria(Province searchCriteria, Page<Province> page) throws DataAccessException;
 
}

ProvinceDaoImpl.java

/**
 * 
 */
package com.shims.persist.maintenance.impl;

import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.shims.dto.Page;
import com.shims.exceptions.DataAccessException;
import com.shims.model.maintenance.Province;
import com.shims.persist.maintenance.api.ProvinceDao;

/**
 * @author Semika Siriwardana
 *
 */
 @Repository
 public class ProvinceDaoImpl extends AbstractMaintenanceDaoSupport<Province, Long> implements ProvinceDao {

 @Autowired
 public ProvinceDaoImpl(SessionFactory sessionFactory) {
      setSessionFactory(sessionFactory);
 }

 /**
  * {@inheritDoc} 
  */
 @SuppressWarnings("unchecked")
 @Override
 public Page<Province> findByCriteria(ProvinceDto searchCriteria, Page<Province> page) throws SHIMSDataAccessException {
  
      Criteria criteria = getSession().createCriteria(Province.class);
  
      if (searchCriteria.getName() != null && searchCriteria.getName().trim().length() != 0) {
          criteria.add(Restrictions.ilike("name", searchCriteria.getName(), MatchMode.ANYWHERE)); 
   
      }
  
      //get total number of records first
      criteria.setProjection(Projections.rowCount());
      Integer rowCount = ((Integer)criteria.list().get(0)).intValue();
  
      //reset projection to null
      criteria.setProjection(null);
  
      Integer to = page.getPage() * page.getRows();
      Integer from = to - page.getRows();
  
      criteria.setFirstResult(from);
      criteria.setMaxResults(to); 
  
      //calculate the total pages for the query
      Integer totNumOfPages =(int) Math.ceil((double)rowCount / (double)page.getRows());
  
      List<Province> privinces = (List<Province>)criteria.list(); 
  
      //Update 'page' instance.
      page.setRecords(rowCount); //Total number of records
      page.setTotals(totNumOfPages); //Total number of pages
      page.setResultList(privinces);
  
      return page; 
 }
}

I think, This will be a good help for the developers who wish to use pure jQuery with struts2. If You find more related to this topic, please post bellow.

Wednesday, March 21, 2012

Java form based authentication

Implementing a login module using JAAS  is an of advance topic and also most of the developers have rare chance of involving with this kind of development. But the basic implementation of JAAS login module is not that much hard implementation.That is because, I intended to post this. Here, I am explaining, how to implement a tomcat managed authentication module. This implementation is not container dependent one. We can use it with any container with slight configuration change. 

As the first step, We need to create a login module class which implements javax.security.auth.spi.LoginModule interface. This interface exposes 5 methods that must be implemented by our login module class. Those are initialize(), login(), commit(), abort(), logout().

initialize() method is passed four arguments .

The 'Subject'  is what, We need to authenticate. The subject can represents the related information for a single login user. It can represent identities like 'username', 'password' etc. And also, it can represents the roles assigned to the user. All these identities should be represent as java.security.Principal. So We should create separate classes to distinguish these entities by implementing  java.security.Principal. In my tutorial, I have created separate classes for username, password and role as JAASUserPrincipal, JAASPasswordPrincipal and JAASRolePrincipal.
Subject's getPrincipals() method returns a set of java.security.Principal associated with the subject. To distinguish these, it is important of Creating separate classes for each identity.

The 'CallbackHandler'  is used to communicate with the user. When authenticating the user by the login module, the login module invokes the handle() method of the CallbackHandler instance to get the user name and password. We do not want to worry about CallbackHandler instance yet. Because the container manages to provide the required callbakcs. The tomcat provides JAASCallbackHandler for this purpose. But, If We want to invoke the authentication explicitly, We need to create our own call back handler class by implementing the javax.security.auth.callback.CallbackHandler. I will explain this at the end of the tutorial.

The next important argument for initialize() method is 'options'. These options where We declare those in 'jass.config' file. With the initialisation of login module, map of options declared in 'jass.config' file are provided. I will explain the 'jaas.config' file of our tutorial later.

Next, I will show the full source code for our principal classes.

JAASUserPrincipal.java

package com.rainyday.server.login;

 import java.io.Serializable;
 import java.security.Principal;

/**
 * @author semika
 *
 */
 public class JAASUserPrincipal implements Principal, Serializable {

 private String name;
 
 /**
  * @param name
  */
 public JAASUserPrincipal(String name) {
  
 if (name == null) {
     throw new NullPointerException("NULL user name");
 }
     this.name = name;
 }
 
 @Override
 public String getName() {
     return name;
 }

 @Override
 public String toString() {
     return "UserPrincipal [name=" + name + "]";
 }

 @Override
 public int hashCode() {
     final int prime = 31;
     int result = 1;
     result = prime * result + ((name == null) ? 0 : name.hashCode());
     return result;
 }

 @Override
 public boolean equals(Object obj) {
     if (this == obj)
        return true;
     if (obj == null)
        return false;
     if (getClass() != obj.getClass())
        return false;
     JAASUserPrincipal other = (JAASUserPrincipal) obj;
     if (name == null) {
        if (other.name != null)
           return false;
     } else if (!name.equals(other.name))
        return false;
  
     return true;
 }
}

JAASRolePrincipal.java

package com.rainyday.server.login;

 import java.io.Serializable;
 import java.security.Principal;

/**
 * @author semika
 *
 */
 public class JAASRolePrincipal implements Principal, Serializable {

 private String name;
 
 /**
  * @param name
  */
 public JAASRolePrincipal(String name) {
     if (name == null) {
        throw new NullPointerException("NULL role name");
     }
     this.name = name;
 }

 @Override
 public String getName() {
    return name;
 }

 @Override
 public String toString() {
     return "JASSRolePrincipal [name=" + name + "]";
 }

 @Override
 public int hashCode() {
     final int prime = 31;
     int result = 1;
     result = prime * result + ((name == null) ? 0 : name.hashCode());
     return result;
 }

 @Override
 public boolean equals(Object obj) {
     if (this == obj)
        return true;
     if (obj == null)
        return false;
     if (getClass() != obj.getClass())
        return false;
     JAASRolePrincipal other = (JAASRolePrincipal) obj;
     if (name == null) {
        if (other.name != null)
           return false;
     } else if (!name.equals(other.name))
        return false;
     
     return true;
 }
}
JAASPasswordPrincipal.java 

package com.rainyday.server.login;

 import java.io.Serializable;
 import java.security.Principal;

/**
 * @author semika
 *
 */
 public class JAASPasswordPrincipal implements Principal, Serializable {

 private String name;
 
 /**
  * @param name
  */
 public JAASPasswordPrincipal(String name) {
     if (name == null) {
        throw new NullPointerException("NULL password.");
     }
     this.name = name;
 }

 @Override
 public String getName() {
     return name;
 }

 @Override
 public int hashCode() {
     final int prime = 31;
     int result = 1;
     result = prime * result + ((name == null) ? 0 : name.hashCode());
     return result;
 }

 @Override
 public boolean equals(Object obj) {
     if (this == obj)
         return true;
     if (obj == null)
         return false;
     if (getClass() != obj.getClass())
         return false;
     JAASPasswordPrincipal other = (JAASPasswordPrincipal) obj;
     if (name == null) {
        if (other.name != null)
           return false;
     } else if (!name.equals(other.name))
        return false;
     
     return true;
 }

 @Override
 public String toString() {
     return "JAASPasswordPrincipal [name=" + name + "]";
 }

}

The above three classes are exactly similar. But, We need to create separate classes for each principal to distinguish them.
The login() method of login module performs the authentication. This validates the user entered user name and password with the database. Now, You may have a problem, How login details entered by the user comes into the login() method. As I explained earlier, the call back handler brings the login identities into the login() method. From the login() method, the login module invokes the handle() method of call back handler by passing the required call backs into it. Then handle() method populates those call backs with the required information and make available to login() method.

The commit() method is invoked by the login module after the successful authentication. The subject can be populated with associated principals. For example, We can retrieve user assigned roles from the database and attached those to the subject.

The next, I will explain the required configurations to work this login module.

We should create 'jass.config' file and should be placed that file under '$CATALINA_HOME/conf'. The 'jass.config' file of this tutorial is as follows.
rainyDay {
   com.rainyday.server.login.JAASLoginModule required
   dbDriver="com.mysql.jdbc.Driver"
   dbURL="jdbc:mysql://localhost/rainyday"
   dbUser="root"
   dbPassword="abc123"
   userQuery="select username from secu_user where secu_user.username=? and secu_user.password=?"
   roleQuery="select secu_user_role.rolename from secu_user, secu_user_role where secu_user.username=secu_user_role.username and secu_user.username=?"
   debug=true;
};

'jass.config' file should have this similar format. In addition to the login module declaration, You can declare options as your wish. These options are made available by login module with 'options' map in initialize() method argument.

Additionally, We should tell the tomcat, Where to locate the 'jaas.config' file by adding it's path to JAVA_OPTS environment variable. I have added this into 'catalina.sh' file under $CATALINA_HOME/bin as follows.

JAVA_OPTS="$JAVA_OPTS -Djava.security.auth.login.config==../conf/jaas.config"

Next, You need to declare the JAASRealm configurations. You can add a new 'Realm' entry into the server.xml file under $CATALINA_HOME/conf. In our tutorial, the 'Realm' entry is as follows.

<Realm className="org.apache.catalina.realm.JAASRealm"
                appName="rainyDay"
                userClassNames="com.rainyday.server.login.JASSUserPrincipal,com.rainyday.server.login.JAASPasswordPrincipal"
                roleClassNames="com.rainyday.server.login.JASSRolePrincipal"/>


For apache tomcat's realm configuration, You can view this documentation. The complete source code for our jaas login module.

JAASLoginModule.java

package com.rainyday.server.login;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

import org.apache.log4j.Logger;

/**
 * @author semika
 *
 */
public class JAASLoginModule implements LoginModule { 
 
    private static Logger LOGGER = Logger.getLogger(JAASLoginModule.class); 
 
    // initial state
    private Subject subject;
    private CallbackHandler callbackHandler;
    private Map sharedState;
    private Map options;

    // configurable option
    private boolean debug = false;
    
    // the authentication status
    private boolean succeeded = false;
    private boolean commitSucceeded = false;
    
    //user credentials
    private String username = null;
    private char[] password = null;
    
    //user principle
    private JAASUserPrincipal userPrincipal = null;
    private JAASPasswordPrincipal passwordPrincipal = null;
    
    public JAASLoginModule() {
         super();
    }

    @Override
    public void initialize(Subject subject, CallbackHandler callbackHandler,
                Map<string, ?=""> sharedState, Map<string, ?=""> options) {
        this.subject = subject;
        this.callbackHandler = callbackHandler;
        this.sharedState = sharedState;
        this.options = options;
  
        debug = "true".equalsIgnoreCase((String)options.get("debug")); 
    }

    @Override
    public boolean login() throws LoginException {
  
        if (callbackHandler == null){
            throw new LoginException("Error: no CallbackHandler available " +
            "to garner authentication information from the user");
        }
        Callback[] callbacks = new Callback[2];
        callbacks[0] = new NameCallback("username");
        callbacks[1] = new PasswordCallback("password: ", false);
  
        try {
   
            callbackHandler.handle(callbacks);
            username = ((NameCallback)callbacks[0]).getName();
            password = ((PasswordCallback)callbacks[1]).getPassword();
   
            if (debug) {
                LOGGER.debug("Username :" + username);
                LOGGER.debug("Password : " + password);
            }
   
            if (username == null || password == null) {
                LOGGER.error("Callback handler does not return login data properly");
                throw new LoginException("Callback handler does not return login data properly"); 
            }
   
            if (isValidUser()) { //validate user.
                succeeded = true;
                return true;
            } 
   
        } catch (IOException e) { 
             e.printStackTrace();
        } catch (UnsupportedCallbackException e) {
             e.printStackTrace();
        }
  
        return false;
    }

    @Override
    public boolean commit() throws LoginException {
        if (succeeded == false) {
            return false;
        } else { 
            userPrincipal = new JAASUserPrincipal(username);
            if (!subject.getPrincipals().contains(userPrincipal)) {
                subject.getPrincipals().add(userPrincipal);
                LOGGER.debug("User principal added:" + userPrincipal);
            }
            passwordPrincipal = new JAASPasswordPrincipal(new String(password)); 
            if (!subject.getPrincipals().contains(passwordPrincipal)) {
                subject.getPrincipals().add(passwordPrincipal);
                LOGGER.debug("Password principal added: " + passwordPrincipal);
            }
      
            //populate subject with roles.
            List<string> roles = getRoles();
            for (String role: roles) {
                JAASRolePrincipal rolePrincipal = new JAASRolePrincipal(role);
                if (!subject.getPrincipals().contains(rolePrincipal)) {
                    subject.getPrincipals().add(rolePrincipal); 
                    LOGGER.debug("Role principal added: " + rolePrincipal);
                }
            }
      
            commitSucceeded = true;
      
            LOGGER.info("Login subject were successfully populated with principals and roles"); 
      
            return true;
       }
   }

   @Override
   public boolean abort() throws LoginException {
      if (succeeded == false) {
          return false;
      } else if (succeeded == true && commitSucceeded == false) {
          succeeded = false;
          username = null;
          if (password != null) {
              password = null;
          }
          userPrincipal = null;    
      } else {
          logout();
      }
      return true;
   }

    @Override
    public boolean logout() throws LoginException {
        subject.getPrincipals().remove(userPrincipal);
        succeeded = false;
        succeeded = commitSucceeded;
        username = null;
        if (password != null) {
            for (int i = 0; i < password.length; i++){
                password[i] = ' ';
                password = null;
            }
        }
        userPrincipal = null;
        return true;
   }
 
   private boolean isValidUser() throws LoginException {

      String sql = (String)options.get("userQuery");
      Connection con = null;
      ResultSet rs = null;
      PreparedStatement stmt = null;
  
      try {
          con = getConnection();
          stmt = con.prepareStatement(sql);
          stmt.setString(1, username);
          stmt.setString(2, new String(password));
   
          rs = stmt.executeQuery();
   
          if (rs.next()) { //User exist with the given user name and password.
              return true;
          }
       } catch (Exception e) {
           LOGGER.error("Error when loading user from the database " + e);
           e.printStackTrace();
       } finally {
           try {
               rs.close();
           } catch (SQLException e) {
               LOGGER.error("Error when closing result set." + e);
           }
           try {
               stmt.close();
           } catch (SQLException e) {
               LOGGER.error("Error when closing statement." + e);
           }
           try {
               con.close();
           } catch (SQLException e) {
               LOGGER.error("Error when closing connection." + e);
           }
       }
       return false;
   }

 /**
  * Returns list of roles assigned to authenticated user.
  * @return
  */
  private List<string> getRoles() { 
  
      Connection con = null;
      ResultSet rs = null;
      PreparedStatement stmt = null;
  
      List<string> roleList = new ArrayList<string>(); 
  
      try {
          con = getConnection();
          String sql = (String)options.get("roleQuery");
          stmt = con.prepareStatement(sql);
          stmt.setString(1, username);
   
          rs = stmt.executeQuery();
   
          if (rs.next()) { 
              roleList.add(rs.getString("rolename")); 
          }
      } catch (Exception e) {
          LOGGER.error("Error when loading user from the database " + e);
          e.printStackTrace();
      } finally {
           try {
               rs.close();
           } catch (SQLException e) {
               LOGGER.error("Error when closing result set." + e);
           }
           try {
               stmt.close();
           } catch (SQLException e) {
               LOGGER.error("Error when closing statement." + e);
           }
           try {
               con.close();
           } catch (SQLException e) {
               LOGGER.error("Error when closing connection." + e);
           }
       }
       return roleList;
 }
 
 /**
  * Returns JDBC connection
  * @return
  * @throws LoginException
  */
  private Connection getConnection() throws LoginException {
  
      String dBUser = (String)options.get("dbUser");
      String dBPassword = (String)options.get("dbPassword");
      String dBUrl = (String)options.get("dbURL");
      String dBDriver = (String)options.get("dbDriver");

      Connection con = null;
      try {
         //loading driver
         Class.forName (dBDriver).newInstance();
         con = DriverManager.getConnection (dBUrl, dBUser, dBPassword);
      } 
      catch (Exception e) {
         LOGGER.error("Error when creating database connection" + e);
         e.printStackTrace();
      } finally {
      }
      return con;
   }
}

The abort() method of the login module will be invoked if something went wrong within the login() or commit() method execution. In this kind of situation, We can not say that the authentication process was successfully completed and the required clean up operations can be done within the abort() method, if the authentication process encountered a failure.

We can utilise the 'options' map which was initialise within the initialize() method of the login module to get the configuration information declared within the 'jass.config' file. You can come up with a good technique to get JDBC connection object. I did not concentrate on that with in this tutorial and only wanted to show you the mechanism, How the things should be done.
By now, We have completed the thing which are required for basic JAAS authentication module. Next, We should configure our security constraints in web.xml file.
  
<login-config>
     <auth-method>FORM</auth-method>
     <realm-name>rainyDay</realm-name>
     <form-login-config>
          <form-login-page>/login.jsp</form-login-page>
          <form-error-page>/error.jsp</form-error-page>
     </form-login-config>
</login-config>
<security-role>
    <role-name>*</role-name>
</security-role>
<security-constraint>
    <web-resource-collection>
         <web-resource-name>Rainy day</web-resource-name>
         <url-pattern>/</url-pattern>
         <http-method>POST</http-method>
         <http-method>GET</http-method>
    </web-resource-collection>
    <auth-constraint>
         <role-name>*</role-name>
    </auth-constraint>
</security-constraint>

With the above security constraints, if some request comes to a particular resource in the protected area of the application with out the authentication, the request will be redirected to the 'login' page. Next, I will show you the simple HTML form which invokes our login module with the submission of the form.

 <form id="loginForm" name="loginForm" method="post" action="j_security_check">
        User Name : <input id="username" type="text" name="j_username" class="textbox"></input>
        Password : <input id="password" type="password" name="j_password" class="textbox"></input>
        <input name="login" type="submit" value="LOGIN" id="submit" class="button blue">
 </form>

So, We are done with that.This is very basic implementation of JAAS. The advantage of this kind of JAAS module is, We can switch to a different login module implementation just with a single configuration change and without doing any modification to our existing code. And also this is container independent. If You want to deploy this with jBoss server, instead of 'jass.config', You can use 'login-config.xml' file in jboss conf folder. As I promised you to explain, How to invoke this kind of login module explicitly, here it is. There are some circumstances, We need to authenticate a particular user with pragmatically, but still We should use our implemented login module. In this kind of situation, the big problem is providing user identities (user name, password etc) to the our login module. In the above case, We used a 'CallbackHandler' class which is 'JAASCallbackHander' provided by apache catalina. But, Here We can not use and We have to implement our own call back handler class.

JAASCallbackHandler.java
package com.rainyday.server.login;

import java.io.IOException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.log4j.Logger;

/**
 * @author semika
 *
 */
 public class JAASCallbackHandler implements CallbackHandler {

 private static final Logger LOGGER = Logger.getLogger(JAASCallbackHandler.class);
 
 private String username = null;
 private String password = null;
 
 /**
  * @param username
  * @param password
  */
 public JAASCallbackHandler(String username, String password) {
     this.username = username;
     this.password = password;
 }


 @Override
 public void handle(Callback[] callbacks) throws IOException,
   UnsupportedCallbackException {
  
     LOGGER.info("Callback Handler invoked ");
  
     for (int i = 0; i < callbacks.length; i++) {
        if (callbacks[i] instanceof NameCallback) {
           NameCallback nameCallback = (NameCallback) callbacks[i];
           nameCallback.setName(username);
        } else if (callbacks[i] instanceof PasswordCallback) {
           PasswordCallback passwordCallback = (PasswordCallback) callbacks[i];
           passwordCallback.setPassword(password.toCharArray());
        } else {
           throw new UnsupportedCallbackException(callbacks[i], "The submitted Callback is unsupported");
        }
     }
 }
}

Next, We have to create an instance of 'LoginContext' to invoke the authentication explicitly.

    LoginContext lc = null;
    try {
        lc = new LoginContext("rainyDay", new JAASCallbackHandler(username, password));
        lc.login();
        //get the subject.
        Subject subject = lc.getSubject();
        //get principals
        subject.getPrincipals();
        LOGGER.info("established new logincontext");
    } catch (LoginException e) {
        LOGGER.error("Authentication failed " + e);
    } 
If We end up with the execution of above code without any exception, that implies the authentication was succeeded. If an exception was encountered, authentication has failed. 

That's all from this tutorials. http://docs.oracle.com/javaee/1.4/tutorial/doc/Security5.html was a good tutorial for me to understand login authentication.
Share

Widgets