Results (cont.)
More features and topics about Results.
Aliases
Hard-coding URLs and method names in result values is not considered as a good practice. Madvoc offers way to define path aliases to prevent hard-coding. Aliases can be defined in following ways: in action annotation or manually in Madvoc configuration.
Either way defined, aliases may be used in result value, surrounded by
<
and >
signs.
Aliases are also useful for marking 'open' points for integration with 3rd party code. You may mark some important action method with alias name and then allow 3rd party code to use it.
Aliases defined in annotation
Aliases may be defined using @Action
's element alias
on target
action.
One of the previous examples can be re-written in the following way:
// Target action (/index.html) @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}"; } }
Alias is defined in the IndexAction
class: alias name is index
and value equals to the action path: /index.html
.
Therefore, behavior of the OneAction#execute
action remains identical.
Aliases are convenient for redirect:
results.
Default aliases
Besides explicit aliases, every Madvoc action has it's default alias. Default alias is named from actions class and method name:
default_alias = <action class name> + '#' + <action method name>
This way you can reach to any action method in your application.
Aliases defined by ActionsManager
Aliases also may be registered manually by using ActionsManager
component:
actionsManager.registerPathAlias("/hello.all.html", "/hi-all");
This gives even more flexibility! You can rewrite the whole result path and make action go to totally different target JSP.
Overriding action path within result path
By default, result path consist of action path and result value. 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(extension = NONE) 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
(no extension)
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.jsp
). Because of special prefix characters used
(#
) it strips a word from action path starting from its end. So,
the result path is just: /form
. And this means that the both actions
will share the same resulting page, e.g. /form.jsp
.
Think about #
as a 'BACK' command for actions path.
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 this collection, but
should return to the same page. Therefore, result path is changed from
/foo.add.html.ok
to /foo.list.ok
. Madvoc will dispatch to the very
same page, showing all elements from collection, including the new one.
Note that we had to use two #
signs to move two segments back in action path.
We had to skip both extension (html
) and method name (add
) in action path
to get to the same root: /foo
.
When using #
there is one more thing to know. As we explained, result path
consist of action path and result value. Dispatcher result uses both to
find matching JSP. For dispatcher it is important to know which part is
action part (and that can be reduced) and which part is result value
(that can be appended during finding matched JSP). When you return #
to override the action path, everything after that sign is considered
to be the action path! Consider the following hello-world example:
@MadvocAction public class HelloAction { @Action public String world() { return ...; } }
What we have here is an action mapped to: /hello.world.html
. If we return
"ok"
, that string will be result value.
Dispatcher would try to match following JSPs:
/hello.world.html.ok.jsp
, /hello.world.html.jsp
,
/html.world.ok.jsp
, /hello.world.jsp
… In other words, result value
remains the same while action path is getting shorten.
But if we return #ok
then only action path is going to be modified and
result value would be null
. Dispatcher would just try to match following:
/hello.world.ok.jsp
, /hello.world.jsp
… That might be different
from what expected. In order to specify the result value while using
the #
you need to add additional dot to separated value from path.
So if we return #.ok
the action path will be reduced but result value
would be "ok"
. Dispatcher would try to match following JSPs:
/html.world.ok.jsp
, /hello.world.jsp
…
Result path cheat-sheet
Following table summarize default behavior of ResultMapper
- Madvoc
component dedicated for building result paths from results and action
path.
action path | 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 | #.ok | /zoo/boo.ok 1 |
/zoo/boo.foo | #doo.ok | /zoo/boo.doo.ok |
/zoo/boo.foo | #doo..ok | /zoo/boo.doo.ok 1 |
/zoo/boo.foo | (void) or (null) | /zoo/boo.foo |
/zoo/boo.foo | ##ok | /zoo/ok |
Custom result names
Custom result handler has to extend ActionResult
class. Since there are many
ways how Madvoc resolves action result, you may configure your implementation
to match how you plan to use it.
That being said, you may provide result name if you want your action result
handler to be specified in strings of actions returned values. Or you
can specify a type if you your handler is just for certain returning type.
Or you can omit specifying either and use your action result in annotation
or Result
class.
In any case, your action result must to implement the render()
method.
Here is an real-life example of custom result 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 unexpected results. Idea is to manually control what to serialize in action method. So, this example will use result objects, similar to 'raw' result names.
First thing is to create JsonData
, wrapper for
our JsonSerializer
or any other JSON serializer (simplified version):
@RenderWith(JsonResult.class) public class JsonData { private final JsonSerializer jsonSerializer; private final Object target; public JsonData(Object target) { this.target = target; jsonSerializer = new JsonSerializer(); } 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); } }
Here is the code for JsonResult
action result handler (simplified version):
public class JsonResult extends BaseActionResult<JsonData> { @Override public void render( ActionRequest request, JsonData jsonData) throws Exception { // write output HttpServletResponse response = request.getHttpServletResponse(); response.setContentType(MimeTypes.MIME_TEXT_PLAIN); PrintWriter writer = null; try { writer = response.getWriter(); writer.println(jsonData.toJsonString()); } finally { StreamUtil.close(writer); } } }
And the usage:
@MadvocAction public class HelloAction { @Action public JsonData view() { Object foo = ... // get object somehow return new JsonData(foo).exclude("address"); } }
This example covers usage of custom result, and we used @RenderWith
annotation.
Better approach
Maybe better approach would be not to have JsonData
wrapper and instead just
return objects that needs to be rendered into JSON:
@MadvocAction public class HelloAction { @Action(result = JsonResult.class) public Book view() { Book foo = ... // get object somehow return foo; } }
Now your action is clean of custom wrapper and your JsonResult
may
serialize whatever is returned by the action. Simple as that ;)