Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mapping Struts 2 Exception Handler to an Action

I have Struts 2 configured to redirect any java.lang.Exception to a special Action which logs the exception. My redirection works, but my Action always gets a null exception (even when I explicitly throw an Exception). Here is my struts.xml file:

<global-results>
    <result name="errHandler" type="chain">
    <param name="actionName">errorProcessor</param>
    </result>
</global-results>

<global-exception-mappings>
     <exception-mapping exception="java.lang.Exception" result="errHandler" />
</global-exception-mappings>

<action name="errorProcessor" class="myErrorProcessor">
      <result name="error">/error.jsp</result>
</action>

<action name="throwExceptions" class="throwExceptions">
      <result name="success">done.jsp</result>
</action>

In my error processor, I have the following:

public class myErrorProcessor extends ActionSupport {

   private Exception exception;

   public String execute() {
         System.out.println("null check: " + (exception == null));
         return "error";
   }

   public void setException(Exception exception) {
         this.exception = exception;
   }

   public Exception getException() {
         return exception;
   }
}

In the throwsException class, I have the following:

public String execute() {
     int x = 7 / 0;
     return "success";
}

When I run my program, the Exception handler always gets a null exception. I am using the chain type to redirect to the exception handler. Do I need to implement some sort of ExceptionAware interface? Is the Struts 2 exception setter called something besides setException?

Note: I was trying to follow this tutorial when writing this program.

like image 657
David Avatar asked Apr 21 '11 16:04

David


2 Answers

Using struts2 version 2.3.1.2 I do not get a null exception in my error handler everything works as advertised. Ensure you are using an unmodified defaultStack.

As far as credible sources, we can reference the interceptor documentation for ExceptionMappingInterceptor and Chaining Interceptor. The ExceptionMappingInterceptor pushes an ExceptionHolder onto the value stack which in turn exposes an exception property. The chaining interceptor copies all the properties on the value stack to the target. Since ExceptionHolder is on the stack if there is a setter for Exception it will be set.

Here are all the files which produce a working example:

struts.xml (kept quite similar to the questions):

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

<struts>
    <constant name="struts.devMode" value="true" />
    <constant name="struts.ui.theme" value="simple" />
    <package  name="kenmcwilliams"  namespace="/" extends="struts-default">
        <global-results>
            <result name="errHandler" type="chain">
                <param name="actionName">errorProcessor</param>
            </result>
        </global-results>
        <global-exception-mappings>
            <exception-mapping exception="java.lang.Exception" result="errHandler" />
        </global-exception-mappings>
        <action name="errorProcessor" class="com.kenmcwilliams.test.myErrorProcessor">
            <result name="error">/WEB-INF/content/error.jsp</result>
        </action>
        <action name="throwExceptions" class="com.kenmcwilliams.kensocketchat.action.Bomb">
            <result name="success">/WEB-INF/content/bomb.jsp</result>
        </action>
    </package>
</struts>

MyErrorProcessor.java

package com.kenmcwilliams.test;

import com.opensymphony.xwork2.ActionSupport;

public class MyErrorProcessor extends ActionSupport {

    private Exception exception;

    @Override
    public String execute() {
        System.out.println("Is exception null: " + (exception == null));
        System.out.println(""
                + exception.getMessage());
        return "error";
    }

    public void setException(Exception exceptionHolder) {
        this.exception = exceptionHolder;
    }

    public Exception getException() {
        return exception;
    }
}

Bomb (just throws RuntimeException):

package com.kenmcwilliams.kensocketchat.action;

import com.opensymphony.xwork2.ActionSupport;

public class Bomb extends ActionSupport{
    @Override
    public String execute() throws Exception{
        throw new RuntimeException("Hello from Exception!");
    }
}

View for bomb (/WEB-INF/content/bomb.jsp) [Never reachable]

<%@taglib prefix="s" uri="/struts-tags"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>The Bomb!</title>
    </head>
    <body>
        <h1>The Bomb!</h1>
    </body>
</html>

View for error (/WEB-INF/content/error.jsp)

<%@taglib prefix="s" uri="/struts-tags"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Global Error Handler</title>
    </head>
    <body>
        <h1>Global Error Handler</h1>
        <s:property value="exception.stackTrace"/>
    </body>
</html>

Output:

I see error.jsp render and I see the following printed on the glassfish console:

INFO: Is exception null: false
INFO: Hello from Exception!
like image 58
Quaternion Avatar answered Dec 02 '22 15:12

Quaternion


I was having the same issue you were having!

While it is much cleaner to have the exception field populated by Struts, it appears that this field population is not happening (as you stated). To get around this, I removed the exception field (and its getter/setter) from my Exception handler class (your myErrorProcessor class) and replaced that with a method to "manually" extract the exception from the ValueStack:

/**
 * Finds exception object on the value stack
 * 
 * @return the exception object on the value stack
 */
private Object findException() {        
    ActionContext ac = ActionContext.getContext();
    ValueStack vs = ac.getValueStack();
    Object exception = vs.findValue("exception");       

    return exception;
}

I can then use instanceof to make sure the exception Object is the type that I was expecting and then handle that exception accordingly (like writing messages found in a custom Exception object to a database).

Let me know how this works for you! :)

like image 21
icats Avatar answered Dec 02 '22 17:12

icats