Pages

Friday, November 14, 2014

Create and Invoke a JSR-352 Batchlet Using EJB Timer

Batch Processing is referred to processing a series of jobs without manual intervention. These are in contrast with OLTP processes that require some input from the users to initiate a process. All input parameters are predefined through scripts, job control language control files, etc. These tasks (jobs) often process large amounts of data from a range of sources.

Java EE Batch Processing Framework (JSR-352) provides the batch execution infrastructure common to all batch applications. This enables us, the developers, to concentrate on the business logic of these batch processes. The batch framework consists of a job specific XML based language, as set of batch annotations and some interfaces to implement the business logic, a batch container that manages the bath jobs and a set of APIs to interact with the batch container.

In this first JSR-352 post, I will concentrate on the Batchlet. I have chosen this because this is the simplest way of getting up and running with the JSR-352 Batch Processing Framework. We will use a Timer to trigger the Batchlet and a RESTful Web Service to create the Timer that will trigger the Batchlet. Let's start with the Service first.

This is how my the service looks like:

package com.mybatchproject;


import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import javax.ejb.EJB;
import javax.ejb.Timer;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
 * http://localhost:8080/BatchJobServer/resource/test/timerservice
 * 
 * @author Raza Abidi
 *
 */
@Path("test")
public class BatchJobService {
 
 @EJB
 MyEJBTimer timerEJB;
 
 
 @GET
 @Path("/timerservice")
 @Produces(MediaType.TEXT_XML)
 public TimerInfo generateTimeStamp() {
  
  Date schedule;
  
  try {
   // Add a delay of one minute
   Calendar cl = new GregorianCalendar();
   cl.add(Calendar.MINUTE, 1);
   schedule = cl.getTime();
  } catch (Exception e) {
   e.printStackTrace();
   return null;
  }
  
  TimerInfo to = new TimerInfo();
  to.setJobName("my-test-batch-job"); // same as the xml name
  to.setJobSchedule(schedule);
  to.setJobMessage("Job scheduled sucessfully");
  
  Timer timer;
  try{
   timer = timerEJB.createNewTimer(schedule, to);
  }catch(Exception e){
   to.setJobMessage("Failed to create timer");
   e.printStackTrace();
   return to;
  }
  
  to = (TimerInfo) timer.getInfo();
  
  System.out.println("Timer created successfully: " + to);
  
  return to;
 }
}

All we are doing here is preparing a simple POJO TimerInfo and populating it with some data. We are then creating a simple Timer using the MyEJBTimer helper EJB and saving the POJO in the Timer. This is to illustrate that a Timer can contain data that can be used when the Timer expires. It will make more sense later on.

This is what the POJO TimerInfo looks like:


package com.mybatchproject;

import java.io.Serializable;
import java.util.Date;

import javax.xml.bind.annotation.XmlRootElement;

/**
 * @author Raza Abidi
 * @date 13 Nov 2014 15:45:22
 */
@XmlRootElement(name="TimerInfo")
public class TimerInfo implements Serializable{
 
 private static final long serialVersionUID = -6478588792874137803L;
 
 private String jobName;
 private Date jobSchedule;
 private String jobMessage;
 
 @Override
 public String toString() {
  
  StringBuilder sb = new StringBuilder();
  sb.append("\n").append("Name: ").append(jobName);
  sb.append("\n").append("Schedule: ").append(jobSchedule);
  sb.append("\n").append("Message: ").append(jobMessage).append("\n");
  
  return sb.toString();
  
 }
 
 public String getJobName() {
  return jobName;
 }

 public void setJobName(String jobName) {
  this.jobName = jobName;
 }

 public Date getJobSchedule() {
  return jobSchedule;
 }

 public void setJobSchedule(Date jobSchedule) {
  this.jobSchedule = jobSchedule;
 }

 public String getJobMessage() {
  return jobMessage;
 }

 public void setJobMessage(String jobMessage) {
  this.jobMessage = jobMessage;
 }

}

Once the POJO is populated with data then we use the timerEJB.createNewTimer(schedule, to); method of our helper EJB to create a timer. The method takes two parameters, schedule and info. The schedule is a date object representing the date/time when the timer is scheduled to expire and the info is an instance of TimerInfo class that contains some necessary data that we can retrieve from the timer when it gets expired. Note that we are already creating the Scheduled Object with a delay of 1 minute. This timer will expire one minute after it is created.

Let’s see what the MyEJBTimer helper class looks like:

package com.mybatchproject;

import java.util.Date;

import javax.annotation.Resource;
import javax.ejb.Singleton;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerService;


/**
 * @author Raza Abidi
 * @date 13 Nov 2014 15:36:58
 */
@Singleton
public class MyEJBTimer {

 @Resource 
 private TimerService ts;

 public Timer createNewTimer(Date date, TimerInfo timerInfo) {
  
  Timer timer = ts.createTimer(date, timerInfo);
  return timer;
  
 }
 
 @Timeout
 public void timerExpired(Timer timer){
  
  System.out.println("Timer Expired @ :" + new Date());
  
  TimerInfo to = (TimerInfo) timer.getInfo();
  
  to.setJobMessage("Timer Executed Successfully");
  
  System.out.println("Timer Info: " + to);
  
  BatchJobHandler jobHandler = new BatchJobHandler();
  
  jobHandler.startJob(to);
  
 } 
}

This is a simple EJB providing the implementation of what needs to be done when a Timer gets expired. The TimerService is injected as a Resource in this EJB and used by the two methods to create and use Timers created using the resource.

The first method createNewTimer(Date date, TimerInfo timerInfo) is the one called by the RESTFul service to create a Timer. This takes two parameters and uses the TimerService resource to create the Timer in the system. Upons successful creation of a Timer it will return the Timer object to the caller.

The second method timerExpired(Timer timer) annotated with the @Timeout annotation is the one that gets triggered when a timer gets expired. This is where we have implemented the logic to trigger the Batchlet using the BatchJobHandler helper class. Let’s see that that class looks like.

package com.mybatchproject;

import java.util.Properties;

import javax.batch.operations.JobOperator;
import javax.batch.runtime.BatchRuntime;

/**
 * @author Raza Abidi
 * @date 13 Nov 2014 15:40:11
 */
public class BatchJobHandler {

 public void startJob(TimerInfo to) {
  
  String jobName = to.getJobName();
  
  JobOperator jobOperator = BatchRuntime.getJobOperator();
  
  Properties jp = new Properties();
  
  long executionId = jobOperator.start(jobName, jp);
  
  System.out.println("Job Started: " + jobName + "  Execution ID:" + executionId);
 }
}

Here we are getting a reference to the JobOperator from the BatchRuntime and then using the jobOperator.start(jobName, jp); method to invoke the Batch Process. The method returns an Execution ID which is the ID of that Job and can be used later to pause, stop, restart the job using the methods provided by the Batch API.

Note that we used the TimerInfo object to get the name of the job that we want to start.

Now we need to create the actual Batch Job. All Batch Jobs are defined in XML and they must be located inside the META-INF/batch-jobs folder of your application server. I created an XML document to describe my Batch Job at:

 META-INF/batch-jobs/my-test-batch-job.xml

Notice the name of the file, it is exactly the same as what we passed to the to.setJobName("my-test-batch-job");. The Batch Runtime uses the name of the XML file to identify whcih jobs it needs to start when you invoke the jobOperator.start(jobName, jp); method.

This is what the my-test-batch-job.xml file contains:


<?xml version="1.0" encoding="UTF-8"?>
<job id="my-test-batch-job" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
 
 <step id="my-test-batch-step">
  <batchlet ref="MyBatchJobBatchlet"/>
 </step>
 
</job>

Now we need to create a class that represents the Batch Job. This class must implement the Batchlet Interface and provide the implementation of at least the process() method. This how my class looks like:


package com.mybatchproject;

import javax.batch.api.Batchlet;
import javax.enterprise.context.Dependent;
import javax.inject.Named;

/**
 * @author Raza Abidi
 * @date 13 Nov 2014 15:50:56
 */
@Dependent
@Named("MyBatchJobBatchlet")
public class MyBatchJobBatchlet implements Batchlet {

 @Override
 public String process() throws Exception {
  
  System.out.println("Starting the Batch Job");
  
  for (int i = 0; i < 5; i++) {
   
   
   try{
    Thread.sleep(1000);
   }catch(Exception e){
    e.printStackTrace();
   }
   
   System.out.println("Processing the Batch Job : " + i);
   
  }
  
  System.out.println("Finished the Batch Job");
  
  
  return null;
 }

 @Override
 public void stop() throws Exception {
 }
}


Notice the @Named annotation and the batchlet tag in the XML file. Both of these are referring to the same name, i.e., MyBatchJobBatchlet to bind the job definition with the job.

This Batchlet is not doing anything useful really; it is simply printing a message and then waiting for one second before printing the next message in a loop that will iterate 5 times. This is only to illustrate that the jobs will be ruining for a long period of time without any manual intervention.

When you type in the URL for this service ina browser, you will get teh following response:

<?xml version="1.0" encoding="UTF-8" standalone="true"?>
<TimerInfo>
 <jobMessage>Job scheduled sucessfully</jobMessage>
 <jobName>my-test-batch-job</jobName>
 <jobSchedule>2014-11-13T17:52:16.475Z</jobSchedule>
</TimerInfo>

And this is what you should see on the console output of your Application Server.

INFO: Initiating Jersey application, version Jersey: 2.0 2013-05-14 20:07:34...
INFO: Timer created successfully: 
Name: my-test-batch-job
Schedule: Thu Nov 13 17:22:24 GMT 2014
Message: Job scheduled sucessfully
INFO: Timer Expired @ :Thu Nov 13 17:22:24 GMT 2014
INFO: Timer Info: 
Name: my-test-batch-job
Schedule: Thu Nov 13 17:22:24 GMT 2014
Message: Timer Executed Successfully
INFO: Job Started: my-test-batch-job  Execution ID:48
INFO: Starting the Batch Job
INFO: Processing the Batch Job : 0
INFO: Processing the Batch Job : 1
INFO: Processing the Batch Job : 2
INFO: Processing the Batch Job : 3
INFO: Processing the Batch Job : 4
INFO: Finished the Batch Job

As you can see, the service created a Timer and sent a response back to the browser streight away. After 1 minute, the timer expired and started the Batch Job.

Last but not least, if you are using Maven to build your project then you need to add the Maven dependency for JSR-352 to our Java EE project.

<dependency>
 <groupId>javax.batch</groupId>
 <artifactId>javax.batch-api</artifactId>
 <version>1.0</version>
 <scope>provided</scope>
</dependency>

If you are using Glassfish as your application server then you can use the admin console to view the Bath Jobs executing in the server. You can view them under the "Monitoring Data" area and then the "Batch" tab on the Monitoring Data screen. At the moment you can only view the status of teh jobs running in the system, btu hopefully in later versions there will be options to interact with the jobs.

You can also use the methods ptovided by the Batch Runtime API to interact with the batch runtime and provide your own implementation of the admin interface to interact with the Bath Jobs if you need to. It is not as diffcult as it sounds really. If I manage to find some time, I may write another post on how to create a management interface for the Batch Runtime. :)

Monday, November 3, 2014

Configuring Glassfish 4.1 as a Windows Service

Previously I did publish a post on how to install Glassfish V2 as a Windows service a few years ago. Things have moved on quite a bit since then and now the latest version of the server comes with a built in command to configure the domain as a windows service.

There are a few short comings though. The command does not fully leverage the windows services and you still have to configure a few things after creating the service. Well, without further ado, let’s start installing the service and you will soon find out what I meant by “shortcomings”.

First thing first, we need to make sure the Glassfish is working properly even before we think about starting to configure that as a service. Use the asadmin prompt to verify that you can start-stop the sample domain and that everything is working fine.

NOTE: You need admin privileges to install the windows service.

Now open a new command prompt as administrator and type in the following command.

asadmin create-service domain1
The Windows Service was created successfully.  It is ready to be started.  Here are the details:
ID of the service: domain1
Display Name of the service:domain1 GlassFish Server
Server Directory: C:\glassfish4\glassfish\domains\domain1
Configuration file for Windows Services Wrapper: C:\glassfish4\glassfish\domains\domain1\bin\domain1Service.xml
The service can be controlled using the Windows Services Manager 
or you can use the Windows Services Wrapper instead:
Start Command:  C:\glassfish4\glassfish\domains\domain1\bin\domain1Service.exe  start
Stop Command:   C:\glassfish4\glassfish\domains\domain1\bin\domain1Service.exe  stop
Restart Command:  C:\glassfish4\glassfish\domains\domain1\bin\domain1Service.exe  restart
Uninstall Command:  C:\glassfish4\glassfish\domains\domain1\bin\domain1Service.exe  uninstall
Install Command:  C:\glassfish4\glassfish\domains\domain1\bin\domain1Service.exe  install
Status Command: C:\glassfish4\glassfish\domains\domain1\bin\domain1Service.exe status
You can also verify that the service is installed (or not) with sc query state= all
windows.services.uninstall.good=Found the Windows Service and successfully uninstalled it.
For your convenience this message has also been saved to this file: C:\glassfish4\glassfish\domains\domain1\PlatformServices.log
Command create-service executed successfully.

This command will create a service like a breeze. However, the description of the service created using this command is not very descriptive. Problem here is that if you have more than a few services installed on the system, especially if you are looking to install multiple domains in one server then these descriptions are really not good enough.

Fortunately the command created a few exe files and a configuration file that we can use to install, uninstall the service at will. These files are located in the bin folder of the domain. The configurations of the service are stored in domain1Service.xml which looks like below for me.


<service>
  <id>domain1</id>
  <name>domain1 GlassFish Server</name>
  <description>GlassFish Server</description>
  <executable>C:/glassfish4/glassfish/lib/nadmin.bat</executable>
  <logpath>C:\\glassfish4\\glassfish\\domains/domain1/bin</logpath>
  <logmode>reset</logmode>
  <depend>tcpip</depend>
  <startargument>start-domain</startargument>
  <startargument>--watchdog</startargument>
  <startargument>--domaindir</startargument>
  <startargument>C:\\glassfish4\\glassfish\\domains</startargument>
  <startargument>domain1</startargument>
  <stopargument>stop-domain</stopargument>
  <stopargument>--domaindir</stopargument>
  <stopargument>C:\\glassfish4\\glassfish\\domains</stopargument>
  <stopargument>domain1</stopargument>
</service>

We are only interested in the following tags as they are the ones displayed in the Windows Services Manager

<service>
  <name>domain1 GlassFish Server</name>
  <description>GlassFish Server</description>
</service>

We simply need to update these two parameters and give our domain a more descriptive display name and description. I updated the values as follows and save the file.

<service>
  <name>GF Domain1</name>
  <description>Windows service for the GlassFish Domain1 domain</description>
</service>

After saving the file, you now need to uninstall the existing service and then install the service again using the domain1Service.exe file that got created in the previous step. Open a command prompt, go to the bin directory, and run the following commands:

C:\> cd \glassfish4\glassfish\domains\domain1\bin
C:\glassfish4\glassfish\domains\domain1\bin> domain1Service.exe uninstall
C:\glassfish4\glassfish\domains\domain1\bin> domain1Service.exe install
C:\glassfish4\glassfish\domains\domain1\bin> 

You now have your Glassfish 4.1 domain configured as a Windows service with a name and description of your choice.

Thursday, April 17, 2014

Securing RESTful APIs with HTTP Basic Authentication

HTTP Basic Authentication is the simplest way for a HTTP User Agent to provide a username and password to the web server to enforce access control of the resources. The Basic Authentication method provides no confidentiality and the credentials are transmitted as merely Base64 encoded string. Therefore, this method of authentication is typically used over HTTPS for added security.

The user credentials are sent using the Authorization header. The header is constructed as follows:

  • Combine username and password into a string “username:password”
  • Encode the resulting string in a Base64 variant
  • Prefix the encoded string with “Basic ” ; notice the space here
These encoded credentials are then sent over to the server. On the server side you can then extract these credentials and use them to authenticate the user.

In this example, I shall create a very simple RESTful web service and a very simple java client that will call this restful web service with an HTTP Authorization header. Let's start with creating a RESTful web resource that extracts the authentication data from the HTTP Header and returns the decoded credentials as simple text back to the client.


package com.test.service;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

import sun.misc.BASE64Decoder;

@Path("auth")
public class TestHTTPAuthService {
 
 @Context
 private HttpServletRequest request;
 
 @GET
 @Path("basic")
 @Produces(MediaType.TEXT_PLAIN)
 public String authenticateHTTPHeader(){
  
  String decoded;
  try{
   // Get the Authorisation Header from Request
   String header = request.getHeader("authorization");
   
   // Header is in the format "Basic 3nc0dedDat4"
   // We need to extract data before decoding it back to original string
   String data = header.substring(header.indexOf(" ") +1 );
   
   // Decode the data back to original string
   byte[] bytes = new BASE64Decoder().decodeBuffer(data);
   decoded = new String(bytes);
   
   System.out.println(decoded);
   
  }catch(Exception e){
   e.printStackTrace();
   decoded = "No/Invalid authentication information provided";
  }
  
  return decoded;
 }
}

We know the format of data in the authorization header and thus we can extract the encoded part of the data and then decode it to get the credentials. This service is simply returning the decoded credentials back to the caller.

Now let’s create a simple java class that will call this service. We will send the username and password in the HTTP Header and printout the result from the service.


package servlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

import sun.misc.BASE64Encoder;

public class BasicHTTPAuthentication {

 public static void main(String[] args) {

  try {
   
   String webPage = "http://localhost:8080/TESTService/resource/auth/basic";
   String name = "Aladdin";
   String password = "Open Sesame";

   String authString = name + ":" + password;
   System.out.println("Auth string: " + authString);
   
   String authStringEnc = new BASE64Encoder().encode(authString.getBytes());
   System.out.println("Base64 encoded auth string: " + authStringEnc);

   URL url = new URL(webPage);
   URLConnection urlConnection = url.openConnection();
   urlConnection.setRequestProperty("Authorization", "Basic " + authStringEnc);
   InputStream is = urlConnection.getInputStream();
   InputStreamReader isr = new InputStreamReader(is);

   int numCharsRead;
   char[] charArray = new char[1024];
   StringBuffer sb = new StringBuffer();
   while ((numCharsRead = isr.read(charArray)) > 0) {
    sb.append(charArray, 0, numCharsRead);
   }
   String result = sb.toString();

   System.out.println("---------------------------------------------");
   System.out.println("Response from the server: " + result);
   
  } catch (MalformedURLException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}

Here we are simply converting and encoding the credentials according to the HTTP guidelines. After that we are using the setRequestProperty() to add these values to an authorization header. Once the header is set, we then send this request to the service which can extract the credentials form the request and sends them bac as a response.

Below is the console output when we run this code:


Auth string: Aladdin:Open Sesame
Base64 encoded auth string: QWxhZGRpbjpPcGVuIFNlc2FtZQ==
---------------------------------------------
Response from the server: Aladdin:Open Sesame

The service side of the code is simply extracting the credentials from the HTTP Header and writing them in the log file before generating a response, which is also nothing more than the decoded credentials.

Below is an extract from the log files showing the entry logged by this service:

[glassfish 4.0] [INFO] [tid: _ThreadID=22 _ThreadName=Thread-3] [[Aladdin:Open Sesame]]

This is a very simple example to show the HTTP Basic Authentication using the HTTP Authorization headers. This is not a recommended approach by any means. You can clearly see more than a few flaws in this approach, not to mention how easy it is to decode the credentials.

However, simply adding the added security of HTTPS over TLS, you significantly improve the level of security of this simple authentication mechanism.

Another improvement would be to use Servlet Filters instead of authentication credentials in every service/resource. You can intercept the request to a resource and authenticate the user even before it reaches anywhere near the resource. Feel free to browse my previous post on Using Servlet Filters for a demonstration on how the servlet filters can be used to intercept request and responses.

Thursday, April 10, 2014

Using ServletFilter to authenticate a user's Session

Servlet Filters are interceptors for requests and responses to transform or use the information in the request or response. Filters normally do not create a response themselves, instead, they provide global functions that can be attached to any web resources.

Filters are very important for a number of reasons. First of all, they provide a modular way to create units of functionality that can be reused, and secondly they can be used to transform the data in request or response.

Filters can perform manay different types of functions, including but not limited to :

  • Authenticating the requests
  • Logging and auditing
  • Data Transformation
  • Data Compression
  • Localization
In this example I am going to try to explain Filters by developing a simple filter that verifies that the user still has a valid session. If the session is expired then the Filter will block the request from going any further and redirect the user to the login page.

Let’s base our Filter on the following assumptions

  • We have a session bean called UserBean
  • The bean is stored in the session as user
  • The login page authenticates the user
  • The login page creates and stores a UserBean in session
  • We will always have a user variable in session
Based on the following assumptions, all we need to do is to make sure that we have an instance of UserBean in the session variable user with some valid user credentials.

This is what my Servlet Filter looked like when I first created it. All we need to do is to implement the Filter interface and override the doFilter() method. Whatever we want to do with that filter would be provided in this method.


public class SessionFilter implements Filter {

 @Override
 public void doFilter(ServletRequest req, ServletResponse res,
   FilterChain chain) throws IOException, ServletException {

  String sessionExpired = "/mywebproject/sessionExpired.html";

  try {

   HttpServletRequest request = (HttpServletRequest) req;
   HttpServletResponse response = (HttpServletResponse) res;

   HttpSession session = request.getSession(false);
   if(null == session){
   
    request.getRequestDispatcher("/sessionExpired.html").forward(request, response);
    
   }else{
   
    UserBean ub = (UserBean)session.getAttribute("user");
    if(null == ub){
    
     response.sendRedirect(sessionExpired);
     
    }else if (ub.getUserID() == null){
    
     response.sendRedirect(sessionExpired);
    }
   }

  } catch (Exception f) {
   f.printStackTrace();
  }
  chain.doFilter(req, res);
 }
}

Here we are simply checking for the session if we have a valid UserBean object in the session. If we have an object in the session then we validate the object if it contains a user id. If everything is fine then we continue the process and pass the process flow to the next filter in the chain. If not then redirect the control to the session expired page to let the users know that the session is no longer valid and they have to login to the system again.

Ignored Resources:
There is one problem though; this filter was not working because it was redirecting even the login page to the session expired page. This is because the filter is invoked every time we send a request to the server and a request to the login page will never have a user object in the session on our first request. As a result the system becomes un-usable.

There are a few ways to deal with this issue; the easiest would be to get the context path from the request and avoid any session checks if the context path contains the name of your login page.

The clean way however is to provide a list of ignored resources in the web.xml configuration file. This is what the configuration file looks like when we provide ignored resources:


 <filter>
     <filter-name>SessionFilter</filter-name>
     <filter-class>com.mywebproject.common.filter.SessionFilter</filter-class>
     <init-param>
         <param-name>avoid-urls</param-name>
         <param-value>login</param-value>
     </init-param>     
 </filter>
 <filter-mapping>
     <filter-name>SessionFilter</filter-name>
     <url-pattern>/pages/*</url-pattern>
 </filter-mapping>
 

The avoid-urls parameter tag initializes the Filter with this list of URLs that the filter will avoid. Unfortunately this is simply an indicator here and you still need to provide an implementation for the filter to avoid the URLs provided in this list.

Note: You can provide more than one comma separated URLs in the param-value parameter.

Now we need to provide an implementation to avoid these URLs in our Filter. This is how the updated SessionFilter looked like after adding the functionality to load and then avoid the URLs provided in the web.xml configuration file.


package com.mywebproject.common.filter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.StringTokenizer;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.mywebproject.user.bean.UserBean;

public class SessionFilter implements Filter {
 
 private ArrayList<String> urlList;

 @Override
 public void init(FilterConfig config) throws ServletException {

  urlList = new ArrayList<String>();
  String urls = config.getInitParameter("avoid-urls");
  StringTokenizer token = new StringTokenizer(urls, ",");
  
  while (token.hasMoreTokens()) {
   urlList.add(token.nextToken());

  } 
 }
 
 @Override
 public void doFilter(ServletRequest req, ServletResponse res,
   FilterChain chain) throws IOException, ServletException {

  String sessionExpired = "/mywebproject/sessionExpired.html";

  try {

   HttpServletRequest request = (HttpServletRequest) req;
   HttpServletResponse response = (HttpServletResponse) res;
   
   String url = request.getServletPath();
   boolean allowedRequest = false;
   for (String allowed: urlList) {
    
    if(url.contains(allowed)){
     allowedRequest = true;
     break;
    }
   }

   if(!allowedRequest){

    HttpSession session = request.getSession(false);
    if(null == session){
    
     request.getRequestDispatcher("/sessionExpired.html").forward(request, response);
     
    }else{
    
     UserBean ub = (UserBean)session.getAttribute("user");
     if(null == ub){
     
      response.sendRedirect(sessionExpired);
      
     }else if (ub.getUserID() == null){
     
      response.sendRedirect(sessionExpired);
     }
    }

   }

  } catch (Exception f) {
   f.printStackTrace();
  }
  chain.doFilter(req, res);
 }
}

This Filter will now avoid checking for a valid session for any requests for the login page. Other than that page, it will always look for a valid session and a valid logged in user. If the session is expired or if there is no valid UserBean in the session then the Filter will not allow the request to go ahead and will redirect the user to the login page.

It is just one example of how the filters can be used. As mentioned in the beginning of this post, the filters can be used for all sorts of tasks that you want to perform on all or probably most of requests coming to your server.

Thursday, January 9, 2014

Compressed ZIP IO Streams using java

Java provides the java.util.zip package for data compression in a zip-compatible format. Classes in this package allow you to manipulate zip files and create zip archives. Java uses specialized input/output streams to read/write compressed data. These streams can be used to send data to any destination in a compressed format.

The java.util.zip package provides a ZipInputStream for reading the compressed data, i.e., zip files and a ZipOutputStream for writing data in a compressed format. Both of these IO Streams are implementations of Filter Input/Output streams and in terns extend the java.io.InputStream and java.io.OutputStream in the hierarchy.

This interesting inheritance hierarchy makes it every easy for us to use both of these Zip IO Streams as wrappers to any IO Streams. For example, it would be very easy to write a custom RMI Socket to transfer compressed data over the network. For example, here is the code to read data from a file, compress it and send it over to another server using a socket:

 // Create an input stream for reading file data
 InputStream in = new FileInputStream(file);
 
 // Create a socket on a remote server
 Socket socket = new Socket(hostName, portNumber);
 
 // Acquire the remote socket to write data to
 OutputStream out = socket.getOutputStream();
 
 // Create a ZIP output stream to compress data
 ZipOutputStream zos = new ZipOutputStream(out);
 
 //  Open and start reading from the file
 int len;
    while ((len = in.read(buffer)) > 0) {
  // Send data using ZIP output stream (compressed)
     zos.write(buffer, 0, len);
    }

Similarly on the server side, you need to wrap the input stream in a ZipInputStream to read the compressed data. This approach will significantly reduce the network traffic if there is a requirement to send large amount of data over remote systems.

For the purpose of illustrating the end to end working, I used the file IO Streams to demonstrate how the ZIP package is used. There is no reason however, why you cannot replace the java.io.FileInputStream and the java.io.FileOutputStream with any type of IO Streams you may need to use for your project.

For the purpose of this post, I created a utility class ZipUtil, that has three methods:

  • public void createArchive: This method is used to create a new ZIP file containing all files in a given folder.
  • public void extractArchive: This method is used to extract all the files from a given ZIP archive.
  • private List<String> listFiles: This is a private method to generate a list of all files that can be compressed. This method is used internally by createArchive() method.
Let's look at all of these methods in detail:

Creating ZIP Archive

First we shall see how we can create ZIP Archives using the classes provided in the java.util.zip package.

 public void createArchive(File sourceDir, File zipFile)throws Exception{
 
  String root = sourceDir.getAbsolutePath();
  
  // Needed for listFiles method
  int rootLen = root.length() + 1;
  List<String> files = listFiles(rootLen, root, zipFile.getName());
  
  ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
  
  for (String file : files) {
   // Create a new zip entry and add it to the file 
   zos.putNextEntry(new ZipEntry(file));
   
   // Open a read the file data to compress in zip format
   InputStream in = new FileInputStream(root + File.separator + file);
   int len;
   while ((len = in.read(buffer)) > 0) {
    zos.write(buffer, 0, len);
   }
   in.close();
  }
  zos.close();
 }

As you can see, it is a very simple method that reads a folder and creates a zip entry for every file in the given folder. The only tricky bit in the code is actually getting the list of all the files that are to be archived in the new zip file. I shall explain that later. Other then that, all we are doing here is reading data from each file in the folder and writing that to the ZipOutputStream, which will compress that data for you before storing it to the disk in the given zip file.

Now let's see the listFiles method:

 private List<String> listFiles(int rootLen, String path, String zipFile) {

  List<String> files = new ArrayList<String>();
  
  File root = new File( path );
  File[] list = root.listFiles();

  // see if the directory is empty
  if (list == null) 
   return files;
  
  // traverse through each file in the directory
  for ( File f : list ) {
   if ( f.isDirectory() ) {
    
    // In case it is a directory then call recursively to 
    // get a list of all the files in the sub directory
    files.addAll(listFiles(rootLen, f.getAbsolutePath(), zipFile));
   }
   else {
    
    // Skip the zip file if it already exists anywhere in the directory tree
    // otherwise that can result in a deadlock
    if(f.getName().equals(zipFile))
     continue;
    
    // We need the relative pats in the archive, remove the parent directories. 
    files.add(f.getAbsolutePath().substring(rootLen));
   }
  }
  return files;
 }

It is a very simple method to get the list of all the files in the given directory recursively. For that, simply get the list of all files in a directory, go through the list of all the files one by one, if the file is a file then add it in the list to return, if the file is a directory then call the same method recursively with the subdirectory. That way, we will get the list of all the files in the full directory tree.

There are, however, a few minor details that we need to consider. Notice the two extra parameters to this method, rootLen and zipFile. Both of them are there for a special reason. The root length is the length of the root path of the parent folder provided to the createArchive() method. The class java.io.File only provides either the full path of a file or just the file name, both of these are not usable for the archive because we have to maintain the folder hierarchy within the zip archive, relative to the root folder path.

The workaround is to get the full path of each file and then remove the root path from the absolute path of the file, this way we will end up with the path of the files in sub folders, relative to the root path of the parent folder. Simply because String manipulation is comparatively expensive, we pass the length of the root path in rootLen parameter and remove that number of characters from the absolute path of each file, leaving only the relative paths with the names.

Another important consideration is that if the zip file already exists (anywhere in the given directory tree) it can create a deadlock. We have already created an OutputStream for the file and we will end up trying to open an InputStream on the same file, which can result in a deadlock. To avoid that problem we pass the name of the zip file in zipFile parameter and remove the zip file from the returned list if it already exists.

Extracting a ZIP Archive

We just saw how easy it is to create a ZIP Archive using the utility classes provided by the java.util.zip package. Now let's see how to extract files from a ZIP Archive to a folder.

 public void extractArchive(File archive, File outputDir) throws Exception{
 
  // Open the ZIP file
  ZipInputStream zis = new ZipInputStream(new FileInputStream(archive));
  
  // Start traversing the Archive
  ZipEntry ze;
  while ((ze = zis.getNextEntry()) != null){
   
   // Create a new file for each zip entry
   File f = new File(outputDir, ze.getName());
   
   // Create all folder needed to store in correct relative path.
   f.getParentFile().mkdirs();
   
   OutputStream os = new FileOutputStream(f);
   int len;
   while ((len = zis.read(buffer)) > 0) {
    os.write(buffer, 0, len);
   }
   os.close();
  }
  zis.close();
 }

This is the other way around in comparison to the createArchive() method we saw earlier. The compressed ZIP files must be opened using the ZipInputStream and the data read using this stream can be written back to any type of stream.

The only tricky bit here is the f.getParentFile().mkdirs(); part, this is because we must create subdirectories if there are any in the zip archive. Remember, a ZIP file may contain a full directory tree.

Testing the Example

To demonstrate the working of these utility methods, I created a test class and a folder with some test data. We shall first create a zip file using that test data and then extract the zip file in a given folder.

Here is the test class:

package zip;

import java.io.File;

public class MyZipTest {

 private static final String ZIP_FILE = "C:/temp/zipTest/testFiles/test.zip";
 private static final String SOURCE_FOLDER = "C:/temp/zipTest/testFiles";
 private static final String OUTPUT_FOLDER = "C:/temp/zipTest/unzip";

 public static void main(String arg[]){

  File archive = new File(ZIP_FILE);
  File srcDir = new File(SOURCE_FOLDER);
  File destDir = new File(OUTPUT_FOLDER);

  ZipUtil tst = new ZipUtil();
  try{
   // To create a zip archive
   tst.createArchive(srcDir, archive);
   
   // To extract the zip archive
   tst.extractArchive(archive, destDir);
  }catch(Exception e){
   e.printStackTrace();
  }
 }
}

Here is the utility class:

package zip;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

public class ZipUtil {

 byte[] buffer;
 
 public ZipUtil(){
  buffer = new byte[1024];
 }
 
 /**
  * Utility method to create a zip archive of all the files in a given folder. 
  * The method takes the full qualified name of the folder and the name 
  * of the zip file
  * 
  * @param sourceDir -- Directory to archive
  * @param zipFile -- Archive name
  * @throws Exception
  */
 public void createArchive(File sourceDir, File zipFile)throws Exception{

  String root = sourceDir.getAbsolutePath();
  
  if(!sourceDir.exists())
   throw new IOException("Source Directory " + root + " does not Exists");
  
  if(!sourceDir.isDirectory())
   throw new IllegalArgumentException(root + " is not a valid directory");
  
  // Needed for listFiles method
  int rootLen = root.length() + 1;
  List<String> files = listFiles(rootLen, root, zipFile.getName());
  
  if (files.size()<=0)
   throw new IllegalArgumentException(root + " is not empty");

  ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
  
  System.out.println("Archiving: " + root);
  
  for (String file : files) {
   
   System.out.println("Ading: " + file);
   
   // Create a new zip entry and add it to the file 
         zos.putNextEntry(new ZipEntry(file));
   
         // Open a read the file data to compress in zip format
   InputStream in = new FileInputStream(root + File.separator + file);
   int len;
         while ((len = in.read(buffer)) > 0) {
          zos.write(buffer, 0, len);
         }
         in.close();
  }
  zos.close();
  System.out.println("Created: " + zipFile);
 }
 
 /**
  * This private method is used by the <code> public void createArchive(File sourceDir, File zipFile) </code> <br /> 
  * This method will generate the list of all the files in a given directory tree, with relative paths 
  * of the files in the sub directories if there are any. <br />
  * The third parameter is the name of the file that this method will ignore while generating the list. 
  * @param rootLen -- Length of the root directory path
  * @param path -- Directory to traverse
  * @param zipFile -- Name of the file to avoid 
  * @return
  */
 private List<String> listFiles(int rootLen, String path, String zipFile) {

  List<String> files = new ArrayList<String>();
  
  File root = new File( path );
  File[] list = root.listFiles();

  // see if the directory is empty
  if (list == null) 
   return files;
  
  // traverse through each file in the directory
  for ( File f : list ) {
   if ( f.isDirectory() ) {
    
    // In case it is a directory then call recursively to 
    // get a list of all the files in the sub directory
    files.addAll(listFiles(rootLen, f.getAbsolutePath(), zipFile));
   }
   else {
    
    // Skip the zip file if it already exists anywhere in the directory tree
    // otherwise that can result in a deadlock
    if(f.getName().equals(zipFile))
     continue;
    
    // We need the relative pats in the archive, remove the parent directories. 
    files.add(f.getAbsolutePath().substring(rootLen));
   }
  }
  return files;
 }
 
 /**
  * Utility method to extract all the files in a ZIP Archive to the given directory. 
  * If the ZIP file contains a complete directory tree, the method will take care of 
  * creating sub directories and placing files in appropriate locations within the 
  * sub directories. 
  * @param archive -- Zip Archive
  * @param outputDir -- Output directory for extracted files 
  * @throws Exception
  */
 public void extractArchive(File archive, File outputDir) throws Exception{
  
  String root = outputDir.getAbsolutePath();
  
  if(!outputDir.exists()){
   outputDir.mkdir();
  }
  
  if(!outputDir.isDirectory())
   throw new IllegalArgumentException(root + " is not a valid directory");

  System.out.println("Processing: " + archive.getAbsolutePath());
  System.out.println("Extracting files to: " + root);
  
  // Open the ZIP file
  ZipInputStream zis = new ZipInputStream(new FileInputStream(archive));
  
  // Start traversing the Archive
  ZipEntry ze;
  while ((ze = zis.getNextEntry()) != null){
   
   System.out.println("Extracting: " + ze);
   
   // Create a new file for each zip entry
   File f = new File(outputDir, ze.getName());
   // Create all folder needed to store in correct relative path.
            f.getParentFile().mkdirs();
   
            OutputStream os = new FileOutputStream(f);
            
   int len;
         while ((len = zis.read(buffer)) > 0) {
          os.write(buffer, 0, len);
         }
   os.close();
  }
  
  zis.close();
  
  System.out.println("Successfully Extracted: " + archive.getAbsolutePath());
 }
}

And this is the output when you run the test class to create a zip archive.

Archiving: C:\temp\zipTest\testFiles
Ading: first\TestText-1.txt
Ading: first\TestText-2.txt
Ading: first\TestText-3.txt
Ading: second\TestText-4.txt
Ading: second\TestText-5.txt
Ading: second\TestText-6.txt
Ading: second\TestText-7.txt
Ading: TestText-10.txt
Ading: TestText-8.txt
Ading: TestText-9.txt
Created: C:\temp\zipTest\testFiles\test.zip

And this is the output when you run the test class to extract a zip archive.

Processing: C:\temp\zipTest\testFiles\test.zip
Extracting files to: C:\temp\zipTest\unzip
Extracting: first\TestText-1.txt
Extracting: first\TestText-2.txt
Extracting: first\TestText-3.txt
Extracting: second\TestText-4.txt
Extracting: second\TestText-5.txt
Extracting: second\TestText-6.txt
Extracting: second\TestText-7.txt
Extracting: TestText-10.txt
Extracting: TestText-8.txt
Extracting: TestText-9.txt
Successfully Extracted: C:\temp\zipTest\testFiles\test.zip

As you can see, the technique can be used to compress any type of data streams. This may significantly reduce the bandwidth requirements of your system and also can dramatically improve the performance.

However, there are a few pitfalls there. You are adding some extra processing before sending data over any stream, and also you have to process data on the other end before you can use it. This extra processing time may not be justifiable in most of the cases, and you may even end up slowing down your system rather then gaining any performance benefits.

As every powerful tool, this must be approached with caution. This approach is desired only when you are transferring large amounts of data on a single request, and a data compression would make a difference. If you are sending small data chunks then it may not give you any benefits, or worse, may even introduce more problems.