Removing convention.annotation - replace with config in struts.xml - struts2

I'm having an issue removing the #Action and #Result Convention plugin annotations from an action and replacing them with the equivalent config in struts.xml.
package com.microed.cars.web;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Result;
import com.opensymphony.xwork2.ActionSupport;
public class HomeAction extends ActionSupport {
#Action(results = {
#Result(location = "/jsp/home.jsp")
})
#Override
public String execute() throws Exception {
return super.execute();
}
}
When these annotations are there, I can successfully access localhost:port/context/home.action
When I remove the annotations I get 'no result defined for action..... ' struts error, despite there being a 'capture all' result in struts.xml - the entire struts.xml is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.1//EN"
"http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
<constant name="struts.devMode" value="true"/>
<constant name="struts.convention.package.locators" value="web"/>
<constant name="struts.convention.default.parent.package" value="beetroot"/>
<package name="beetroot" extends="json-default">
<action name="home" class="homeAction">
<result>/jsp/home.jsp</result>
</action>
<action name="cars" class="baseCarsAction">
<result name="input" type="json">
<param name="root">autoResults</param>
/jsp/home.jsp
</result>
</action>
</package>
</struts>
It extends json-default because I need the json result type for an autocomplete function.
I don't know why it's not picking up the action mapping for the homeAction class. I know struts.xml is being read because if I remove the action mapping "cars" then the autocomplete is disabled (but this needs the annotations which I'm trying to remove in order to validate this).
I know that 'no result defined' is a simple error, usually caused by spelling/capitalization errors but this is definitely not the case here, it's simply seems to be ignoring the whole "home" action mapping.
When stepping through DefaultActionInvocation.createResult, there are no 'results' at all for it to try to match against.

As it stands the cars action declaration isn't valid (nor does it make sense, IMO):
<action name="cars" class="baseCarsAction">
<result name="input" type="json">
<param name="root">autoResults</param>
<param name="location">/jsp/home.jsp</param>
</result>
</action>
That said: if it's a JSON result, a JSP isn't helpful, and it'll be ignored (or downright rejected, I'm not sure if it's an error or not). A single result will be either JSON, or HTML.
Turn logging up to DEBUG level to catch startup errors to narrow the range of possible causes.
If baseAction is configured in your Spring config file (which is unnecessary if you're using Spring annotations for injection) the configuration for the home action is valid.
I'd be wary of deploying the convention plugin if you're not actually using it: it changes how actions are mapped; it may have an impact on the surrounding application and cause problems. Stick with one or the other, avoid both–it makes it harder to reason about the source of application behavior.
Unrelated, but I recommend putting JSP pages under /WEB-INF to disallow direct client access.

Related

How does the namespace attribute in a Struts2 package work?

I am still confused how to properly use the namespace attribute in Struts2.
In the namespace configuration, it was mentioned that:
Namespaces are not a path!
Namespace are not hierarchical like a file system path. There is one namespace level. For example if the URL /barspace/myspace/bar.action is requested, the framework will first look for namespace /barspace/myspace. If the action does not exist at /barspace/myspace, the search will immediately fall back to the default namespace "". The framework will not parse the namespace into a series of "folders". In the Namespace Example, the bar action in the default namespace would be selected.
I have tried making a simple Struts2 sample:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="true" />
<package name="default" extends="struts-default">
<action name="defaultIndex">
<result name="success">/pages/default.jsp</result>
</action>
</package>
<package name="package1" namespace="/" extends="struts-default">
<action name="index1">
<result name="success">/pages/home1.jsp</result>
</action>
</package>
<package name="package2" namespace="/namespace1" extends="struts-default">
<action name="index2">
<result name="success">/pages/home2.jsp</result>
</action>
</package>
<package name="package3" namespace="/namespace1/namespace2" extends="struts-default">
<action name="index3">
<result name="success">/pages/home3.jsp</result>
</action>
</package>
</struts>
Where SampleDynamicWebProject is the context root.
Based on the documentation, if I try
.../SampleDynamicWebProject/randomText/defaultIndex
Then, Struts2 will look for the /randomText namespace and check for the defaultIndex action. If it doesn't exist, then it will look for the default namespace which is the package with no namespace attribute.
But if I try this URL:
.../SampleDynamicWebProject/namespace1/randomText/index2
Struts2 should look at the /namespace1/randomText namespace for the index2 action and if it cannot see one, then it should look at the default namespace. However, the URL above is still directed at the index2 action in the /namespace1.
The same thing is happening when I try
.../SampleDynamicWebProject/randomText/index1
The index1 in the root namespace is invoked.
Can you please clarify how exactly it works?
You're definitely right, nowhere in the documentation is mentioned that if a namespace is part of the URL, it is enough to make the whole thing work.
But it does, and here is how it works in detail:
To parse an URL and call the appropriate namespace and action, Struts2 uses an ActionMapper:
The ActionMapper interface provides a mapping between HTTP requests and action invocation requests and vice-versa.
There are different implementations, but the default one is the DefaultActionMapper.
When an URL is analyzed, the parseNameAndNamespace() method is invoked. If it's not the default "" nor the root "/" one, and the alwaysSelectFullNamespace attribute is set to false (that is the default), then the available namespaces are scanned:
The magic is in this line:
if (ns != null
&& prefix.startsWith(ns)
&& (prefix.length() ==ns.length() || prefix.charAt(ns.length()) == '/')) {
if (ns.length() > namespace.length()) {
namespace = ns;
}
///...
}
name = uri.substring(namespace.length() + 1);
In your example:
/namespace1/randomText/index2
prefix = /namespace1/randomText/
ns = /namespace1
In your case it is not null, the URI starts with the namespace, the length is not the same BUT the character after the /namespace1 String in the URI is a slash, then the action mapper decides that:
namespace = /namespace1
action = randomText/index2
This is why the namespace is taken. But then why it works, since the action name is index2 and not randomText/index2 ?
The second magic is in this other line:
if (!allowSlashesInActionNames) {
int pos = name.lastIndexOf('/');
if (pos > -1 && pos < name.length() - 1) {
name = name.substring(pos + 1);
}
}
If the allowSlashesInActionNames attribute is set to false (that is the default) and the action contains slashes (like in randomText/index2), then strips everything up to the last slash, transforming randomText/index2 in index2.
Then as long as you have an URI that is starting with a namespace and ending with an action, no matter what's in the middle, it works.
It should not work instead using /randomText/namespace1/index2, because no namespace is starting with /randomText.

Variable at start of namespace (or equivalent)

I am trying to create URLs that look like http://localhost:8080/app/client/shared-namespaces-and-actions:
“/app” would be the context root.
“client” would actually be a variable, replaced by the actual client version/interface of the application, such as
“john” (i.e.:
http://localhost:8080/app/john/namespaces-and-actions).
“/shared-namespaces-and-actions” would be packages/actions
accessible under one or more clients (such as “/about/contact”, or
“/products/73/edit”).
Ideally, a URL could entirely omit the “client” component, and so that for actions where it makes sense some sort of combined view could be displayed. For example “/app/” would display links to the client versions, whereas the client’s “/app/client/” would have what that client wants on their homepage.
This variable “client” component is what I have a question about.
Thus far, I have been using a query string parameter to handle the client selection, rather than part of the URL path. However, this has some obvious issues for things like bookmarks, as I always have to pass the client parameter in the query string (not attractive), else bookmarks will fail.
Is there an easy way to make this change? It seems like what I want is a wildcard in the namespace, as I do with actions, but the documentation doesn't seem to support that as a possibility, nor did experimentation. I already use packages to set namespaces, interceptor stacks, and to group related actions in the struts.xml file, so it would not be trivial to change all actions to be under the "/" namespace package with an additional wildcard for the client.
An option I was looking at was to extend the class DefaultActionMapper, and override the method parseNameAndNamespace(String, ActionMapping, ConfigurationManager), removing the client component of the URI (if it exists), and then passing that modified URI to the parent implementation. However this has a number of issues, such as breaking all links, form targets, and redirects. I expect fixing the links/forms would be annoying to fix, but not impossible, but I'm not sure if the same would be true of redirects.
I am using 2.3.16.3.
struts.xml:
<struts>
<constant name="struts.enable.SlashesInActionNames" value="true" />
<constant name="struts.mapper.alwaysSelectFullNamespace" value="false" />
<constant name="struts.patternMatcher" value="namedVariable"/>
<constant name="struts.action.extension" value="" />
<package
name="poc-default"
extends="struts-default"
strict-method-invocation="true">
<default-interceptor-ref name="defaultStack" />
<default-action-ref name="http404" />
<action
name="http404"
class="poc.DefaultAction">
<result name="success">/WEB-INF/http404.jsp</result>
</action>
</package>
<package
name="root"
namespace="/"
extends="poc-default"
strict-method-invocation="true">
<action
name=""
class="poc.DefaultAction">
<result name="success">/WEB-INF/home.jsp</result>
</action>
<action
name="clients"
class="poc.DefaultAction">
<result name="success">/WEB-INF/client-select.jsp</result>
</action>
</package>
</struts>
Using the struts.xml above, "http://localhost:8080/poc/" goes to the home page, and "http://localhost:8080/poc/clients" goes to the client selection page.
I then replace the package "root" with the following (added named wildcard to namespace):
<package
name="root"
namespace="/{uriClientString}/"
extends="poc-default"
strict-method-invocation="true">
<action
name=""
class="poc.DefaultAction">
<result name="success">/WEB-INF/home.jsp</result>
</action>
<action
name="clients"
class="poc.DefaultAction">
<result name="success">/WEB-INF/client-select.jsp</result>
</action>
</package>
Now, neither action is mapped by the URLs "http://localhost:8080/poc/john/" and "http://localhost:8080/poc/john/clients".
Having given Dave Newton's comment some thought, I believe that he is correct that I was going about solving my problem (optional client selector prefix to URL) is probably not how I should be going about this.
URL rewriting seems like a better (and possible) choice, and instead putting the client into context via an attribute of the request. I have created a filter that appears before the Strut 2 filter in web.xml:
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
throws ServletException, IOException {
final HttpServletRequest httpRequest = (HttpServletRequest) request;
final String requestServletPath = httpRequest.getServletPath();
// Obtain set of client strings here.
final List<String> clients = Arrays.asList("john");
String newServletPath = null;
for (String client : clients) {
final String toCheck = "/" + client;
if (requestServletPath.equals(toCheck) || requestServletPath.startsWith(toCheck + "/")) {
/*
* Put client into context here, {#link HttpSession#setAttribute(String, Object)} or
* {#link ServletRequest#setAttribute(String, Object)}.
*/
request.setAttribute("uriClientString", toCheck);
newServletPath = requestServletPath.substring(toCheck.length());
break;
}
}
if (newServletPath != null)
request.getRequestDispatcher(newServletPath).forward(request, response);
else
chain.doFilter(request, response);
}
It was also necessary Struts 2's filter mapping to support forwards:
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
Unfortunately, it is necessary to rewrite the generated URLs to include the client prefix if it is being used, since otherwise Struts2 will just start from the actual namespace:
<s:a namespace="%{#request.uriClientString}" action="">Home page</s:a>
Finally, it is also necessary to rewrite the redirects to include the client prefix if it is being used, since, once again, Struts2 will just start from the actual namespace:
<action name="re">
<result type="redirectAction">
<param name="namespace">${#request.uriClientString}</param>
<param name="actionName">clients</param>
</result>
</action>
This is not as easy to implement as I was hoping at the outset, but it does seem like it will meet my needs once everything is updated.
I will accept this in a couple of days if nothing better is suggested.

In Struts2 HOWTO handle a url with same namespace but with or without the trailing slash similarly

In struts2 I am writing an app where I need to make sure that the url redirection works the same whether or not there is a trailing slash at the end.
E.g. example.com/app should behave same way as if user entered example.com/app/. Currently I changed mapping in struts.xml like so -
<struts>
<package name="default" namespace="/" extends="secure">
<interceptors> ... <interceptors>
<action name="app">
<result type="redirectAction">/app/</result>
</action>
</package>
</struts>
and
<struts>
<package name="app" namespace="/app" extends="secure">
<interceptors> ... <interceptors>
<action name="" class="com.example.action.app.LoginAction" method="display">
<interceptor-ref name="store">
<param name="operationMode">RETRIEVE</param>
</interceptor-ref>
<interceptor-ref name="basic" />
<result type="redirectAction">home</result>
<result name="display">/jsp/base/content/login/loginForm.jsp</result>
</action>
</package>
</struts>
But this seems hackish since if I go to example.com/app it will show example.com//app/.html in the URL.
Any help appreciated.
Answer was derived in comments under the answer.
Quaternion:
Personally I would write all my urls with out the trailing slash...
and then I would use something external to the application to rewrite
urls as appropriate, perhaps iptables could determine if there is a
trailing slash and if so always strip it.
Mohana Rao SV:
As suggested above follow without tailing slash. And override
StrutsPrepareAndExecuteFilter one of the filter job is from the url it
has to identify the namespace and action name and invoke respective
action. So here remove tailing slash from url.
Quaternion:
In namespace "/" you have an action called app. That is all there is
to it to invoke CONTEXT_ROOT/app (that is what struts2 expects), you
don't ever expect to see a "/" on the end of the url, so you want to
find a method that parses the url before struts2 resolves the mapping.
What you have described only requires something to remove a trailing
"/" if it exists. I'd look to iptables because I've used it before or
some other url rewriter... Mahana would keep it all part of the web
app and use a filter, methods differ but the effect is the same.

struts2 action not calling properly

On default I want my struts2 app to forward to an action:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
<constant name="struts.devMode" value="false" />
<package name="myApp" namespace="/myApp" extends="struts-default">
<action name="Login_*" method="{1}" class="myApp.SessionManager">
<result name="input">/myApp/Login.jsp</result>
<result type="redirectAction">Menu</result>
</action>
</package>
<package name="default" namespace="/" extends="struts-default">
<default-action-ref name="index" />
<action name="index">
<result type="redirectAction">
<param name="actionName">Login_input.action</param>
<param name="namespace">/myApp</param>
</result>
</action>
</package>
</struts>
I'm looking for the application to call SessionManager.input(), but instead it calls SessionManager.execute().
You don't want typically a public input() method. The most basic and typical scenario for an Action is:
The Action is intended to "do something" (one thing), and this action needs some user input.
The Action does than something in the method execute()
If the Action did succesfully that something, it returs SUCCESS. This triggers a result informative JSP page.
If the Action didn't get the user input (because there wasn't any, or because it was insufficient or wrong) it returns INPUT. This triggers a "input form" JSP so that the user can fill the data and (re)try the action.
Now, this basic scenario can be coded in several ways, among them:
1) Two different mappings, one for the input form, other for the execution
<!-- default action class: execute does nothing, returns SUCCES -->
<action name="ActionXXXShowForm">
<result>/myApp/XXXForm.jsp</result>
</action>
<action name="ActionXXX" class="myApp.XXXAction">
<result name="input">/myApp/XXXForm.jsp</result>
<result>/myApp/XXXDone.jsp</result>
</action>
2) Just one mapping
<action name="ActionXXX" class="myApp.XXXAction">
<result name="input">/myApp/XXXForm.jsp</result>
<result>/myApp/XXXDone.jsp</result>
</action>
Both are very similar (specially if your are doing programmatic validation).
In both cases, we have only a "struts" method in the action (execute), which is good practice as our action only "does one thing".
But in the second case, we need to deal with the case in which no data is post, and dont emit an error message for that case.
Example: In case 1:
public class XXXAction extends ActionSupport {
...
public String execute() throws Exception {
if(! inputOk()) return INPUT;
// .. do your stuff
return SUCCESS;
}
private boolean inputOk() {
// ... check your inputs - sort of programatic validation
if( surname == null ) addActionError("no surname");
// .... more checks
return ! hasActionErrors());
}
}
In case 2 you modify it slightly:
public class XXXAction extends ActionSupport {
....
public String execute() throws Exception {
if( emptyInput() ) return INPUT; // no error messages in this case
if(! inputOk()) return INPUT;
// .. do your stuff
return SUCCESS;
}
private boolean inputOk() {
// same as before
}
private boolean emptyInput() {
// detect no input was sent. do not emit errors herer
}
}
When you call an actions from jsp, the default method is execute(). If you want to call another method, you can spec by the attribute method="".
<s:url action="SessionManager" method="input"/>
The attribute method is common on other tags.
If you are using a <s:a... tag, then you will not be able to call the intended method apart from the default execute() method. Instead of <s:a... tag, you need to define the action using an <s:url... tag with an id and call the same using normal html anchor tag ie.,
<a href=${<<id>>} but you may need to specify the method in your struts.xml or any other xml configuration file, which you have included in your struts.xml file with the method attribute in your <action... method="..." clause.

Redirecting URL using struts2

How can i redirect www.mysite.com/12345 to www.mysite.com/action?id=12345 using struts2?
I use URL rewriting to get these kind of flexible mappings working (though you could probably do it in struts proper, possibly with your own interceptor or something). There's a great little project, urlrewritefilter that gets the job done. In your URL rewriting configuration you'd have a rule like:
<rule>
<from>^/(\d+)$</from>
<to>/action?id=$1</to>
</rule>
Have a look at the manual to see if it is what you are looking for.
<action name="12345">
<result type="redirect-action">
<param name="actionName">action</param>
<param name="id">12345</param>
</result>
</action>
UPDATE
Ok. Based on the comment below.
I have managed something like this in this way in the past. Create a package in struts with a catch all action.
<package name="numbers">
<action name="*" class="my.package.ActionClass" method="urlrewrite">
<result type="redirect-action">
<param name="actionName">${nextpage}</param>
<param name="id">${id}</param>
</result>
</action>
</package>
Then in the urlrewrite method of the action class:
public String urlrewrite(){
//parse the url and determine the ID and the next page
nextpage = "action";
id = "12345";
return SUCCESS;
}
So in calling the action you will have to do like this:
http://www.mysite.com/numbers/12345.action
If you do not want a new package then you can do this in the default package.

Resources