Tuesday, July 22, 2008

Problem Statement and Source Files


Theory without examples can be hard to grasp and less useful. So just making a small problem and corresponding files. Lets see
Problem statement: Given an online reporting application. There is a form that posts certain parameters and displays reports accordingly. Let us say that there are two basic parameters: report type and date range. Report type may be word, pdf, text. Date range consists of a from and a to time. Further user has an option to specify current time as the to time. Our form bean would look something like this:

package myPackage;

public class MyFormBean{
private String reportType;
//date in format dd/MM/yyyy HH:mm
privateDate fromDate;
private Date toDate;
private boolean useCurrentTime=true;

//.......
//public getters and setters for fields
//.......
}

Reports are denoted by a simple interface:
package myPackage;

public interface Report{
String getFileUrl();
String getType();
Date getCreationTime();
}
Reports are fetched using this interface:
package myPackage;

public interface DataBaseUtil{
List<Report> getReports(MyFormBean myFormBean);
}
which may have some implementation like:
package myPackage;

public class DataBaseUtilImpl{
private static DataBaseUtilImpl singleInst = new DataBaseUtilImpl();
private DataBaseUtilImpl(){}

public static DataBaseUtilImpl getInstance(){ return singleInst ; }

public List<Report> getReports(MyFormBean myFormBean){
//process command object to get a list of reports
}
}
The controller will be injected something like this in the config file:


 <bean id="reportFormController" class="myPackage.ReportFormController">
<property name "dataBaseUtil" ref="DataBaseUtilBean"/>
<property name="formView" value="reportInput"/>
<property name="bindOnNewForm" value="true"/>
<property name="successView" value="reportOutput"/>
<property name="validator" ref="myValidator"/>
<property name="commandName" value="reportFormBean"/>
<property name="commandClass" value="myPackage.MyFormBean"/>
</bean>
The actual form controller will look something like:

package myPackage;

import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.text.*;

public class ReportFormController extends SimpleFormController {
private DataBaseUtil dataBaseUtil;

public void setDataBaseUtil(DataBaseUtil dbUtil){
this.dataBaseUtil = dbUtil;
}

public DataBaseUtil getDataBaseUtil(){
return this.dataBaseUtil;
}

/*Override so as string inputs in the form bind directly to date in command object*/
@Override
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder dataBinder) throws Exception{
DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm");
df.setLenient(false);
dataBinder.registerCustomEditor(Date.class,new CustomDateEditor(df,false));
super.initBinder(request,dataBinder);
}


@Override
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response,Object command, BindException bindException)throws Exception{
//handle submit request
ReportFormBean repFormBean = (ReportFormBean ) command;

List<Report> reps ;

if(dataBaseUtil != null)
reps = dataBaseUtil.getReports(repFormBean);
else
reps = new ArrayList<Report>();

ModelAndView mv = showForm(request, response, bindException);

mvmodel.put("REPORTS", reps);

return mv;
}
}
From next post we will start exploring some practical problems using these files as our base.

Monday, July 14, 2008

Testing a SimpleFormController

Testing a SimpleFormController using JUnit and JMock was a small challenge I came across. After hitting a number of hurdles I managed to write tests that were meaningful and worked. Following points noted for people on similar quest :):

  1. Keep handy JMock Cheat Sheet , especially the Methods and Expectations part
  2. Use MockHttpServletResponse, MockHttpServletRequest and MockHttpSession in the package org.springframework.mock.web to mock web response, request and session respectively.
  3. Remember you can construct post requests and get requests with your MockHttpServletRequest using: MockHttpServletRequest mockRequest = new MockHttpServletRequest("POST","myUrl"); and MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET","myUrl");
  4. Finally use ModelAndView modelAndView = myController.handleRequest(mockRequest, mockResponse);
  5. Use JUnit asserts to verify ModelAndView as per your expectations

Generally your controller will follow this path: receive request-process-return ModelAndView to render. Processing may involve using the command object and passing values to the backend for processing. In your controller tests do NOT attempt to check backend- just mock it. Focus on checking the controller. A combination of expectations and asserts is your best bet to do the testing thoroughly. Let us explore a small example based on these principles in our next post.

Thursday, July 3, 2008

Conditional Form Binding

Form binding is a useful feature in Spring. However I needed to turn on or off form binding depending on certain conditions. Golden question, how to do it...

1. In the controller constructor (or in XML config for DI) call setBindOnNewForm(true);
2. Do NOT call/set setSessionForm(true);
3. Override suppressBinding and return true or false according to your condition. true will suppress form binding.

@Override
protected boolean suppressBinding(HttpServletRequest request){
boolean myCondition = false;
//some checks on myCondition depending on request params
return myCondition ;
}

Tuesday, July 1, 2008

Date Binding on SimpleFormController using initBinder

If you wish to bind an input date field in your web form directly to a Date property in your command object, you will need to override the initBinder method of the SimpleFormController. Following maps a date in dd/MM/yyyy format on the front-end to backend and vice-versa.

@Override
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder dataBinder) throws Exception{
DateFormat df = new SimpleDateFormat("dd/MM/yyyy");
df.setLenient(false);
dataBinder.registerCustomEditor(Date.class,new CustomDateEditor(df,false));
super.initBinder(request,dataBinder);
}

Your valang validators can help you put some crisp checks on date if need be. Remember dd/mm/yyyy and dd/MM/yyyy are NOT same.