In my application I have added interceptor to filter the request.Here each user is associated with a list of menu. So if user try to access page which is not associated to him then we will redirect him to unauthorizedUser.jsp page otherwise we will let the user to access the page.
Here is my interceptor code ...
#Override
public String intercept(ActionInvocation actionInvocation) throws Exception {
String returnAction = "unauth.user";
Map<String, String> keyValMap = FCCommonUtils.getALLMenuIdMap();
ActionContext context = actionInvocation.getInvocationContext();
HttpServletRequest request = (HttpServletRequest) context.get(StrutsStatics.HTTP_REQUEST);
HttpSession session = null;
if (request != null) {
session = request.getSession(false);
String contextPath = request.getContextPath();
contextPath = contextPath + RequestURIDtls.SEPERATOR;
String reqURI = request.getRequestURI().substring(contextPath.length(), request.getRequestURI().length());
String requestedRole = keyValMap.get(reqURI);
if (requestedRole != null && session != null) {
UserInfoUIForm userForm = (UserInfoUIForm) session.getAttribute(WebConstants.USER_INFO);
if (userForm != null) {
List<Long> userRoleLst = FCCommonUtils.getmenuids(userForm.getRoleId());
if (userRoleLst.contains(new Long(requestedRole))) {
//TODO : GUNJAN : NEED TO DO R&D WHY actionInvocation.invoke() CREATES NULL POINTER EXCEPTION
//returnAction=actionInvocation.invoke();
returnAction = "success";
} else {
returnAction = "unauth.user";
}
} else {
returnAction = "unauth.user";
}
} else {
returnAction = "unauth.user";
}
} else {
returnAction = "unauth.user";
}
return returnAction;
}
In above code returnAction=actionInvocation.invoke() gives null pointer exception.
Here is my struts.xml configuration to access the page ..
<action name="viewCorporate" class="com.ndil.web.corporate.MstCorporateAction" method="viewCorporatePage">
<interceptor-ref name="menuFilterInterceptor" />
<result name="unauth.user">/jsp/unAuthUser.jsp</result>
<result name="success">/jsp/mngCorporate.jsp</result>
</action>
Can any one suggest me why actionInvocation.invoke() gives null pointer exception ???
Thanks,
Gunjan Shah.
Free code review.
1) Intercept result decared as variable, unused.
2) Said value should be a constant anyway.
3) Variable is named incorrectly--it's not an action name, it's a result name.
4) If you're in an interceptor, you've gotten a request--there's just no way for this to be null. If it is null, something far more serious than an unauthorized user has occurred, and the world should blow up.
5) Similarly, unless you've specifically configured your entire app not to create sessions, checking for a session is redundant. If you don't, something has gone horribly wrong. Check for known session attributes to determine if a user is logged in, not for the presence of the session itself--much easier.
IMO both 4 and 5, if handled at all, should be handled with declarative exceptions. In that state, the web app is likely inoperable--peg the user to HTTP 500 or similar.
6) The nested conditionals are way too deep. Strict adherence to "one return per method" creates difficult-to-understand code, particularly when a method has deeply-nested conditionals.
7) It looks like you're relying on form data to determine the user's role. This is inherently insecure; user role information should be kept in the session, where it can't be easily manipulated.
8) Some miscellaneous tweaks leave us with this:
public class FooInterceptor {
private static final String UNAUTHORIZED_USER = "unauth.user";
public String intercept(ActionInvocation actionInvocation) throws Exception {
ActionContext context = actionInvocation.getInvocationContext();
HttpServletRequest request = (HttpServletRequest) context.get(StrutsStatics.HTTP_REQUEST);
if (request == null) {
return UNAUTHORIZED_USER;
}
HttpSession session = request.getSession(false);
if (session == null) {
return UNAUTHORIZED_USER;
}
Long requestedRole = getRequestedRole(request);
if (requestedRole == null) {
return UNAUTHORIZED_USER;
}
UserInfoUIForm userForm = (UserInfoUIForm) session.getAttribute(WebConstants.USER_INFO);
if (userForm == null) {
return UNAUTHORIZED_USER;
}
List<Long> userRoles = FCCommonUtils.getmenuids(userForm.getRoleId());
return userRoles.contains(requestedRole) ? ActionSupport.SUCCESS : UNAUTHORIZED_USER;
}
private Long getRequestedRole(HttpServletRequest request) {
String contextPath = request.getContextPath() + RequestURIDtls.SEPARATOR;
String reqURI = request.getRequestURI().substring(contextPath.length(), request.getRequestURI().length());
try {
return Long.valueOf(FCCommonUtils.getALLMenuIdMap().get(reqURI));
} catch (NumberFormatException e) {
return null;
}
}
}
While testing the method remains relatively difficult, it's much easier to understand the precise testing needs. It's easier to read, because you no longer have to wonder "what if the opposite is true?" as in the deeply-nested code.
Use this:
<action name="viewCorporate" class="com.ndil.web.corporate.MstCorporateAction" method="viewCorporatePage">
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name="menuFilterInterceptor" />
<result name="unauth.user">/jsp/unAuthUser.jsp</result>
<result name="success">/jsp/mngCorporate.jsp</result>
</action>
Struts won't add this default interceptor automatically when you are explicitly specifying a interceptor for action declaration.
Related
I am trying to redirect unhandled exceptions to an error page by first generating a crash code and then redirecting the user to the error page. The problem is that after the first time it happens, the original url/action is permanently mapped to the error page url and doesn't even enter the OnException method anymore. Even after I fix the cause of the original exception, the url/action is still mapped to just redirect to the error page. Not sure where this is happening or how to fix it. Below is the code:
public class UnhandledExceptionFilter : IExceptionFilter
{
protected static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public void OnException(ExceptionContext filterContext)
{
var ex = filterContext.Exception;
if (ex != null)
{
string user = "";
try
{
user = Managers.UserManager.FindByUsername(filterContext.HttpContext.User.Identity.Name, true).FullName;
}
catch
{
}
var url = filterContext.HttpContext.Request.Url.ToString();
var urlRef = filterContext.HttpContext.Request.UrlReferrer.PathAndQuery;
var randStr = StringExtensions.GenerateRandomAlphaNumerics(new System.Random(), 4);
Logger.Error(ex, "crashCode={0} - user={1} - url={2} - urlRef={3}", randStr, user, url, urlRef);
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
filterContext.ExceptionHandled = true;
filterContext.Result = new RedirectResult("/misc/whoops?crash_code=" + randStr, true);
}
}
}
The problem should be solved by not passing true to the second parameter of RedirectResult. Pass false instead:
filterContext.Result = new RedirectResult("/misc/whoops?crash_code=" + randStr, false);
Passing true as the second parameter indicates the redirect is permanent. Which is, based on your question, exactly want you do not want.
See: MSDN RedirectResult and
301 vs 302 redirects
I think I have come across a bug in spring-session but I just want to ask here if it really is a bug. Before I forget
https://github.com/paranoiabla/spring-session-issue.git
here's a github repository that reproduces the problem. Basically I have a 2 controllers and 2 jsps, so the flow goes like this:
User opens http://localhost:8080/ and the flow goes through HomepageController, which puts 1 attribute in the spring-session and returns the homepage.jsp which renders the session id and the number of attributes (1)
The homepage.jsp has this line inside it:
${pageContext.include("/include")}
which calls the IncludeController to be invoked.
The IncludeController finds the session from the session repository and LOGs the number of attributes (now absolutely weird they are logged as 0) and returns the include.jsp which renders both the session id and the number of session attributes (0).
The session id in both jsps is the same, but somehow after the pageContext.include call the attributes were reset to an empty map!!!
Can someone please confirm if this is a bug.
Thank you.
Problem
The problem is that when using MapSessionRepository the SessionRepositoryFilter will automatically sync the HttpSession to the Spring Session which overrides explicit use of the APIs. Specifically the following is happening:
SessionRepositoryFilter is obtaining the current Spring Session. It caches it in the HttpServletRequest to ensure that every invocation of HttpServletRequest.getSession() does not make a database call. This cached version of the Spring Session has no attributes associated with it.
The HomepageController obtains its own copy of Spring Session, modifies it, and then saves it.
The JSP flushes the response which commits the HttpServletResponse. This means we must write out the session cookie just prior to the flush being set. We also need to ensure that the session is persisted at this point because immediately afterwards the client may have access to the session id and be able to make another request. This means that the Spring Session from #1 is saved with no attributes which overrides the session saved in #2.
The IncludeController obtains the Spring Session that was saved from #3 (which has no attributes)
Solution
There are two options I see to solving this.
Use HttpSession APIs
So how would I solve this. The easiest approach is to stop using the Spring Session APIs directly. This is preferred anyways since we do not want to tie ourselves to the Spring Session APIs if possible. For example, instead of using the following:
#Controller
public class HomepageController {
#Resource(name = "sessionRepository")
private SessionRepository<ExpiringSession> sessionRepository;
#Resource(name = "sessionStrategy")
private HttpSessionStrategy sessionStrategy;
#RequestMapping(value = "/", method = RequestMethod.GET)
public String home(final Model model) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
final String sessionIds = sessionStrategy.getRequestedSessionId(request);
if (sessionIds != null) {
final ExpiringSession session = sessionRepository.getSession(sessionIds);
if (session != null) {
session.setAttribute("attr", "value");
sessionRepository.save(session);
model.addAttribute("session", session);
}
}
return "homepage";
}
}
#Controller
public class IncludeController {
private final static Logger LOG = LogManager.getLogger(IncludeController.class);
#Resource(name = "sessionRepository")
private SessionRepository<ExpiringSession> sessionRepository;
#Resource(name = "sessionStrategy")
private HttpSessionStrategy sessionStrategy;
#RequestMapping(value = "/include", method = RequestMethod.GET)
public String home(final Model model) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
final String sessionIds = sessionStrategy.getRequestedSessionId(request);
if (sessionIds != null) {
final ExpiringSession session = sessionRepository.getSession(sessionIds);
if (session != null) {
LOG.error(session.getAttributeNames().size());
model.addAttribute("session", session);
}
}
return "include";
}
}
You can simplify it using the following:
#Controller
public class HomepageController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public String home(HttpServletRequest request, Model model) {
String sessionIds = request.getRequestedSessionId();
if (sessionIds != null) {
final HttpSession session = request.getSession(false);
if (session != null) {
session.setAttribute("attr", "value");
model.addAttribute("session", session);
}
}
return "homepage";
}
}
#Controller
public class IncludeController {
#RequestMapping(value = "/include", method = RequestMethod.GET)
public String home(HttpServletRequest request, final Model model) {
final String sessionIds = request.getRequestedSessionId();
if (sessionIds != null) {
final HttpSession session = request.getSession(false);
if (session != null) {
model.addAttribute("session", session);
}
}
return "include";
}
}
Use RedisOperationsSessionRepository
Of course this may be problematic in the event that we cannot use the HttpSession API directly. To handle this, you need to use a different implementation of SessionRepository. For example, another fix is to use the RedisOperationsSessionRepository. This works because it is smart enough to only update attributes that have been changed.
This means in step #3 from above, the Redis implementation will only update the last accessed time since no other attributes were updated. When the IncludeController requests the Spring Session it will still see the attribute saved in HomepageController.
So why doesn't MapSessionRepository do this? Because MapSessionRepository is based on a Map which is an all or nothing thing. When the value is placed in the map it is a single put (we cannot break that up into multiple operations).
I've come across a weird problem in my MVC4 (RC) application. (running on .NET 4.0)
I have just setup Elmah for logging exceptions / errors.
I basically installed the Elmah.MVC and elmah.sqlserver NuGet packages. (versions 2.0.0 and 1.2 respectively)
It seemed to work fine out of the box - I can go to the elmah page and view errors:
http://myserver/elmah
for example, if I create some 404 errors, they appear in this log.
What is not working is this: I have a standard MVC controller with a [HttpPost] action. I've set it up so it will always throw an exception:
public class TestController : Controller
{
[HttpPost]
[ValidateInput(false)]
public void Testing()
{
throw new Exception("uh oh");
}
}
I then try to post data to this controller via jQuery:
$.post('/Test/Testing', {test_data: 'This is some test data'});
Ok, this works. The response returns the typical yellow screen of death, and the error is caught and logged in Elmah.
However, if I try to post something like XML/HTML the error is not logged in Elmah. I still get the same response from the server back (yellow screen of death), but nothing in Elmah.
$.post('/Test/Testing', {test_data: '<test><test1>This is some test data</test1></test>'});
Why? It doesn't make sense.
Notice I have already turned off the request validation on the action. If I didn't do that, then posting XML/HTML data would cause this exception:
A potentially dangerous Request.Form value was detected from the client
NuGet would also refuse to log that exception too - which I believe is a bug:
http://code.google.com/p/elmah/issues/detail?id=217
So what is the cause of this problem that I'm experiencing? It it a bug related to the issue I found above?
It just seems quite an unfortunate situation that I can't log exceptions just because the request contained XML/HTML.
Surely there is a way around this?
ELMAH does not catch HttpRequestValidationException by default and if a user sends an invalid request it will be missed in ELMAH's report. so it's necessary to define and use this global filter as well:
public class ElmahRequestValidationErrorFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (context.Exception is HttpRequestValidationException)
ErrorLog.GetDefault(HttpContext.Current).Log(new Error(context.Exception));
}
}
I have a work around for now, which someone suggested on http://code.google.com/p/elmah/issues/detail?id=217
You can force ASP to use the older request validation logic by adding this into the <system.web> section of your Web.config:
<httpRuntime requestValidationMode="2.0" />
This will effect the app globally which is not really that great of a situation.
If anyone has something better, please let me know.
Looks like this was fixed after the current release (as of this writing) 1.2.2. I ended up explicitly logging the error in the log. This won't go through the normal processing as unhandled exceptions (email notifications, etc) but it will log it to the error log.
catch (Exception ex)
{
var newEx = new HttpException(500, "Kaboom!!!", ex);
// Adding this explicit ELMAH logging because of ELMAH bug:
// https://code.google.com/p/elmah/issues/detail?id=217
// Waiting for a new ELMAH release with the fix
var elmahError = new Elmah.Error(newEx);
var elmahLog = Elmah.ErrorLog.GetDefault(HttpContext.ApplicationInstance.Context);
elmahLog.Log(elmahError);
throw newEx;
}
There was a bug in Elmah that was fixed a while back, but I don't believe any of the builds on the google code site are recent enough to include it.
As I mentioned in my comment, the issue is that if you access data through the model (MVC) or control (webforms), no problem; but if you access request.Form["inputId"] (which Elmah does when building one object), it throws an exception every time.
What I did:
Export the google code project to my github account
Clone the new repo to my workstation using VS2013
Select the NETFX 4.5 build configuration
Build the solution, and run the demo
Test my case in the demo by adding the following to default.aspx:
Run some tests
After I was happy with my tests, I added the new dlls to my solution by:
Removing the current Elmah.dll reference
Copy/paste Elmah.dll, Elmah.AspNet.dll, and AntiXssLibrary.dll to a 'lib' folder in my solution
Add references to the three new dlls
Update web.config so that it looks like the demo's web.config (things are very similar, but different (Elmah = Elmah.AspNet))
Remove the Elmah.mvc reference (it caused problems for me)
Remove filters.Add(new HandleErrorAttribute()); from FilterConfig (this allows custom errors to continue to work)
In the Elmah source, the crucial piece is the use of request.Unvalidated in HttpRequestValidation.cs
With thanks to Amarisi for the code, I've reworked it into my Global.asax.cs files Application_Error method to log all the exceptions Elmah fails to log.
It looks like this (edited down but should work):
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
// ELMAH will not log exceptions arising from POST requests
// when the form values contains 'potentially dangerous' chars
// e.g. html tags
// Adding this explicit ELMAH logging because of ELMAH bug:
// https://code.google.com/p/elmah/issues/detail?id=217
// Waiting for a new ELMAH release with the fix
// get form object
System.Collections.Specialized.NameValueCollection RequestForm = Request.Unvalidated.Form;
// get combined text of form values
string formValues = string.Join(",", RequestForm.AllKeys.Select(key => RequestForm[key]));
// is this the type of POST that Elmah fails on?
if (formValues.Contains("<"))
{
// log the exception manually
var elmahError = new Elmah.Error(exception);
var elmahLog = Elmah.ErrorLog.GetDefault(Context);
elmahLog.Log(elmahError);
}
}
}
Contains("<") isn't the best test. I doubt it covers everything that Elmah fails to log.
Would love to see if anyone can improve this further.
You can get Elmah to fire by throwing an HttpException instead of a normal exception.
Our solution was to wrap our controller action code with a try/catch block, and in the catch block wrap the exception that is thrown by your code in an HttpException and throw that instead. Like this:
[HttpPost]
[ValidateInput(false)]
public ActionResult MyAction(FormCollection riskyData)
{
try
{
//... your code here ...
}
catch (Exception ex)
{
throw new HttpException(500, "Internal Server Error", ex);
}
}
Add the following classes to your project:
public class ElmahErrorLogModuleFix : ErrorLogModule
{
protected override void LogException(Exception e, HttpContext context)
{
if (e == null)
throw new ArgumentNullException("e");
ExceptionFilterEventArgs args = new ExceptionFilterEventArgs(e, (object)context);
this.OnFiltering(args);
if (args.Dismissed)
return;
ErrorLogEntry entry = (ErrorLogEntry)null;
try
{
//FIX STARTS
//Error error = new Error(e, context);
Error error = CreateErrorSafe(e, context);
//FIX ENDS
ErrorLog errorLog = this.GetErrorLog(context);
error.ApplicationName = errorLog.ApplicationName;
string id = errorLog.Log(error);
entry = new ErrorLogEntry(errorLog, id, error);
}
catch (Exception ex)
{
Trace.WriteLine((object)ex);
}
if (entry == null)
return;
this.OnLogged(new ErrorLoggedEventArgs(entry));
}
public static Error CreateErrorSafe(Exception e, HttpContext context)
{
try
{
var safeFormCollection = new NameValueCollection();
var form = context.Request.Form;
var additionalMessage = string.Empty;
foreach (var key in form.AllKeys)
{
try
{
safeFormCollection.Add(key, form[key]);
}
catch (Exception)
{
safeFormCollection.Add(key, "_invalid input data_");
additionalMessage += "Form parameter with name=" + key + " has dangerous value. " + Environment.NewLine;
}
}
//if no invalid values in form then do as elmah does
if (string.IsNullOrEmpty(additionalMessage))
{
return new Error(e, context);
}
var exception = new Exception(additionalMessage, e);
var error = new Error(exception);
error.HostName = TryGetMachineName(context, null);
IPrincipal user = context.User;
if (user != null && NullString(user.Identity.Name).Length > 0)
error.User = user.Identity.Name;
HttpRequest request = context.Request;
//this._serverVariables = Error.CopyCollection(request.ServerVariables);
error.ServerVariables.Add(CopyCollection(request.ServerVariables));
if (error.ServerVariables != null && error.ServerVariables["AUTH_PASSWORD"] != null)
error.ServerVariables["AUTH_PASSWORD"] = "*****";
error.QueryString.Add(CopyCollection(request.QueryString));
error.Form.Add(CopyCollection(safeFormCollection));
error.Cookies.Add(CopyCollection(request.Cookies));
return error;
}
catch (Exception logEx)
{
return new Error(new Exception("Error when trying to process error catched by elmah", logEx));
}
}
/// <summary>
/// Elmah dll method in Environment.cs
/// </summary>
/// <param name="context"></param>
/// <param name="unknownName"></param>
/// <returns></returns>
public static string TryGetMachineName(HttpContext context, string unknownName)
{
if (context != null)
{
try
{
return context.Server.MachineName;
}
catch (HttpException ex)
{
}
catch (SecurityException ex)
{
}
}
try
{
return System.Environment.MachineName;
}
catch (SecurityException ex)
{
}
return NullString(unknownName);
}
/// <summary>
/// Elmah method in Mask.cs
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static string NullString(string s)
{
if (s != null)
return s;
else
return string.Empty;
}
/// <summary>
/// Elmah method in Error.cs
/// </summary>
/// <param name="collection"></param>
/// <returns></returns>
private static NameValueCollection CopyCollection(NameValueCollection collection)
{
if (collection == null || collection.Count == 0)
//FIX HERE: cannot allow reutrn null collection as elmah does, because of exception. fix as below
//return (NameValueCollection)null;
return new NameValueCollection();
//FIX ENDS
else
return new NameValueCollection(collection);
}
/// <summary>
/// Elmah method in Error.cs
/// </summary>
/// <param name="cookies"></param>
/// <returns></returns>
private static NameValueCollection CopyCollection(HttpCookieCollection cookies)
{
if (cookies == null || cookies.Count == 0)
//FIX HERE: cannot allow reutrn null collection as elmah does, because of exception. fix as below
//return (NameValueCollection)null;
return new NameValueCollection();
//FIX ENDS
NameValueCollection nameValueCollection = new NameValueCollection(cookies.Count);
for (int index = 0; index < cookies.Count; ++index)
{
HttpCookie httpCookie = cookies[index];
nameValueCollection.Add(httpCookie.Name, httpCookie.Value);
}
return nameValueCollection;
}
}
and
public class ElmahErrorMailModuleFix : ErrorMailModule
{
private bool _reportAsynchronously2;
protected override void OnInit(HttpApplication application)
{
base.OnInit(application);
IDictionary config = (IDictionary)this.GetConfig();
if (config == null)
return;
_reportAsynchronously2 = Convert.ToBoolean(GetSetting(config, "async", bool.TrueString));
}
protected override void OnError(Exception e, HttpContext context)
{
if (e == null)
throw new ArgumentNullException("e");
ExceptionFilterEventArgs args = new ExceptionFilterEventArgs(e, (object)context);
this.OnFiltering(args);
if (args.Dismissed)
return;
//FIX STARTS
//Error error = new Error(e, context);
Error error = ElmahErrorLogModuleFix.CreateErrorSafe(e, context);
//FIX ENDS
if (this._reportAsynchronously2)
this.ReportErrorAsync(error);
else
this.ReportError(error);
}
/// <summary>
/// Elmah method in ErrorMailModule.cs
/// </summary>
/// <param name="config"></param>
/// <param name="name"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
private static string GetSetting(IDictionary config, string name, string defaultValue)
{
string str = ElmahErrorLogModuleFix.NullString((string)config[(object)name]);
if (str.Length == 0)
{
if (defaultValue == null)
throw new global::Elmah.ApplicationException(string.Format("The required configuration setting '{0}' is missing for the error mailing module.", (object)name));
str = defaultValue;
}
return str;
}
}
These classes inherit from ErrorLogModule and ErrorMailModule and rewrite methods where the Error class is created, so that the HttpRequestValidationException exception will not raise.
Then add these to your Web.config:
<add name="ErrorLog" type="YourProject.SomeFolder.ElmahErrorLogModuleFix, YourProject" preCondition="managedHandler" />
<!--and for email module-->
to use these classes instead of the original ones. A bit of a dirty hack, but it works.
Credit goes to the poster of message #17 found here.
I use a Struts2 Convention plug-in to map my actions. Please, help me to solve the following problem. Here I have an action mapping
#Action(value="/{categorie:\\w+}/{hoofdgroep:\\w+}/{artikelgroep:\\w+}/", results = {
#Result(name="success", location="articlelist.jsp"),
#Result(name="maingroup", location="/%{categorie}/%{hoofdgroep}/", type="redirect"),
#Result(name="category", location="/%{categorie}/", type="redirect")
}, interceptorRefs = {
...
})
public String execute() throws Exception {
...
Category category = service.getCategory(categorie);
if (category == null) return NONE;
...
MainGroup mGroup = service.getMainGroup(hoofdgroep);
if (mGroup == null) return "category";
...
ArticleGroup artGroup = service.getArticleGroup(artikelgroep);
if (artGroup == null) return "maingroup";
...
return SUCCESS;
}
When, for instance, there is no artGroup for specified artikelgroep it should redirect link _http://site/categorie/hoofdgroep/artikelgroep/ to url _http://site/categorie/hoofdgroep/ which it perfectly does. The only problem here is that it also prepends additional parameters which are undesired. So link _http://site/categorie/hoofdgroep/artikelgroep/ is redirected to _http://site/categorie/hoofdgroep/?categorie=categorie&hoofdgroep=hoofdgroep&artikelgroep=artikelgroep.
My question is How to get rid of these parameters?
Here are some config params from my struts.properties file
...
struts.serve.static=false
struts.ognl.allowStaticMethodAccess=true
struts.enable.DynamicMethodInvocation=false
struts.action.extension= ,
struts.url.includeParams=none
struts.enable.SlashesInActionNames=true
struts.mapper.alwaysSelectFullNamespace=false
struts.patternMatcher=regex
struts.convention.default.parent.package=app-default
struts.convention.action.packages=...
struts.convention.action.alwaysMapExecute=false
struts.convention.package.locators.disable=true
struts.convention.relative.result.types=dispatcher
struts.convention.result.path=/WEB-INF/jsp/
So basically is this a bug or it should work this way?
Perhaps it is not so elegant solution but here what I have done. I overrode org.apache.struts2.dispatcher.ServletRedirectResult#getProhibitedResultParams
public class ServletRedirectResult
extends org.apache.struts2.dispatcher.ServletRedirectResult
{
public ServletRedirectResult() {
super();
initProhibitedResultParams();
}
public ServletRedirectResult(String location) {
super(location);
initProhibitedResultParams();
}
public ServletRedirectResult(String location, String anchor) {
super(location, anchor);
initProhibitedResultParams();
}
private List<String> prohibitedParamNames;
private void initProhibitedResultParams() {
String[] parentParams = (String[])super.getProhibitedResultParams().toArray();
int len = parentParams.length;
String[] params = new String[len + 4];
for (int i = 0; i < len; i++) {
params[i] = parentParams[i];
}
params[len] = "statusCode";
// TODO: This is a temporary solution because RegexPatternMatcher puts parameters
// from urls into ResultConfig for some reason.
params[len+1] = "categorie";
params[len+2] = "hoofdgroep";
params[len+3] = "artikelgroep";
prohibitedParamNames = Arrays.asList(params);
}
protected List<String> getProhibitedResultParams() {
return prohibitedParamNames;
}
}
What you describe is the default behaviour of both com.opensymphony.xwork2.util.NamedVariablePatternMatcher and org.apache.struts2.util.RegexPatternMatcher however it not the behaviour of com.opensymphony.xwork2.util.WildcardHelper (which is the default implementation)
From what you have shown the default implementation can handle what you are doing with far less headaches (regular wildcard matching).
Consulting this page: http://struts.apache.org/2.3.1.2/docs/wildcard-mappings.html
It states for "Parameters in namespaces" (I know you are not using this):
From Struts 2.1+ namespace patterns can be extracted as request
parameters and bound to the action.
However this equally applies to what is happening in the action and it really seems to be the only behaviour (where I would assume from "can be" that there would be another choice when it should have really been written as "... namespace/action patterns are extracted as request parameters...") and it seems to apply to the regex pattern matching equally, it would be nice for the documentation to more explicitly state this.
From your comments I can better understand what you are doing...
Why don't you simply set up three actions for:
*/*/*, */* and *
Then just pass the numbered parameters into the action?
I was digging into the code of org.apache.struts2.dispatcher.ServletRedirectResult#doExecute. Probably this piece prepends undesired parameters
ResultConfig resultConfig = invocation.getProxy().getConfig().getResults().get(invocation.getResultCode());
if (resultConfig != null)
{
Map<String, String> resultConfigParams = resultConfig.getParams();
for (Map.Entry<String, String> e : resultConfigParams.entrySet())
{
if (!getProhibitedResultParams().contains(e.getKey()))
{
String potentialValue = e.getValue() == null ? "" : conditionalParse(e.getValue(), invocation);
if (!suppressEmptyParameters || ((potentialValue != null) && (potentialValue.length() > 0)))
{
requestParameters.put(e.getKey(), potentialValue);
}
}
}
}
There is nothing wrong with this code. And the question is Why those three parameters appeared in the ResultConfig? Because it is working like, when you write like so
<result name="maingroup" type="redirect">
<param name="location">/${categorie}/${hoofdgroep}/</param>
<param name="namespace">/</param>
<param name="categorie">${categorie}</param>
<param name="hoofdgroep">${hoofdgroep}</param>
<param name="artikelgroep">${artikelgroep}</param>
</result>
I've developed a web site using Struts2 as a controller and integrated it with Spring and Hibernate to do the business logic and DB stuff. The website's URIs are http://my.domian.com/URI; which {URI} is dynamically generated thorough the admin cms. The mapping of each uri to the servlet are done with help of Apache mod_rewrite, as follow:
RewriteCond %{HTTP_HOST} ^www\.domain\.com
RewriteRule ^([a-zA-Z0-9_-]+)$ /dynamic\.action?f=$1 [QSA,L]
(Before any further information, is this a good and suitable approach?)
The struts configuration is just a typically-academic one as:
<package name="Default" extends="struts-default" namespace="/">
...
<action name="dynamic" class="DynamicContentAction">
<result name="index">/content/web/dynamic/index.jsp</result>
</action>
</package>
DynamicContentAction is extending ActionSupport and implementing ServletRequestAware, ServletContextAware. I'm checking a couple of things (such as a current visiting language which is identified as a subdomain), looking up in the database that the requested uri is valid or not, generating that uri's content and setting a couple of runtime global variables (such as current visiting page id, layout config due to current visiting language ...) and put it on a Request object in this servlet.
Everything looks good and even works perfectly ok, unless too many dynamic pages being requested by a single user concurrently. "Too Many" in my case is at least 9-10 pages. In this case it throws exceptions, different ones! Sometimes the HttpServletRequest request is null, sometimes ServletContext servletContext is null, some other times these are ok, but the runtime variables are null which is used in business logic or db querying.
I've googled about it and found out that this action is being instantiated "Per Request". Isn't this so? If there is an action per request, what's wrong with this conflict or "nullability thing". Should I do some thread-like thing in that action, beyond the threading of struts?
I'd be so appreciated if you could help me out or point me a direction.
Here is simplified version of DynamicContentAction.java
public class DynamicContentAction extends ActionSupport implements ServletRequestAware, ServletContextAware {
private HttpServletRequest request;
private ServletContext servletContext;
private ResourceSelectorService resourceSelectorService;
private String f = null;
public String execute() {
if ( f != null ) {
HashMap<String, Object> resolvedURI = resourceSelectorService.resolveURI(f);
if ( resolvedURI.get("ERROR").equals(true) ) {
//Generating nice 404 error page content
} else {
//Generating Content
//and put it on request object as:
//request.setAttribute("attrName", resourceContent);
}
}
else {
//Generating nice 404 error page content
}
request = null;
servletContext = null;
f = null;
return "index";
}
#Override
public void setServletRequest(HttpServletRequest request) {
this.request = request;
}
#Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
public void setF(String f) {
this.f = f;
}
public String getF() {
return f;
}
}
as I'm writing this post, I have came to the knowledge that this class is NOT thread-safe. Is it? So I've changed it a little bit as follow:
Here is newer version of DynamicContentAction.java
public class DynamicContentAction extends ActionSupport {
private ResourceSelectorService resourceSelectorService;
private String f = null;
public String execute() {
if ( f != null ) {
final HttpServletRequest request = ServletActionContext.getRequest();
final ServletContext servletContext = ServletActionContext.getServletContext();
HashMap<String, Object> resolvedURI = resourceSelectorService.resolveURI(f);
if ( resolvedURI.get("ERROR").equals(true) ) {
//Generating nice 404 error page content
} else {
//Generating Content
//and put it on request object as:
//request.setAttribute("attrName", resourceContent);
}
f = null;
}
else {
//Generating nice 404 error page content
}
return "index";
}
public void setF(String f) {
this.f = f;
}
public String getF() {
return f;
}
}
and the Null thing problem is almost gone, but there is still conflict with the generated content. For example if user try to open:
http:// www.domain.com/A
http:// www.domain.com/B
http:// www.domain.com/C
http:// www.domain.com/D
http:// www.domain.com/E
simultaneously, all of the pages will be rendered in browser, but the content of A is shown in A and B, C is correct, and there is a very good chance that the content of D and E are incorrect too.