I'm working on a JSF web application in which I need to bring up a "Session Expired" page if the view expires, but a general technical error page for all others. The application only goes to the technical error page when I trigger the exception. Here's the error-page definitions:
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/jsps/utility/sessionExpired.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/jsps/utility/technicalError.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/jsps/utility/technicalError.jsp</location>
</error-page>
I removed the technicalError.jsp error page elements and it worked fine, but when I put them back I can't get to the sessionExpired.jsp page. How do I tell the web container the order to evaluate these tags so that the right page comes up? Thanks.
This is because the ViewExpiredException is been wrapped in a ServletException as per the JSF specification. Here's an extract of chapter 10.2.6.2 of the JSF 1.2 specification:
10.2.6.2 FacesServlet
Call the execute() method of the saved Lifecycle instance, passing the
FacesContext instance for this request as a parameter. If the execute() method
throws a FacesException, re-throw it as a ServletException with the
FacesException as the root cause.
How the error pages are allocated is specified in Servlet API specification. Here's an extract of chapter 9.9.2 of Servlet API specification 2.5:
SRV.9.9.2 Error Pages
If no error-page declaration containing an exception-type fits using the
class-hierarchy match, and the exception thrown is a ServletException or
subclass thereof, the container extracts the wrapped exception, as defined by
the ServletException.getRootCause method. A second pass is made over the error
page declarations, again attempting the match against the error page
declarations, but using the wrapped exception instead.
In class hierarchy, ServletException already matches Throwable, so its root cause won't be extracted for the second pass.
To prove this specified behaviour, replace javax.faces.application.ViewExpiredException by javax.servlet.ServletException as <exception-type> and retry. You'll see the expected error page being displayed.
To solve this, simply remove the error page on java.lang.Throwable or java.lang.Exception. If no one exception specific error page matches, then it will fall back to the one for error code of 500 anyway. So, all you need is this:
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/jsps/utility/sessionExpired.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/jsps/utility/technicalError.jsp</location>
</error-page>
Update: as per the (deleted) comment of the OP: to reliably test this you cannot do a throw new ViewExpiredException() in a bean constructor or method or so. It would in turn get wrapped in some EL exception. You can eventually add a debug line printing rootCause in the Filter to see it yourself.
If you're using Eclipse/Tomcat, a quick way to test ViewExpiredException is the following:
Create a JSF page with a simple command button, deploy and run it and open it in webbrowser.
Go back to Eclipse, rightclick Tomcat server and choose Clean Tomcat Work Directory. This will restart Tomcat and trash all serialized sessions (important! just restarting Tomcat is not enough).
Go back to webbrowser and press the command button (without reloading page beforehand!).
As mentioned by BylusC, deployment descriptor must not contain any error-page handler that will catch ViewExpiredException (wrapped inside ServletException) instead of the correct one - error-page for ViewExpiredException.
Do not forget to verify that server's deployment descriptor (e.g. TomEE/conf/web.xml) does not contain java.lang.Throwable or java.lang.Exception error-page definitions. Because these two web.xml are basically merged.
Yes - application's web.xml has precedence over server's web.xml, but if application's web.xml contains
error-page for 500 (/error.xhtml)
and server's web.xml for
500 (/500.html) and for
Throwable (/bigerror.html)
then application context will contain merge of those two:
500 (/error.xhtml)
Throwable (/bigerror.html)
and ViewExpiredExcpetion error handling will not work correctly.
Related
I'm exploring the new features in JSF 2.2 (pretty cool so far) but I still don't understand how Protected Views works, I created a facelet1 with a link to facelet2, like this:
<h:link styleClass="link" value="Go to protected page" id="link1"
outcome="/protected/facelet2.xhtml"></h:link>
and in my faces-config.xml I added this:
<protected-views>
<url-pattern>/protected/facelet2.xhtml</url-pattern>
</protected-views>
Now when I run the page a token is added in the url:
http://localhost:8080/<project>/protected/facelet2.faces?javax.faces.Token=1426608965211
According to the documentation, if the token does not match with the one in the server, the GET request is not processed (is my understanding correct?).
But if I modify the token (using Firebug or the dev tools included in the browser) the request is still processed, even if the token was modified.
Am I doing something wrong?
This is caused because your FacesServlet is apparently mapped on JSF 1.0-style URL pattern of *.faces instead of JSF 2.0-style URL pattern of *.xhtml. The <protected-views><url-pattern> must match the actual URL pattern as you see in browser's address bar.
So, given an actual URL of /protected/facelet2.faces, you need to configure it as below:
<protected-views>
<url-pattern>/protected/facelet2.faces</url-pattern>
</protected-views>
However, while at it, I discovered some nasty issues in current Mojarra 2.2.10 implementation:
It does actually not do a prefix/suffix match as per Servlet 12.1 specification (there's even a vague comment in source code indicating that!). It does merely an exact match. This means, you can't use wildcard URL patterns like /protected/*.
When generating the <h:link>, it does not compare the protected view URL pattern to the resolved URL, but to the JSF view ID. And, when checking an incoming request, it does not compare the request URL to the JSF view ID (like as during link generation), but to the <url-pattern>. This thus never matches, totally explaining why you could simply access it without a valid token.
Basically, you need the following configuration if you need to keep the JSF 1.0-style URL pattern of *.faces.
<protected-views>
<url-pattern>/protected/facelet2.xhtml</url-pattern>
<url-pattern>/protected/facelet2.faces</url-pattern>
</protected-views>
It'll then properly throw javax.faces.application.ProtectedViewException when being accessed without a valid token. Much better is to just map the FacesServlet explicitly on *.xhtml in web.xml.
<servlet-mapping>
<servlet-name>facesServlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
This way you never need to deal with virtual URLs.
I've reported this to Mojarra guys as issue 3837.
See also:
Sometimes I see JSF URL is *.jsf, sometimes *.xhtml and sometimes /faces/*. Why?
I have some problems with customs error pages and their exception type.
I have in my web.xml this error page;
<error-page>
<exception-type>java.io.FileNotFoundException</exception-type>
<location>/faces/error.xhtml</location>
</error-page>
This error ocurrs when I click in a link and the JSF file does not exist. My problem is when this error happens, the web page does not redirect to my error.xhtml page.
How is this caused and how can I solve it?
This error page on FileNotFoundException works only if you were actually requesting an URL which matches the URL pattern of the FacesServlet. So imagine that the FacesServlet is mapped on *.jsf, then opening /somenotexistent.jsf will in case of Mojarra indeed throw a subclass of FileNotFoundException which would indeed match your error page.
However, if you're requesting an URL which does not match the URL pattern of the FacesServlet, then the request will be handled by another servlet instead, usually the container's own DefaultServlet. If the resource does not exist, then it would usually return a 404 instead of throwing an exception.
You'd like to add another error page to cover that as well:
<error-page>
<error-code>404</error-code>
<location>/faces/error.xhtml</location>
</error-page>
<error-page>
<exception-type>java.io.FileNotFoundException</exception-type>
<location>/faces/error.xhtml</location>
</error-page>
However, to prevent this duplication, you could also consider to use a servlet filter which catches any instance of FileNotFoundException coming from FacesServlet and then properly returns a 404. JSF utility library OmniFaces has already such a filter, the FacesExceptionFilter. This way you can ultimately end up with only the error page on the error code of 404.
No, as you wrote it the error page is shown when your servlet/JSP/JSF/whatever throws a FileNotFoundException. In the case of a 404, your servlet is not even called so it does not throw anything.
Use this
<error-page>
<error-code>404</error-code>
<location>/the404_page.html</location>
</error-page>
I have the following error-pages in my web.xml file:
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/pages/inactivity.jsf</location>
</error-page>
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/pages/error.jsp</location>
</error-page>
The problem is the following:
Sometimes, I get the ViewExpiredException, and it goes to the inactivity page, but, seems like some .xhtml file tries to use something that's not in session anymore, then I generally got a NullPointerException. But, the second error is a child of the first one, so, my first error handle should be enought.
I was wondering if is there a way to made error-page for a specific exception-type not be showned in some specific page (in inactivity, in my case).
Is there a way to do this?
BTW: I'm using JSF 1.2.
EDIT
Complete Exception.
Also, I just realized that it dont happens in Firefox, just chrome. Dont test in IE because I dont have it :)
If my JSF applications, I'll sometimes come across a bug that, for example, corrupts a user session bean somewhere and the user is stuck looking # a bunch of java exception gobbly-gook on their screen. The only way they can fix this is to restart their browser.
Instead, I would like the application to handle something like this gracefully...basically by being able to catch any of these uncaught exceptions and display an error message (and or possibly contain a link to allow the user to logout/login so they don't have to restart their browser).
Is there a way for JSF to do this easily? If not, does anyone have a solution for this?
You can just create a custom error page and define its location in <error-page> in web.xml.
E.g.
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/error.jsp</location>
</error-page>
You've all freedom to make it look like the way you want.
Aim for the solution proposed by BalusC since in the long term is the easier thing to maintain, otherwise you could try something like this (actually I already did something similar by defining my own custom view handler for exception handling): Custom JSF/Facelet Exception Handling
I am using JSF and I have a backing bean method which does some processing and sets a
variable 'outcome' which then decides the next page to navigate to depending on the
faces-config.xml navigation rules.
What I want to do is add parameters to the URL (in the backing bean?) when the next page is navigated to.
However in the Handler where the backing bean method is, there is no reference to the
HttpRequest object. This is an existing handler which has been around for a long time, so I
am wondering how I can do
request.setAttribute("name", value);
Is there a different approach available for JSF? Any help much appreciated.
HI BalusC,
I am trying to implement what you explained below, however I am running into a problem.
This is what I have:
StringBuffer url = ( (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest()).getRequestURL();
url.append( "?serialNumber=");
url.append(regBean.getSerialNumber());
try{ FacesContext.getCurrentInstance().getExternalContext().redirect(url.toString());
}catch (Exception ex){
ex.printStackTrace();
}
There is no exception generated however I get a 500 Http error "the server has encountered an unknown error." The log shows a little more detail but not enough to be helpful:
ERROR [lifecycle] JSF1054: (Phase ID: INVOKE_APPLICATION 5, View ID: /registration/productValidation.jsp) Exception thrown during phase execution: javax.faces.event.PhaseEvent[source=com.sun.faces.lifecycle.LifecycleImpl#591dae]
11:19:12,186 ERROR [[Faces Servlet]] Servlet.service() for servlet Faces Servlet threw exception
java.lang.IllegalStateException
at org.apache.catalina.connector.ResponseFacade.sendRedirect(ResponseFacade.java:435)
at com.sun.faces.context.ExternalContextImpl.redirect(ExternalContextImpl.java:421)
at com.sun.faces.application.NavigationHandlerImpl.handleNavigation(NavigationHandlerImpl.java:181)
at com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:130)
at javax.faces.component.UICommand.broadcast(UICommand.java:387)
at org.ajax4jsf.component.AjaxViewRoot.processEvents(AjaxViewRoot.java:321)
at org.ajax4jsf.component.AjaxViewRoot.broadcastEvents(AjaxViewRoot.java:296)
at org.ajax4jsf.component.AjaxViewRoot.processPhase(AjaxViewRoot.java:253)
at org.ajax4jsf.component.AjaxViewRoot.processApplication(AjaxViewRoot.java:466)
at com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:82)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:100)
Any ideas at all will be very much appreciated. Thanks!
Ok, thanks for your comments, I changed some stuff around and now I have:
FacesContext.getCurrentInstance().getExternalContext().redirect("mypage.jsp?serialNumber=555555");
Upon debugging I can see that the redirect is working since on mypage.htm I am displaying some headers from a resourcebundle (properties file) so when it tried to get the header to display it is encountering a NullPointer on the line below:
FacesContext context = FacesContext.getCurrentInstance();
context is null, so the log shows NullPointer error but the url of the page is correct I can see the address bar showing http://..../mypage.jsp?serialNum=5555 just as expected!
It appears its having trouble just displaying the contents of the page. So close yet so far ;-(
You need to fire ExternalContext#redirect() in the bean action method yourself.
public void submit() {
String url = "page.jsp?name1=value1&name2=value2";
FacesContext.getCurrentInstance().getExternalContext().redirect(url);
}
If your IDE validator is jerking about the void action method, then you can just ignore it or declare it back to String and put return null; at end of method block.
If you want to set the particular parameters back in some bean in the subsequent request, then you can set them as managed properties in faces-config.xml by #{param.name1} and #{param.name2}.
That said, request attributes should not be confused with request parameters. The request attributes are attached to the current request in the server side only. They are in no way passed to the next request. There you use request parameters for which you can either attach to the redirect URL or include as hidden parameters in a POST form in the response page.
Further, it might be useful to know that you can get a handle of the HttpServletRequest in JSF by ExternalContext#getRequest(). You should however try to avoid to go that far with hauling the "raw" Servlet API from under the JSF hoods. Make use of JSF-provided facilities as many as possible.
JSF 2 added parameters to the navigation handler via the view-param element. From the spec:
If a matching <navigation-case> element was located, and the <redirect/> element was specified in this <navigation-case>, call getRedirectURL() on the ViewHandler, passing the current FacesContext, the <to-view-id>, any name=value parameter pairs specified within <view-param> elements within the element, and the value of the include-view-params attribute of the <redirect /> element if present, false, if not. The return from this method is the value to be sent to the client to which the redirect will occurr. Call getFlash().setRedirect(true) on the current FacesContext. Cause the current response to perform an HTTP redirect to this path, and call responseComplete() on the FacesContext instance for the current request. If the content of <to-view-id> is a value expression, first evaluate it to obtain the value of the view id.