Action Result is returning value (also known as result object) of an action method. Result object provides result string; that is it's toString() value. Result string is formed in the following way:
result string = <result_type>:<result_value>
result_type - optional result type id;result_value - result value, used for building result path.Result type defines which result type handler will process the result. When result type is omitted, the default one is used (defined in global Madvoc configuration).
Result value is used to build the result path. Result path is formed in the following way:
result path = <action_path_no_ext>.<result_value>
action_path_no_ext - action path with stripped extension;result_value - part of toString() result value.Result type handler may use single value or any combination of these values to process the action result: result object, result value and/or result path. This makes Madvoc very flexible in processing the results.
Actions may return result object of any type, not only String.
Actions may also return void, what will trigger the default result type handler with empty result value. Returning null has the same effect.
When result value starts with the '/' sign, Madvoc will take it as a full result path. In that case result path is equal to result value.
Servlet dispatching is the default result type. This result type handler appends 'jsp' extension to result path to build the page name.
If this page is not found, dispatcher result type handler will start removing one word from the result path starting from the end (that is not including the extension), until the page is found.
The hello world example:
@MadvocAction
public class HelloAction {
@Action
public String world() {
return "ok";
}
}This action is mapped to action path: /hello.world.html. Now, the results: since there is no explicit result type id, 'dispatch' is assumed. Result value is 'ok', so Madvoc will try to dispatch to following pages, in given order:
hello.world.ok.jsp /hello.world.jsp /hello.jsp Dispatcher result type handler will dispatch to the first founded page of above. If no pages is found, error 404 occurs.
Dispatcher detects if founded page is included, so finally it performs either include or straight forward to the target page.
Servlet redirection is simple: result value specifies the redirection URL. Usually, result value specifies full result path (starts with '/'):
@MadvocAction
public class HelloAction {
@Action
public String world() {
return "redirect:/index.html";
}
}Redirection URL may contain all necessary parameters within it. Speaking of which, Jodd provides nice utility for building
url's and encoding parameters: UrlEncoder.
Moreover, it is possible to inject action properties into the resulting url:
@MadvocAction
public class OneAction {
String value;
@Action
public String execute() {
value = "173";
return "redirect:/index.html?value=${value}";
}
}Invocation of action path: /one.html will perform the redirection to: /index.html?value=173.
Hard-coding URLs is not considered as good practice. Madvoc offers way to define aliases for the actions, to prevent URL hard-coding. This is done by using @Action's element 'alias' on target action and the referencing it by surrounding alias with '%' sign in calling action's result.
The previous example can be re-written in the following way:
@MadvocAction
public class IndexAction {
@Action(alias="index")
public void view() {
}
}
Calling action (/one.html)
@MadvocAction
public class OneAction {
String value;
@Action
public String execute() {
value = "173";
return "redirect:/%index%?value=${value}";
}
}Behavior is identical.
Aliases also may be registered manually during Madvoc configuration:
public class MyWebApplication extends WebApplication {
@Override
protected void init(MadvocConfig madvocConfig, ServletContext servletContext) {
madvocConfig.registerResultAlias("/hello.all", "/hi-all");
}
}
As seen above, aliases may be referenced in result value by surrounding alias name with '%', what is useful for redirection. Nevertheless, Madvoc will also consider every result path as an possible alias! So, previous configuration and the following action may change the result path:
@MadvocAction
public class HelloAction {
@Action
public void all() {
}
}Action is mapped to /hello.all.html. Result path of this action is /hello.all. (this is just a result path; appropriate result type handler adds the extension). So, Madvoc checks if this result path exists among aliases, and since it exist, result path will be replaced with alias value. In this example it means that dispatcher will forward to: /hi-all.jsp. This behavior is convinient for dispatch results.
Aliases functionality is part of Madvoc core, not result type handlers.
By default, result value is added to action path (without extension) to form the result path. Very often is necessary for an action to return to result path of another action, defined in the same action class. Following action illustrates this:
@MadvocAction
public class FormAction {
@Action
public void view() {
}
@Action
public String post() {
return "#";
}
}This is a common case in web applications. Some form is mapped to action path: /form.html. This action (method: view()) just prepares form for presentation. Second action is mapped to /form.post.html and serves as form handler that will be invoked when user submits a form. What is different now is that this action doesn't forward to new page (e.g. /form.post.ok.jsp). Because of special character used ('#') it strips a word from action path starting from its end. So, the result path is just: /form. Since default result type is 'dispatch', only the following page will be taken in account: /form.jsp. And those are the very same result paths and pages as for the first action.
Good practice is to introduce string constant with name 'BACK' and value "#".
Another example:
@MadvocAction
public class FooAction {
@Action
public String list() {
return "ok";
}
@Action
public String add() {
return "#list.ok";
}
}Similarly, first action (/foo.list.html) prepares some data for listing and dispatches to result page: /foo.list.ok.jsp. Now, the second action (/foo.add.html) adds new element in used collection but should return to the same page. Therefore, result path is changed from /foo.add.ok to /foo.list.ok, so Madvoc will dispatch to the very same page, showing all elements from collection, including the new one.
Using one prefix '#' character allows to remove method name from action path when building the result path. Speaking more generic, '#' prefix removes one word from action path. Having more '#' characters in the front will continue with the practice, allowing to overwrite class name as well:
@MadvocAction
public class FooAction {
@Action
public String hello() {
return "##boo.list.ok";
}
}This action is mapped to /foo.hello.html, but the result will be forwarded to /boo.list.ok.jsp.
While using one '#' character to override method name make sense since all logic stays in one action class, using double '#' is not consider as good practice and should be avoided if possible.
Overriding result path functionality is part of Madvoc core, not result type handlers.
In some (rare) cases it is necessary to use full action path when building the result path, i.e. not to strip action path extension. By default, Madvoc always strips extension from action path: the word after last dot. Sometimes, however, action is mapped to an action path without the extension.
For example, action path might be /search.suggest (resolved from action SearchAction#suggest()). As said, Madvoc would consider '.suggest' as the extension and would strip it before result path creation.
To prevent action path extension stripping, action should return a string that starts with a dot (.). That indicates Madvoc not to strip the extension when building the result path.
Chaining actions is similar to forwarding, except it is done by Madvoc and not by servlet container. Chain result type handler takes result value as the next action path. Chaining to the next action happens after the complete execution of first action, including all interceptors. The following example illustrates this result type:
@MadvocAction
public class HelloAction {
@Action
public String chain() {
return "chain:/hello.link.html";
}
@Action
public void link() {
}
}First action is mapped to the /hello.chain.html. After the execution of this action finishes, Madvoc will continue to the next action, /hello.link.html. When the send action is invoked, it is executed in the very same way as it would be if the request would come from the outside. There is no difference between chained execution and regular one.
When chaining, first action may send custom data to the next action by setting values in various scopes (request, session, etc).
Main problem with the redirection is the necessity of sending parameters through the URL as GET parameters. This means that it is needed to write the complete and properly formed url string. Although there are some helpers in Jodd for this, it is still not very maintainable and visual solution.
Move result type handler works similar as redirect one, except it stores the current action in the session before the redirection. After the redirection, Madvoc detects the stored (source) action and performs the outjection of its data to request attributes. Like that, all data from source action becomes visible (via servlet attributes) for the target action! This frees from hard-coding the parameters in the url string.
The best way to understand 'moving' is to compare it with the 'redirect'. The target action is a simple and has one input value (property 'value' annotated with @In):
@MadvocAction
public class TwoAction {
@In
String value;
@Action(alias = "two")
public void view() {
System.out.println(value);
}
}
Now, the caller action. This example will show two versions of an action: one that uses 'redirect' result, and the second that uses 'move' result:
@MadvocAction
public class OneAction {
String value;
@Action
public String execute() {
value = "173";
return "redirect:/%two%?value=${value}";
}
}
Version#2: action that uses moving to target
@MadvocAction
public class OneAction {
@Out
String value;
@Action
public String execute() {
value = "173";
return "move:/%two%";
}
}
Both actions work exactly the same. The difference is obvious: second example doesn't prepare url string and parameters, but just data.
As everything, this approach has its cons: there are no actual request parameters available for target action! After moving to the requested target page, stored action is immediately removed from the session. Therefore, any further target page reload will not have any available parameter. Also, if there is some part of code that explicitly depends on request parameters it will not work if it is executed during invocation of second, target action.
There are situations when data needs to be sent directly to the output stream of HTTP response. In that case, action method is responsible for sending the full response. Action also has to return 'none:', the result type that will not perform any additional result processing, since action is responsible for sending the result data back. This result type handler doesn't takes any result value and result value in result string may be omitted.
When working with direct responses, action needs direct access to the http request and response. Although they can be easily injected in action class instance, such class becomes 'hard-wired' with servlets interfaces: it can't be initialized easily outside the container, therefore it is less testable, and so on...
The purpose of result type handlers is to divide this process into two parts: preparing of data (in action class, by action method) and actual data transfer (in result type handler), leaving the action class 'clean' of http servlets.
Sending some raw results (image, file download...) is common example. Although raw content can be sent directly to the output using 'none' result type handler, it is not considered as a good practice. Action instead can just prepare the resulting byte array (raw data) that will be transferred back to the client by (custom) result type handler.
Now, Madvoc offers two approaches how to achieve this. The first one is quite obvious: action method may prepare byte array (or whatever the result is) and store it somewhere in its object. Action then returns result value that indicates somehow what field(s) contain prepared result data, so result type handler may use it for transferring back to the client. Something like:
@MadvocAction
public class RawAction {
byte[] bytes;
@Action
public RawResultData view() {
bytes = ... // create byte array somehow
return "foo:bytes";
}
}
Here some result type handler ('foo' in this example) may process the action object using reflection and reads all fields marked as holders of returned data ('bytes' in this example) and then transfer this data back. Of course, this is just one scenario; many variations are availiable, such as using annotations instead of result value etc.
Madvoc offers one more convenient solution. Instead of returning a string, action may return result object of any type! It is on result type handler to process result value, result path and/or result object how it likes. Until now, all result type handlers just used the result value (toString() of result object) or result path. But result type handler may go further and consider whole result object to prepare the result. The best way to understand this is to refactor the previous example using this approach.
Instead of storing byte array in the field of the action class, byte array can be wrapped into some custom data holder that will be returned from the action as result object. This data holder must have toString() overridden so Madvoc will invoke corresponding result type handler that knows how to deal with this data holder. Simple as this:
@MadvocAction
public class RawAction {
@Action
public RawResultData view() {
byte[] bytes = ... // create byte array somehow
return new RawResultData(bytes);
}
}
RawResultData is just a simple wrapper for byte arrays that has to be sent back to the client. It works together with 'raw' type handler, that recognizes and works with this type. RawResultData has overrided toString() method and returns just 'raw:'. All what action method has to do is to prepare byte array and return it as RawResultData.
Returning just string 'raw:...' will send result value string back to the client (maybe not very useful).
Custom result type handler has to extend ActionResult class. It has to provide result type name and to override render() method. Here is an real-life example of custom result type handler for generating JSON results.
Generating JSON from existing objects might be not so easy sometimes: some object values has to be omitted, or objects come from ORM mapper that uses lazy initialization, so using reflection and deep-scanning is not enough or may produce unespected results. Idea is to manually control what to serialize in action method. So, this example will use result objects, similar to 'raw' result types.
First thing is to create JsonData, wrapper for flexjson.JSONSerializer, or any other json serializer (simplified version):
public class JsonData {
private static final String RESULT = JsonResult.NAME + ':';
private static final String[] DEFAULT_JSON_EXCLUDES = new String[] {"class", "*.class"};
private final JSONSerializer jsonSerializer;
private final Object target;
public JsonData(Object target) {
this(target, true);
}
public JsonData(Object target, boolean excludeDefault) {
this.target = target;
jsonSerializer = new JSONSerializer();
if (excludeDefault == true) {
jsonSerializer.exclude(DEFAULT_JSON_EXCLUDES);
}
}
public JsonData include(String... includes) {
jsonSerializer.include(includes);
return this;
}
public JsonData exclude(String... excludes) {
jsonSerializer.exclude(excludes);
return this;
}
public String toJsonString() {
return jsonSerializer.serialize(target);
}
@Override
public String toString() {
return RESULT;
}
}
Here is the JsonResult result type handler (simplified version):
public class JsonResult extends ActionResult {
public static final String NAME = "json";
public JsonResult() {
super(NAME);
}
@Override
public void render(ActionRequest request, Object resultObject, String resultValue, String resultPath) throws Exception {
if (resultObject instanceof JsonData == false) {
return;
}
// write output
HttpServletResponse response = request.getHttpServletResponse();
response.setContentType(MimeTypes.MIME_TEXT_PLAIN);
PrintWriter writer = null;
try {
writer = response.getWriter();
writer.println(((JsonData) resultObject).toJsonString());
} finally {
StreamUtil.close(writer);
}
}
}
And the usage:
@MadvocAction
public class HelloAction {
@Action
public JsonData view() {
Object foo = ... // create object somehow
return new JsonData(foo, true).exclude("address");
}
}
Following table summarize default behavior of ResultMapper - Madvoc component
dedicated for building result paths from results and action path.
| action path (no extension) | result value | result path |
| * | /foo | /foo |
| * | /foo.ext | /foo.ext |
| /zoo/boo.foo | ok | /zoo/boo.foo.ok |
| /zoo/boo.foo | doo.ok | /zoo/boo.foo.doo.ok |
| /zoo/boo.foo | # | /zoo/boo |
| /zoo/boo.foo | #ok | /zoo/boo.ok |
| /zoo/boo.foo | #doo.ok | /zoo/boo.doo.ok |
| /zoo/boo.foo | (void) or (null) | /zoo/boo.foo |
| /zoo/boo.foo | ##ok | /zoo/ok |
Following table summarize default behavior of ResultMapper when action path extension should not be stripped from the result path.
| action path with extension | result value | result path |
| /zoo/boo.foo.ext | ok | /zoo/boo.foo.ok |
| /zoo/boo.foo.ext | .ok | /zoo/boo.foo.ext.ok |
| /zoo/boo.foo.ext | . | /zoo/boo.foo.ext |
| /zoo/boo.foo | ok | /zoo/boo.ok |
| /zoo/boo.foo | .ok | /zoo/boo.foo.ok |
| /zoo/boo.foo | . | /zoo/boo.foo |
| /zoo/boo | ok | /zoo/boo.ok |
| /zoo/boo | .ok | /zoo/boo.ok |