I have Struts 2 actions with different (HTML and JSON ) result types. They use common interceptor.
If needed to intercept the request, how to return a result based on given action result type?
For example, my Action.ERROR forwards to JSP page. If action is JSON type I want to forward JSON error instead.
In Struts2 the action has not a type. This means that you cannot configure the type of the action. Instead you can configure result types in the xml configuration. In the xml configuration file this is defined as the result-type tag. When you configure the result using result tag you specify type attribute that will be used to determine the corresponding result type. Say name="success" or name="error" are results of the dispatcher result type.
When the action is intercepted you could get the results
Map<String, ResultConfig> results = actionInvocation.getProxy().getConfig().getResults();
In the ResultConfig there's className attribute that could be used to determine the type of the result.
I have Struts2 actions with different (HTML and JSON ) result types. They use common interceptor. If need to intercept the request, how to return result based on given action result type?
For example, my Action.ERROR forwards to JSP page. If action is JSON type I want to forward JSON error instead. Please advice.
Although is true that an Action has not a type, it's also true that, if an Action is called in an AJAX way, like an action returning JSON, all of its results should have the same result type (JSON in this case), unless you are using a single Action to perform different logical actions (ajax and non-ajax operations, that is an anti-pattern);
That said, if you want to return the proper GLOBAL error result, from inside an Interceptor that is used by all of your Actions (each one with its result type), based on their other result type (let's say: SUCCESS, assuming every Action has a SUCCESS result), this is the way to do it:
public String intercept(ActionInvocation invocation) throws Exception {
// Get the action configuration defined in struts.xml
ActionConfig config = invocation.getProxy().getConfig();
// Get the SUCCESS result configured for that Action
ResultConfig success = config.getResults().get("success");
// Get the class of the SUCCESS result
Object clazz = success.getClass();
/* .... oops, some error occurred !!
We now need to redirect to the right global error result .... */
if (clazz instanceof org.apache.struts2.dispatcher.ServletDispatcherResult) {
log.debug("Struts2 Result type: CLASSIC");
return "error";
} else if (clazz instanceof org.apache.struts2.json.JSONResult) {
log.debug("Struts2 Result type: JSON");
return "jsonError";
} else {
log.debug("Struts2 Result type: SOMETHING ELSE, returning default ");
return "error";
}
}
Although this is technically possible, i would discourage it because... there is no real reason to do it;
For your purpose, remember that each global result is scoped in its <package>;
Since you could (/should) have two different packages for Classic Actions (a <package> extending struts-default) and JSON Actions (a <package> extending json-default), you can simply define two different global error result for each package with the same name but different result type; this way the Interceptor will call the one relative to the package of the current Action, outputting the desired kind of result.
Related
First let me say that using Struts2 + Freemarker is a real blast.
Yet there's something is driving me crazy, because I cannot understand why it happens. I ask here as maybe someone else has an idea to share about it.
I've got an action, with a property.
Say
private String myText;
Then I've got a setter and a getter:
public void setMyText(String myText)
{
this.myText = myText;
}
public String getMyText()
{
if (myText == null)
myText = "(empty)";
return this.myText;
}
The result (in struts.xml) is a freemarker result.
So in my Freemarker template there's a line like the following:
<p>The text is: ${myText}</p>
Now consider I'm calling the action without any text parameter: say the url is
http:localhost:8080/myapp/myaction
As the getter provides a default value, when the action is processed and the result passed to my template, the property is set to the default; so I get (html on the browser side)
<p>The text is: (empty)</p>
If I call my action with the parameter set, instead (I mean with something like:
http:localhost:8080/myapp/myaction?myText=hallo
) things go wrong. Freemarker fires the following exception:
Exception occurred during processing request: For "${...}" content:
Expected a string or something automatically convertible to string
(number, date or boolean), but this has evaluated to a
sequence+extended_hash (String[] wrapped into f.e.b.ArrayModel)
It seems that "myText" is found twice...
What am I doing wrong? Or, at least, is there anyone that can explain to me why it happens?
P.S.: it's really found twice; the following is a way to workaround the problem:
<#if myText?is_sequence>${myText[0]}<#else>${myText}</#if>
Yet it seems to me not viable to wrap every variable in that way.
P.P.S.: a further hint: in the freemarker template there's a call to another action some lines before. Something like:
<#s.action var="innerAction" name="getTable" namespace="/foo" />
If I comment the line above, everything works fine.
The myText could be a variable from the freemarker context, but if you want to use action property
<p>The text is: ${action.myText}</p>
Note, that action prefix is not required to access action properties. A property resolution method is applied when resolving freemarker variables:
Property Resoloution:
Your action properties are automatically resolved - just like in a
velocity view.
for example ${name} will result in stack.findValue("name"), which
generally results in action.getName() being executed.
A search process is used to resolve the variable, searching the
following scopes in order, until a value is found :
freemarker variables
value stack
request attributes
session attributes
servlet context attributes
And later you can read what objects are accessible from the context.
Objects in the Context:
The following variables exist in the FreeMarker views
req - the current HttpServletRequest
res - the current HttpServletResponse
stack - the current OgnlValueStack
ognl - the OgnlTool instance
This class contains useful methods to execute OGNL expressions against arbitary objects, and a method to generate a select list using
the <s:select> pattern. (i.e. taking the name of the list property, a
listKey and listValue)
struts - an instance of StrutsBeanWrapper
action - the current Struts action
exception - optional the Exception instance, if the view is a JSP exception or Servlet exception view
The error might be caused by searches from the value stack and returning something that you didn't expect depending on the structure of the stack at the moment of execution.
Adding a prefix to the variable to point out the exact location of the property should fix the redundancy in the code when searching in the value stack.
I have searched and searched and this is destroying me. I have this:
<s:form method="post" action="%{methodOne}" cssClass="buttons">
The emailFormUrl returns the URL correctly but the parameters have been stripped.
public String methodOne() {
return anotherClass.methodTwo(id);
}
Which speaks of:
public static String methodTwo(
String id) {
return fastEncode("", "longurl/view.jsp",
new ParameterPairing("id", id));
}
For some reason, the id is being stripped, this leaves me with a validation error and doesn't complete the action that I require. As I am aware we did not have a problem with it before the July urgent security update but it is small functionality that is rarely used (an argument for its removal I guess).
I don't want to add a hidden parameter as I want to understand the reason that this is not working, not a workaround (I am still in the heavy learning part of my career).
In servlet environment the <s:form> tag uses ServletUrlRenderer class to render form url.
If configuration for the action specified in action attribute cannot be found then literal value (w/o parameters) of an action attribute will be used.
Note: you need to use action name w/o extension in order that it can be found in configuration. So some_action?foo=bar will be set with parameters in form if you have some_action in configuration, but some_action.action?foo=bar won't be found because of .action extension and parameters will be stripped.
My question is about the capabilities of Spring MVC.
#RequestMapping("SOME_VALUE")
RETURN_TYPE NAME_OF_FUNCTION (PARAMS){
STATEMENTS;
}
Is there a way where RETURN_TYPE is dependent on the statements?
For example: When a user clicks on a link, if a certain condition is true, I want the user to download a file (i.e. return a byte[]) but if it's false, I want to show the user a webpage with possible options (i.e. a ModelAndView object).
Now I understand that it can be simply implemented using javascript within the page but coming back to my originial question, is there a way in Spring MVC which does that for us with better handling?
Spring 3.2+ (I'm not sure about previous versions) works as follows. The handler method, #RequestMapping annotated, is invoked through reflection regardless of its return type. Spring receives its return value and dispatches a registered HandlerMethodReturnValueHandler to handle the return value if it supports it.
There are many types of HandlerMethodReturnValueHandler, for #ResponseBody, for ResponseEntity, for String, for ModelAndView, etc. You can see most of them in the javadoc.
So one, ugly way, to do this would be to define your method as
RequestMapping("SOME_VALUE")
public Object NAME_OF_FUNCTION (PARAMS){
if (something) {
return "a string";
} else if (somethingElse) {
return new ResponseEntity(new byte[] {1,2,3});
} else
return new OtherType();
}
Spring will use the first HandlerMethodReturnValueHandler for which supportsReturnType returns true.
In my opinion, you shouldn't do this. As much as possible, you should make the condition be external, ie come from the request. This way, you could map the condition in the #RequestMapping and have multiple #RequestMapping methods, one for each possible condition.
Instead of returning Object you can simply return String and set it to name of your JSP file (i.e. "myjsppage") when you want to return a page, or set it to redirect (i.e. "redirect:/mydownloadablefile") when you want to start downloading. For latter case you will also need to add another controller method with request mapping for "/mydownloadablefile" URL.
The default stack has the parms interceptor who binds the parameters to the setters found in the action who is being invoked. That works fine when you know the name of the parameter. For example if a request parameter is "employee.name" that could be binded to a property of my action which has a property "employee" with a property "name". In order to get it to work I need to know which name the request parameter name would be and put a setter in my action called setEmployee() of an object type Employee or it could be a Map too.
What if I want to let the action bind that param to another property that I don't know which one will be. Let's say that the action receive as a parameter the name on which the request parameter will be set.
<s:action name="showEmployee" executeResult="true">
<s:param name="employeePrefix">xyz.wz.empl</s:param>
</s:action>
That would mean to the action to bind all the parameters of the employee to xyz.wz.empl.
For example let's say the request parameter has the following:
xyz.wz.empl.name=Alfredo
xyz.wz.empl.lastName=Osorio
I would like to bind that to a property of my action, let's say a Map employee, but that won't work because the request parameter is xyz.wz.empl. How can I bind that dynamic parameter to the invoked action using the parameter that was sent to the action (employeePrefix).
I could ask for the request parameters
ActionContext.getContext().getParameters()
and do the conversion myself but I think there must be another way to explicitly call something from the Struts 2 framework to the conversion, in the way that com.opensymphony.xwork2.interceptor.ParametersInterceptor does.
Thank You.
This is not an ideal solution in my opinion but it will do what you need.
Your Struts 2 Action will need to implement SessionAware and ParameterAware interfaces.
Create a private field that is a map called 'parameters' and create a getter/setter for it.
Now when you call getParameters() you will have a map of your name/value pairs.
Now for this next portion you will need to use reflection.
So for example you will need to do this:
Map myParams = getParameters()
Employee e = new Employee()
Class clas = null;
try {
clas = Class.forName("Employee");
Field fieldlist[] = clas.getDeclaredFields();
for (int i = 0; i < fieldlist.length; i++) {
Field fld = fieldlist[i];
if(myParams.containsKey(fld.getName()))
fld.set(e, myParams.get(fld.getName())
}
} catch (ClassNotFoundException ex) {
// handle exception case
}
This should map all parameters in that map to a field in the employee object if it exists in the map. This can be of course ported out to a separate method that would belong in something like a Util class where you pass in the class you want to reflect on.
In one of my Struts action I've got the following code in a method:
...
List<Object> retrievedListOfObjects = c.getListOfObjects();
return mapping.findForward("view");
}
fw_view leads to a new Struts action with another Struts form. Let's say this form has got among others the following field
List<Object> listOfObjects;
I now want to pass the retrievedListOfObjects from within the first Struts action to the form of the following Struts action.
Is this possible without storing it in the session?
you can store it as a request attribute.
request.setAttribute("listOfObjects", listOfObjects);
and then in the Action that is forwarded to
List<Object> listOfObjects = (List<Object>)request.getAttribute("listOfObjects");
Given that when setting request attributes you can give them meaningful names, you should consider setting many attributes rather than setting one big list of objects.
Correction of krock code.
Setting object to request:
request.setAttribute("listOfObjects", listOfObjects);
Getting the object in an other action.
List<Object> listOfObjects = (List<Object>)request.getAttribute("listOfObjects");