Monday, December 22, 2008

Creating custom result type - struts 2

Creating a custom result type comes to the field when your are trying
to develop a Ajax base development with struts 2. By default, struts 2 uses RequestDispatcher originated from servlets API, as the default result type. That is, after returning the control string from your action, this default result type will show the way for final page rendering.

Suppose you want to have some data response to the client, in stead of despatching to a JSP page after returning from your action class. Ajax request to the server often needs this kind of data.
Here, I am going to explain you, how to make a Ajax request to a struts 2 action class and how to use a custom result type to send some data back to the client, instead of rendering a another JSP page as usual. I am not going to give you a detailed description on Ajax related things. My major concern is how to create a custom result type and use it with Ajax development.

I have a list of User's names in a struts select box component and onchange event will make a request to a action class to get full details about the user to the client and update some segment of current rendered page of the browser as usual Ajax application does. I am passing userId as query string parameter to the action class.
Here is the javascript code the responsible to make the request to the server. I have encpsulated all related functions needed to make a request to the server, into one javascript object.

var PDA = {};
PDA.common = {};
PDA.common.ContentLoader = function(httpMethod,url,params,callBack){
     this.init(httpMethod,url,params,callBack);
}
PDA.common.ContentLoader.prototype = {
READY_STATE_UNINITIALIZED :0,
READY_STATE_LOADING :1,
READY_STATE_LOADED :2,
READY_STATE_INTERACTIVE :3,
READY_STATE_COMPLETE :4,
httpMethod : null,
url : null,
params : null,
callBack : null,
error : null,
request : null,

defaultError:function(){
      alert("error fetching data!"
      +"\n\nreadyState:"+this.request.readyState
      +"\nstatus: "+this.request.status
      +"\nheaders: "+this.request.getAllResponseHeaders());
},

getXMLHttpRequest: function(){
   if(window.XMLHttpRequest){
     this.request = new XMLHttpRequest();
   }else if(typeof ActiveXObject != "undefined"){
       try {
           this.request = new ActiveXObject("Msxml2.XMLHTTP");
       }catch(e) {
           try{
                  this.request = new ActiveXObject("Microsoft.XMLHTTP");
              } catch (e){
                    this.request = null;
              }
        }
   } 
},

onReadyState: function(){
 var req = this.request;
 var ready = req.readyState;
 if (ready == this.READY_STATE_COMPLETE){
     var httpStatus = req.status;
    if (httpStatus == 200 || httpStatus == 0){
         this.callBack.call(this);
     }else{
         this.error.call(this);
     }
  }
},

sendRequest: function(){
   this.getXMLHttpRequest();
      if(this.request){
         try{
             var loader = this;
             this.request.onreadystatechange = function(){
                  loader.onReadyState.call(loader);
             }
             this.request.open(this.httpMethod,this.url,true);
             this.request.send(this.params);
          }catch(e){
                this.error.call(this);
          }
    }else{
         this.error.call(this);
    }
},

init: function(httpMethod,url,params,callBack){
      this.httpMethod = httpMethod;
      this.url = url;
      this.params = params;
      this.callBack = callBack;
      this.error = this.defaultError;
      this.sendRequest();
   }
};
The above object almost contains all the function to make a Ajax request to the server. I will simply explain the constructor parameters need to initiate object from this class.
  • httpMethod : eidther "POST" or "GET"
  • url : request url (in my case, it is action name)
  • params : url parameter needed to pass to the server
  • callBack : javascrip function name which handles the response data and update the client browser content
If you want to make a request to the server, you can simply initiate an object from this class. Easy isn't it? Now the time to call the action.
My user list select box' onchange event will fire the fallowing javascrip function which get the user id form the select box and make a request to the "ViewUsers" class.
function fetchUser(){
    var selectBox = document.getElementById('userId');
    var selectedIndex = selectBox.selectedIndex;
    var selectedValue = selectBox.options[selectedIndex].value;
    new PDA.common.ContentLoader("POST",
                                 "ViewUsers.action?userId=" + selectedValue,
                                  null,
                                  showUsers);
}
"showUsers" function is the callback function in my case which is responsible for client side updates. Let's see the source code for ViewUsers action class next.
package com.axiohelix.pda.actions;
import org.hibernate.Transaction;
import com.opensymphony.xwork2.ActionSupport;

public class ViewUsers extends ActionSupport{
 private static final long serialVersionUID = 1L;
 private int userId;
 private User user;
 private Object jsonModel;
 
 public String execute(){
  String result = null;
  try{
   //do some thing to get the user object from userId
   setUserType(user);
   setJsonModel(user);
   result = SUCCESS;
  }catch(Exception e){
   e.printStackTrace();
   result = ERROR;
  }finally{
  }
  return result;
 }
 
 public int getUserId() {
  return userId;
 }

 public void setUserId(int userId) {
  this.userId = userId;
 }

 public User getUser() {
  return user;
 }

 public void setUser(User user) {
  this.user = user;
 }

 public Object getJsonModel() {
  return jsonModel;
 }

 public void setJsonModel(Object jsonModel) {
  this.jsonModel = jsonModel;
 }
}
As you can see, I have created User object from the userId and exposed it into the ValueStack under the property of "jsonModel". After returning from this action class, it should hit our custom result type and should send the user informations back to the client. Here, I am going to use JSON which provides a succinct text-based serialization of data objects. JSON can be a powerfully succinct and lightweight means of communication between a web application server and an Ajax client.
Next, we will see the core of the post.We are going to create the custom result which sends JSON data string back to the client. The only requirement needs to create a struts2 custom result is, implementing Result interface of struts 2. Here is the source code to create a custom result type and serialize the object in to JSON.

package com.axiohelix.pda.jsonresult;

import java.io.PrintWriter;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.util.ValueStack;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;

public class JSONResult implements Result{
 private static final long serialVersionUID = 1L;
 public static final String DEFAULT_PARAM = "classAlias";
 String classAlias;

 public String getClassAlias() {
  return classAlias;
 }
 public void setClassAlias(String classAlias) {
  this.classAlias = classAlias;
 }
 @Override
 public void execute(ActionInvocation invocation) throws Exception {
  ServletActionContext.getResponse().setContentType("text/plain"); 
  ServletActionContext.getResponse().addHeader("Cache-Control", "no-cache");
  PrintWriter responseStream = ServletActionContext.getResponse().getWriter();
  ValueStack valueStack = invocation.getStack();
  Object jsonModel = valueStack.findValue("jsonModel");
  XStream xstream = new XStream(new JettisonMappedXmlDriver());
  if ( classAlias == null ){
   classAlias = "object";
  }
  xstream.alias(classAlias, jsonModel.getClass() ); 
  responseStream.println(xstream.toXML( jsonModel ));
 }
}

Here, I have used two open source libraries, XStream and Jettison. XStream helps to serialize java objects into XML format while Jettison helps to create JSON from the XML.As you can see,I am getting "jsonModel" property exposed into the ValueStack from our ViewUsers action calss,progrmmetically and serialize it into JSON.
Now the time to tell ViewUsers action class about our custom result type. We have to add fallowing entries into our declarative architecture, ie struts.xml file. To declare our custom result type, you have to add fallowing into struts.xml file.
<result-types>
<result-type name="customJSON" class="com.axiohelix.pda.jsonresult.JSONResult">
</result-type>
</result-types>
To use our custom result type, we need to add the fallowing to struts.xml file.
<action name="ViewUsers" class="com.axiohelix.pda.actions.ViewUsers">
<result type="customJSON">user</result>
</action&>
Here, you can see, I am passing in a parameter to the custom result type. It will be set to the "classAlias" field and finally, it will be the name of the root JSON object return to the client. Fallowing is the response JSON string representation of JSON

{"user":{"username":"Mary",
"password":"max",
"age":"28",
"firstName":"Mary",
"lastName":"Greene",
"receiveJunkMail":"false"}}

Saturday, December 6, 2008

Handling custom error pages - struts 2

When you work with struts 2, you may need to handle exception and
forward it to a custom error pages as you wish. The exception interceptor of struts 2 will catch exception and map them to user-defined error pages by type. For example, suppose you want to map all java.lang.Exception to a custom error page defined by yourself, you have to involve the exception interceptor.
You can add the fallowing entry to your struts.xml file just after the package element.


<global-results>
<result name="error">/WEB-INF/jsp/errorPage.jsp
</result>
</global-results>
<global-exception-mappings>
<exception-mapping exception="java.lang.Exception" result="error" />
</global-exception-mappings>

With this declaration, each java.lang.Exception exception will end up with your error page. Also you can display more details on error in your custom page.Before yielding control to the result, the exception interceptor will create an ExceptionHolder object and place it on top of the ValueStack. The Exception-Holder is a wrapper around an exception that exposes the stack trace and the exception as JavaBeans properties that you can access from a tag in your error page. The following snippet shows our error JSP page:

Exception Name: <s:property value="exception" />

What you did wrong:<s:property value="exceptionStack" />

We can use the
exception and exceptionStack property which are already exposed to ValueStack by ExceptionHolder object to get the details about the exception into the error page.

Tuesday, December 2, 2008

Object backed-components – Struts 2

The automatic data transfer of Struts 2 needs only to provide
JavaBeans properties on actions, using the same names as the form
fields being submitted. Object backed – components of Struts 2 allows us to transfer data as objects. Suppose you want
to create a new user.
We have User class as fallows,
public class User{
private String name;
private String email;
// getter and setter methods
}
If you use Struts 2 default data transferring process, you must give the same field names as input JSP’s field’s name, must write JavaBeans properties with the same name in you action class and you have to create User object by yourself to create a new user. If you use, object – backed components of Struts 2 for data transferring, We don’t even have to create the User object that backs the property because the framework’s data transfer will handle this for us when it starts trying to move the data over. You can have more cleaner action class as fallows.
public String execute(){
createUser(user);
return SUCCESS;
}
private User user;
public void setUser(User user){
this.user = user;
}
public User getUser(){
return this.user;
}
You also need to do slight changes in your input JSP’s and the resulting JSP’s when addressing appropriate fields. For example, in the input JSP for creating a new user,
Similarly, when rendering the result,
Welcome <s:property value="user.name" />
The minor consequences are that we have to go a dot deeper when we reference our data from the JSP pages.



Share

Widgets