struts2struts-validationstruts2-interceptors

execAndWait Interceptor not redirecting to success page after waiting


I have a login screen where some user input validation will happen, the user will be authenticated and finally redirected to the welcome screen.

Below is the interceptor definition for the LoginAction:

<package name="default" extends="struts-default" namespace="/">
    <interceptors>  
        <interceptor name="myInterceptor" 
            class="com.interceptor.MyInterceptor"></interceptor>

        <interceptor-stack name="newStack">
            <interceptor-ref name="myInterceptor"/>             
            <interceptor-ref name="defaultStack" />
            <interceptor-ref name="execAndWait">
                <param name="delay">100</param>
                <param name="delaySleepInterval">500</param>
            </interceptor-ref>              
         </interceptor-stack> 
    </interceptors>

    <action name="login"
        class="com.action.LoginAction"> 
        <interceptor-ref name="newStack"/>
        <result name="success">common/Welcome.jsp</result>
        <result name="wait">common/wait.jsp</result>
        <result name="error">Login.jsp</result>
        <result name="input">Login.jsp</result>
    </action>
</package>

Below is the execute method of LoginAction:

   if (isUserAuthenticated) {
        // Some background processing for logging purpose           
        return "success";
    } else {            
        addActionError(getText("error.login"));
        return "error";
    }

I am having a couple of problems with this code:

1) For an authenticated user, the wait.jsp page is getting displayed but the redirection to Welcome.jsp is not happening.

2) for an unAuthenticated user, I am getting the below exception:

java.lang.NullPointerException
at com.opensymphony.xwork2.util.LocalizedTextUtil.findText(LocalizedTextUtil.java:361)
at com.opensymphony.xwork2.TextProviderSupport.getText(TextProviderSupport.java:208)
at com.opensymphony.xwork2.TextProviderSupport.getText(TextProviderSupport.java:123)
at com.opensymphony.xwork2.ActionSupport.getText(ActionSupport.java:103)
at com.infy.action.LoginAction.execute(LoginAction.java:19)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:450)
at com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:289)
at org.apache.struts2.interceptor.BackgroundProcess$1.run(BackgroundProcess.java:57)
at java.lang.Thread.run(Thread.java:662)

Solution

    1. execAndWait causes the action to be executed in a new thread.
    2. Since ActionContext is ThreadLocal, the new thread will not get the values stored in the parent thread version of ActionContext. Every thread has a unique version of ActionContext
    3. getText() will throw a NPE when it tries to execute in the new thread because it depends on ActionContext

    To fix this, you need to copy the parent threads ActionContext into the execAndWait thread. You can do this by extending the BackgroundProcess class, implementing the beforeInvocation() and afterInvocation() methods, and extending ExecuteAndWaitInterceptor, implementing the getNewBackgroundProcess() method.

    EXAMPLE

    public class YourExecAndWaitInterceptor extends ExecuteAndWaitInterceptor {
    
        private static final long serialVersionUID = 1L;
    
    
        /**
         * {@inheritDoc}
         */
        @Override
        protected BackgroundProcess getNewBackgroundProcess(String arg0, ActionInvocation arg1, int arg2) {
            return new YourBackgroundProcess(arg0, arg1, arg2, ActionContext.getContext());
        }
    
    }
    
    
    
    public class YourBackgroundProcess extends BackgroundProcess {
    
        private final ActionContext context;
    
        public YourBackgroundProcess(String threadName, ActionInvocation invocation, int threadPriority, ActionContext context) {
            super(threadName, invocation, threadPriority);
            this.context = context;
         }
    
        /**
         * {@inheritDoc}
         */
        @Override
        protected void beforeInvocation() {
            ActionContext.setContext(context);
        }
    
        /**
         * {@inheritDoc}
         */
       @Override
        protected void afterInvocation() {
            ActionContext.setContext(null);
        }
    
    }