Struts2 ActionContext and Response for chaining actions

3.4k Views Asked by At

I have a pretty complex problem about struts2 chaining actions, thanks in advance for your patience reading my problem. I will try my best to describe it clearly.

Below is my struts.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
    <constant name="struts.enable.DynamicMethodInvocation" value="false" />
    <constant name="struts.enable.SlashesInActionNames" value="true" />
    <constant name="struts.devMode" value="false" />


    <package name="default" extends="struts-default" namespace="/">
        <action name="test" class="com.bv.test.TestAction1" >
            <result name="success" type="chain">y</result>
        </action>

        <action name="x">
            <result name="success">/index.jsp</result>
        </action>

        <action name="y" class="com.bv.test.TestAction2">
            <result name="success">/index.jsp</result>
        </action>
    </package>
</struts>

My logic is like this: When accessing to /myapp/test, TestAction1 will handle the request; In TestAction1, I "include" action x (my 2nd action in my config) like this:

ResponseImpl myResponse = new ResponseImpl(response);
RequestDispatcher rd = request.getRequestDispatcher("/x.action");
rd.include(request, myResponse); 

And the important thing is I am using a customized ResponseIml when including "x.action".

After including, I return "success", so the result chains to action y (3rd action in my config);
And at last, TestAction2 continue to handle the request, it will go to success result, and the jsp should be rendered, but what I see is a blank page.

The jsp file is very simple: index.jsp

<h1>Test!</h1>

My question/puzzle is:

  1. In TestAction1, if I get the response from ServletActionContext, I am getting different ones before and after including; before including is the default response, but after including I got an instance of my customized ResponseImpl; I expect to get the same one: i.e.: the default response;
  2. In TestAction2, I get response from ServletActionContext, what I got is the instance of my customized ResponseIml. This is my most important thing, I think I should get a default response instance here, i.e.: org.apache.catalina.connector.Response, I am running on JBoss;
  3. I am getting a different ActionContext in TestAction2 (compared with the ActionContext I get in TestAction1).

This problem really drive me on the nuts, I have spent days on it.
Any advice will be appreciated!
Thanks a million!!

My Code:

TestAction1:

public class TestAction1 {
  public String execute() {
    ActionContext ac = ActionContext.getContext();
    System.out.println("Before including: the action context is : " + ac);
    HttpServletRequest request = ServletActionContext.getRequest();
    HttpServletResponse response = ServletActionContext.getResponse();
    System.out.println("Before including: the response is : " + response);

    ResponseImpl myResponse = new ResponseImpl(response);
    RequestDispatcher rd = request.getRequestDispatcher("/x.action");
    try {
      rd.include(request, myResponse); 
      String s = myResponse.getOutput();  
      System.out.println("get from response: " + s);
    }
    catch (Exception e) {
      e.printStackTrace();
    }

    ac = ActionContext.getContext();
    System.out.println("After including : the action context is : " + ac);
    response = ServletActionContext.getResponse();
    System.out.println("After including : the response is : " + response);
    return "success";
  }
}

ResponseImpl:

import java.util.Locale;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.Cookie;
import javax.servlet.jsp.JspWriter;

/**
 * 
 * 
 */
public class ResponseImpl extends HttpServletResponseWrapper  {

  //=========================================================
  // Private fields.
  //=========================================================

  private ServletOutputStream outputStream = null;

  private ByteArrayOutputStream byteArrayOutputStream = null;

  private StringWriter stringWriter = null;

  private PrintWriter printWriter = null;

  private HttpServletResponse _response = null;

  private String contentType= "text/html";

  private String encoding = "UTF-8";

  /**
   *
   */
  class ServletOutputStream extends javax.servlet.ServletOutputStream {

    private OutputStream outputStream = null;

    /**
     *
     */
    ServletOutputStream(ByteArrayOutputStream outputStream) {
      super();
      this.outputStream = outputStream;
    }

    /**
     *
     */
    public void write(int b) throws IOException {
      this.outputStream.write(b);
    }
  }

  //=========================================================
  // Public constructors and methods.
  //=========================================================

  /**
   *
   */
  public ResponseImpl(HttpServletResponse response) {
    super(response);
    this._response = response;
  }

  /**
   *
   */
  public String getOutput() {
    if (this.stringWriter != null) {
      return this.stringWriter.toString();
    }

    if (this.byteArrayOutputStream != null) {
      try {
        return this.byteArrayOutputStream.toString(this.encoding);
      }
      catch (UnsupportedEncodingException e) {
      }
      return this.byteArrayOutputStream.toString();
    }

    return null;
  }

  //=========================================================
  // Implements HttpServletResponse interface.
  //=========================================================

  public void addCookie(Cookie cookie) {
  }

  public void addDateHeader(String name, long date) {
  }

  public void addHeader(String name, String value) {
  }

  public void addIntHeader(String name, int value) {
  }

  public boolean containsHeader(String name) {
    return false;
  }

  public String encodeRedirectURL(String url) {
    if (null != this._response) {
      url = this._response.encodeRedirectURL(url);
    }
    return url;
  }

  public String encodeURL(String url) {
    if (null != this._response) {
      url = this._response.encodeURL(url);
    }
    return url;
  }

  public void sendError(int sc) {
  }

  public void sendError(int sc, String msg) {
  }

  public void sendRedirect(String location) {
  }

  public void setDateHeader(String name, long date) {
  }

  public void setHeader(String name, String value) {
  }

  public void setIntHeader(String name, int value) {
  }

  public void setStatus(int sc) {
  }

  public void resetBuffer() {
  }

  //=========================================================
  // Implements deprecated HttpServletResponse methods.
  //=========================================================

  public void setStatus(int sc, String sm) {
  }

  //=========================================================
  // Implements deprecated HttpServletResponse methods.
  //=========================================================

  public String encodeRedirectUrl(String url) {
    return encodeRedirectURL(url);
  }

  public String encodeUrl(String url) {
    return encodeURL(url);
  }

  //=========================================================
  // Implements ServletResponse interface.
  //=========================================================

  public void flushBuffer() {
  }

  public int getBufferSize() {
    return 0;
  }

  public String getCharacterEncoding() {
    return this.encoding;
  }

  public String getContentType() {
    return this.contentType;
  }

  public Locale getLocale() {
    return null;
  }

  public javax.servlet.ServletOutputStream getOutputStream() {
    if (this.outputStream == null) {
      this.byteArrayOutputStream = new ByteArrayOutputStream();
      this.outputStream =
        new ServletOutputStream(this.byteArrayOutputStream);
    }
    return this.outputStream;
  }

  public PrintWriter getWriter() {
    if (this.printWriter == null) {
      this.stringWriter = new StringWriter();
      this.printWriter = new PrintWriter(this.stringWriter);
    }
    return this.printWriter;
  }

  public boolean isCommitted() {
    return true;
  }

  public void reset() {
  }

  public void setBufferSize(int size) {
  }

  public void setCharacterEncoding(String charset) {
  }

  public void setContentLength(int len) {
  }

  public void setContentType(String type) {
    int needle = type.indexOf(";");
    if (-1 == needle) {
      this.contentType = type;
    }
    else {
      this.contentType = type.substring(0, needle);
      String pattern = "charset=";
      int index = type.indexOf(pattern, needle);
      if (-1 != index) {
        this.encoding = type.substring(index + pattern.length());
      }
    }
  }

  public void setLocale(Locale locale) {
  }
}

TestAction2:

public class TestAction2 {

  public String execute() {
    ActionContext ac = ActionContext.getContext();
    System.out.println("In TestAction 2 : the action context is : " + ac);

    HttpServletResponse response = ServletActionContext.getResponse();
    System.out.println("In TestAction 2 : the response is : " + response);
    return "success";
  }
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>Struts2 Application</display-name>

     <filter>
        <filter-name>struts2</filter-name>
        <filter-class>
            org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
        </filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>
</web-app>

This is my debug info.

  • Before including: the action context is :com.opensymphony.xwork2.ActionContext@c639ce
  • Before including: the response is : org.apache.catalina.connector.ResponseFacade@8b677f
  • get from response: <h1>Test!</h1>
  • After including : the action context is : com.opensymphony.xwork2.ActionContext@2445d7
  • After including : the response is : com.bv.test.ResponseImpl@165547d
  • In TestAction 2 : the action context is :com.opensymphony.xwork2.ActionContext@19478c7
  • In TestAction 2 : the response is : com.bv.test.ResponseImpl@165547d

So, I have different ActionContext instances before and after the including!!

2

There are 2 best solutions below

7
On

When you do rd.include another request is fired internally inside the web server. So from struts point of view it sees a completely new request and a new action context is created as a result. (that's why you need to include 'INCLUDE' thing on the struts2 filter.. so that it's seeing included requests as well). Probably since thread local variables are used to track action context and all that when you do ActionContext.getContext() the context related to the new request (related to the include) gets retrieved.

Did you try resetting the response to the initial one in a finally block like below

try {
      rd.include(request, myResponse); 
      String s = myResponse.getOutput();  
      System.out.println("get from response: " + s);
    }
    catch (Exception e) {
      e.printStackTrace();
    } finally {
       ServletActionContext.setResponse(response);
    }

If this resolves the response issue.. you could probably store the string 's' as a variable in the action context and retrieve it inside your Action2

2
On

You could also try the following as well. Instead of using chaining.. inside your TestAction1 include the TestAction2 with the original response. return 'none' from the action as the return value.

public class TestAction1 {
  public String execute() {
    ActionContext ac = ActionContext.getContext();
    System.out.println("Before including: the action context is : " + ac);
    HttpServletRequest request = ServletActionContext.getRequest();
    HttpServletResponse response = ServletActionContext.getResponse();
    System.out.println("Before including: the response is : " + response);

    ResponseImpl myResponse = new ResponseImpl(response);
    RequestDispatcher rd = request.getRequestDispatcher("/x.action");
    try {
      rd.include(request, myResponse); 
      String s = myResponse.getOutput();  
      System.out.println("get from response: " + s);
    }
    catch (Exception e) {
      e.printStackTrace();
    }



      RequestDispatcher rd = request.getRequestDispatcher("/y.action");
        try {
          rd.include(request, response); 
        }
        catch (Exception e) {
          e.printStackTrace();
        } finally {
          return "none";
        }
      }
    }