Wednesday, July 2, 2014

struts2 action return custom result class

Struts2 action classes as we know should return always a string
"success", "error", "input"
which is used for navigation paths. There has to be an entry in struts.xml or via convention to tell struts what result handler to be used for a struts result name

But I always liked the idea of JAX-WS return result flexibilty. For returning an arbritrary string all you need to do it return
String
The returned result is exactly what user sees on screen which is ideal for using struts as JSON data provider.
Recently I found out a way to return arbitrary result class which is not registered in struts.xml. This class gets injected at runtime in struts and performs the custom result creation.

So lets see some code and get a feel.

Action Class - TestAction.java

package actions;

import myresults.MyResult;

import org.apache.struts2.convention.annotation.Action;

import com.opensymphony.xwork2.ActionSupport;


public class TestAction extends ActionSupport {
 private static final long serialVersionUID = 1L;

 @Action("show")
 public MyResult show(){
  System.out.println("This is executed in action class");
  //returning arbritrary result handler instead of result names 
  //like "success" or "error" or "someResultName"
  return new MyResult("This is from my result class");
 }
}
As you can see here the return result is an arbitrary class MyResult. So lets see the MyResult class.

Arbitrary Result class - MyResult.java

package myresults;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletResponse;

import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.StrutsResultSupport;

import com.opensymphony.xwork2.ActionInvocation;


public class MyResult extends StrutsResultSupport {

 private String text;
 public MyResult(String text){
  this.text = text;
 }
 @Override
 protected void doExecute(String finalLocation, 
               ActionInvocation invocation) throws Exception {
  HttpServletResponse res = ServletActionContext.getResponse();
  PrintWriter writer = res.getWriter();
  writer.println(text);
  writer.close();
 }

}
Other normal J2EE artifacts are below.

web.xml



    
        struts2
        org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
        
         actionPackages
         com.mycompany.myapp.actions
        
    

    
        struts2
        /*
    

Maven - pom.xml

This is absolutely not necessary to build using maven.

  4.0.0
  MyResultTest
  MyResultTest
  0.0.1-SNAPSHOT
  war
  
   
    org.apache.struts
    struts2-convention-plugin
    2.3.16.1
   
   
    javax.servlet
    javax.servlet-api
    3.1.0
   
   
    org.apache.struts
    struts2-config-browser-plugin
    2.3.16.1
   
  

Browser screenshot

Console screenshot

So far that was simplest result class. But using this technique a more useful results classes can be built. Like the one for outputting strings or json. So, I modified the classes again to create JSON serializer. I used quick json creation using org.json:json:20140107 maven jar, for performance streams can be used. But this will also work.

TestAction.java

package actions;

import java.util.HashMap;
import java.util.Map;

import myresults.MyJsonResult;

import org.apache.struts2.convention.annotation.Action;

import com.opensymphony.xwork2.ActionSupport;


public class TestAction extends ActionSupport {
 private static final long serialVersionUID = 1L;

 @Action("show")
 public MyJsonResult show(){
  System.out.println("This is executed in action class");
  Map map = new HashMap();
  Map address = new HashMap();
  map.put("firstName", "samarjit");
  map.put("lastName", "samarjit");
  address.put("location", "Singapore");
  address.put("road", "Cecil Street");
  map.put("address", address);
  //returning arbritrary result handler instead of result names
  // like "success" or "error" or "someResultName"
  return new MyJsonResult(map);
 }
}

MyJsonResult.java

package myresults;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.StrutsResultSupport;
import org.json.JSONArray;
import org.json.JSONObject;

import com.opensymphony.xwork2.ActionInvocation;


public class MyJsonResult extends StrutsResultSupport {

 private String text;
 public MyJsonResult(Map  map){
  this.text =  new JSONObject(map).toString(3);
 }
 
 public MyJsonResult(List <Object > lst){
  this.text =  new JSONArray(lst).toString(3);
 }
 
 @Override
 protected void doExecute(String finalLocation, 
                 ActionInvocation invocation) throws Exception {
  HttpServletResponse res = ServletActionContext.getResponse();
  res.setContentType("application/json");
  PrintWriter writer = res.getWriter();
  writer.println(text);
  writer.close();
 }

}

Result browser screenshot

The directory structure is very simple
MyResultTestProject
│
├───pom.xml
│
├───src
│   ├───main
│   │   ├───java
│   │   │   ├───actions
│   │   │   │       TestAction.java
│   │   │   │
│   │   │   └───myresults
│   │   │           MyJsonResult.java
│   │   │
│   │   ├───resources
│   │   └───webapp
│   │       └───WEB-INF
│   │               web.xml
│   │
│   └───test
│       ├───java
│       └───resources

And here is how it looks in eclipse.

Struts2 way of handling custom result

The above result class return type is something I accidentally found out from source code. However struts2 allows registering of custom result class in struts.xml in a new package or by extending struts-default package. Your action classes can then use it by returning a result name as you would have defined in struts.xml or in convention while defining action class. The result type has to be pre-configured either in annotation or in struts.xml in a new package.





    
        
            
            
        
    
        
            
                ${jsonData}
            
        
    


So in this example result name is "success" and result type is defined as "newresult". The actual class name for "newresult" was registered earlier.
Please note this piece code was not tested, and for sake of completeness I just showed it here. It will also help understanding the struts was of handling custom result. You can get advantage of result name based flow definition which is integral part of struts. You might also get advantage of result class creation by struts bean factory and interceptors like workflow interceptors will also function correctly with this method. Thanks for following the post. Hope it helps someone.