5

In an effort to understand how struts2 loads its configuration I wanted to display the path to the JSP which would be rendered. Given the following very minimal struts.xml:

<struts>
    <constant name="struts.devMode" value="true" />
    <constant name="struts.ui.theme" value="simple" />

    <package name="base" namespace="/">
        <result-types>
            <result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>
        </result-types>
        <action name="test" class="com.kenmcwilliams.badwebapp.action.Test">
            <result>/WEB-INF/content/test.jsp</result>
        </action>
    </package>
</struts>

I want to be able to log "/WEB-INF/content/test.jsp" from within the action. Given the following action:

package com.quaternion.badwebapp.action;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.interceptor.PreResultListener;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Test extends ActionSupport {
    //used for a sanity test on JSP
    public String getMessage() {
        return "From test";
    }

    @Override
    public String execute() throws Exception {
        System.out.println("ActionContext.getContext().getActionInvocation().getResultCode(): " + ActionContext.getContext().getActionInvocation().getResultCode());
        ActionInvocation ai = ActionContext.getContext().getActionInvocation();
        ai.addPreResultListener(new PreResultListener() {
            @Override
            public void beforeResult(ActionInvocation invocation, String resultCode) {
                try {
                    System.out.println("PreResultListener resultCode: " + resultCode);
                    System.out.println("PreResultListener result: " + invocation.getResult());
                } catch (Exception ex) {
                    Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
        return SUCCESS;
    }
}

There are three print statements which produce the following output on my console:

INFO:   ActionContext.getContext().getActionInvocation().getResultCode(): null
INFO:   PreResultListener resultCode: success
INFO:   PreResultListener result: null

From testing both the result "invocation.getResult()" and the resultcode is null before the PreResultListener is called but within the PreResultListener the resultcode is set, yet the result still returns null!

From the JavaDoc of the getResult() method:

If the ActionInvocation has been executed before and the Result is an instance of {@link ActionChainResult}, this method will walk down the chain of ActionChainResult's until it finds a non-chain result, which will be returned. If the ActionInvocation's result has not been executed before, the Result instance will be created and populated with the result params.

Seems pretty clear that a result instance is not being created.

So how do I display "/WEB-INF/content/test.jsp" within this action? This is not for typical struts2 use, I'm want to test a configuration provider for which there is something wrong with the construction of the result for the action, hopefully understanding why this isn't working will let me fix that too.

Quaternion
  • 10,380
  • 6
  • 51
  • 102
  • Do you have this test project as SSCCE ? +1, it is interesting – Andrea Ligios Sep 06 '13 at 08:25
  • If you email me (should be on my profile, use the gmail one) I'll send you a copy. – Quaternion Sep 06 '13 at 15:05
  • 1
    `ActionInvocatio#getResult` is null because action returned "success" instead of instance of Result and then there be no null. I know, it isn't documented but actions can return Result directly ;-) – Lukasz Lenart Sep 07 '13 at 05:52
  • What the hell! I'm going to test this... being who you are I'm sure you're right but this is a revelation to me. – Quaternion Sep 07 '13 at 18:42
  • @LukaszLenart Right in [this](http://stackoverflow.com/a/35869461/573032) way? – Roman C Mar 10 '16 at 12:20
  • 2
    @RomanC yes, see this https://github.com/apache/struts/blob/master/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java#L475-L485 and then this https://github.com/apache/struts/blob/master/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java#L188-L193 – Lukasz Lenart Mar 11 '16 at 12:26

3 Answers3

5

The problem is that you want to get result from the action invocation, you shouldn't. The action invocation result is for internal use, and should probably be protected.

To get the result you should consult the ActionConfig and get result from there.

ActionInvocation invocation = ActionContext.getContext().getActionInvocation();
ActionProxy proxy = invocation.getProxy();
ActionConfig config = proxy.getConfig();
Map<String, ResultConfig> results = config.getResults();
ResultConfig resultConfig = results.get(Action.SUCCESS);
String lastFinalLocation = null;
Map<String, String> params = resultConfig.getParams();
if (resultConfig.getClassName().equals("org.apache.struts2.dispatcher.ServletDispatcherResult")) {
  lastFinalLocation = params.get("location");
}
System.out.println("location: " + lastFinalLocation);
Roman C
  • 49,761
  • 33
  • 66
  • 176
  • Yes well the problem is I built the action config with a custom configuration provider, with this configuration the actions I provide are found, but the result is null... hmm you did give me something to try... – Quaternion Sep 06 '13 at 14:50
  • 1
    It worked! The resultBuilder takes has a "location" parameter. I thought this was the result path! Instead I was just supposed to supply it as a parameter. Mapping the xml to the classes isn't very well documented. So now the custom configuration provider is working... well partially. Seems I need the location parameter for some reason... when I don't set it I get a "org.apache.jasper.JasperException: The Struts dispatcher cannot be found" but even if I construct it with new LocationImpl("", "") the action is happy. Oh well, one question per, well question. – Quaternion Sep 06 '13 at 14:58
  • 1
    This is great... except I want to modify the resultConfig params dynamically, and it's an UnmodifiableMap - so I tried buildig a new ResultConfig and sticking it in the config.getResults() map - but it is an UnmodifiableMap too. – dmansfield Sep 22 '14 at 14:26
  • @dmansfield If you have a question you can ask it by the [Ask Question](http://stackoverflow.com/questions/ask). – Roman C Sep 22 '14 at 15:25
2

A couple of things:

  1. At the moment in time where you are trying to print the + getResultCode() no such code exists yet - remember it's the action which will determine the result by returning the result string. So you'll want to print that in any of the interceptors you have in that action, after the invocation.invoke() part.

  2. getResultCode() will return the result string (success, error) and not the corresponding path.

Aleksandr M
  • 24,264
  • 12
  • 69
  • 143
mmalmeida
  • 1,037
  • 9
  • 27
  • Just as I was going to sleep I realized that the "resultCode" was being displayed before the action. Although it _is_ the result which I want to render (well just a non null address value would be reassuring). It makes perfect sense that the action needs to be called before the result code can be known however, it is the result which I want not the resultcode. Than you for the help, I could have been clearer. – Quaternion Sep 06 '13 at 15:01
1

ActionContext.getContext().getActionInvocation().invokeActionOnly() will return string (success, input, error, etc).

Aleksandr M
  • 24,264
  • 12
  • 69
  • 143
Magu
  • 11
  • 1