Using mojarra 2.1.27 and richfaces 4.3.6 on tomcat7, I was trying to implement my own exception handler for the ajax requests (Following all your advices) but I never get access to the render exception.
I have normal postback exceptions handled through web.xml <error-page> directives, which works fine, and ajax request should go through my custom handler. This seems to work when updating values and invoking the actions, but not with exceptions during rendering phase.
Having this simple ajax command button
<a4j:commandButton execute="#this" action="#{testController.actionButton4}"
render="#form" value="Ajax Post + Render Error"/>
which (amongst others) renders a div with a result
<div id="result">
#{testController.result}
</div>
Is backed by a simple action in my TestController
public String actionButton4() {
result = "action4";
inError = true;
return null;
}
And a simple getter which is used during the render.
public String getResult() {
if (inError) {
inError = false;
throw new RuntimeException("Render error in " + result);
}
return result;
}
My handler does more or less the default behaviour
public void handle() throws FacesException {
dohandle();
getWrapped().handle();
}
public void dohandle() throws FacesException {
FacesContext fc = FacesContext.getCurrentInstance();
PhaseId phaseId = fc.getCurrentPhaseId();
boolean partialRequest = fc.getPartialViewContext().isPartialRequest();
boolean ajaxRequest = fc.getPartialViewContext().isAjaxRequest();
Iterator<ExceptionQueuedEvent> iterator = getUnhandledExceptionQueuedEvents().iterator();
log.trace("Phase id ({})", phaseId);
while (iterator.hasNext()) {
log.trace("Request is partial ({}). Request is ajax ({})", partialRequest, ajaxRequest);
if (!ajaxRequest) {
return;
}
...
}
}
What I see during rendering is a logging exception in the ExtendedPartialViewcontextImpl of richfaces
Jun 25, 2014 10:18:44 AM org.richfaces.context.ExtendedPartialViewContextImpl$RenderVisitCallback logException
SEVERE: /test2.xhtml: Error reading 'result' on type c.n.g.w.controller.TestController
...
Then I see the phase ends and the exception handler is consulted
10:18:44.336 {http-bio-8080-exec-2} TRACE c.n.g.w.generic.LifeCycleListener - END PHASE RENDER_RESPONSE 6
10:18:44.336 {http-bio-8080-exec-2} TRACE c.n.g.w.generic.ExceptionHandler - Phase id (RENDER_RESPONSE 6)
But for some reason there is no unhandled exception in the queue.
I couldn't find anything stating that the RF rendering should react any different that others, but I suspect it could be. Does anyone know more than me here?
UPDATE:
I notice from the client side log that the rendered output is clipped right at the EL tag, so the rendering breaks and commits the partial result for some reason.
Related
Background
Using JDeveloper 11.1.2.3 to create a report download button using fileDownloadActionListener as follows:
<af:commandButton text="Run Report" id="submitReport">
<af:fileDownloadActionListener method="#{reportBean.run}"/>
</af:commandButton>
At the top of this JSF page is the following:
<f:view afterPhase="#{validationBean.afterPhase}" ...>
...
<af:form id="f1">
<f:event listener="#{validationBean.postValidate}" type="postValidate"/>
The idea is that the Validation Bean can capture any validation problems as follows:
public void afterPhase(javax.faces.event.PhaseEvent phaseEvent) {
if (phaseEvent.getPhaseId() == PhaseId.RENDER_RESPONSE) {
FacesContext context = phaseEvent.getFacesContext();
FacesMessage.Severity severity = context.getMaximumSeverity();
if (isSevereError(severity)) {
context.getExternalContext().getSessionMap().put(ERROR_FLAG_NAME, true);
}
}
}
This works as expected. When the user presses the button, but the form has an error, the validationError session variable is set to true. This should allow the framework to prevent the report from being generated if the form parameters have errors.
Problem
The validationError session variable is used by the report bean's run method as follows:
public void run(FacesContext facesContext, OutputStream outputStream) {
Object error = facesContext.getExternalContext().getSessionMap().get( ERROR_FLAG_NAME );
if( error != null && error != Boolean.TRUE ) {
Report report = null;
try {
report = getReport();
report.setOutputStream(outputStream);
configure(report.getParameters());
report.run();
} catch (Exception e) {
if (report != null && facesContext != null) {
report.publish(e);
}
}
}
else {
facesContext.getExternalContext().getSessionMap().remove( ERROR_FLAG_NAME );
facesContext.renderResponse();
}
}
When there is a validation error in the page, the facesContext.renderResponse(); code is executed, but the resulting web page is blank. No exceptions are logged. No errors are generated.
Question
One way to avoid this situation uses a hidden button, custom Java, and some JavaScript, as described on the following pages:
http://jobinesh.blogspot.ca/2010/01/customizing-execution-of-to-validate.html
http://tompeez.wordpress.com/2011/07/14/validate-data-before-export-via-afexportcollectionactionlistener-or-affiledownloadactionlistener/
However, that mechanism is complicated. The solution I have in mind will work if the page can be rendered as usual.
How do you force the page to be rendered after the af:fileDownloadActionListener event has fired?
Frank Nimphius said:
using a hidden button is the only option you have available today. I
will file an ER that raises an event for the fileDownload listener
(sort of a pre-download) that should allow you to cancel it by calling
Render Response. As said, this doesn't exist yet and the hidden button
is the option you have available (note that the file download tag is a
client behavior tag and not a full UI component, which is why there is
no way yet to interrupt execution.
I don't redirect or forward my user to another page. So when the my SessionExpiredExceptionHandler (extends ExceptionHandlerWrapper) handles the ViewExireException. I want the user to stay on the same page and display a PrimeFaces Dialog. For notifying that the session has expired and that the user needs to login again (dialog based). I am use Servlet 3.1 functions to login/logout user and Basic/file for auth-method to map the users to different system roles.
What is happening now is that the View/page get refreshed after 2 min, but the session doesn't get invalidated. That only happens the second time when the page refreshes, after 4 min.
<session-config>
<session-timeout>2</session-timeout>
</session-config>
Edit:
Which is refreshed by the meta tag:
<meta http-equiv="refresh" content="#{session.maxInactiveInterval}" />
How can I make SessionExpiredExceptionHandlerinvalidate the session object (Servlet logout) when the Exceptions occur the first time, and how can I invoke a JavaScript (expireDlg.show()) on the client to display a PrimeFaces dialog ?
I have looked at some other threads but not found a viable solution.
Session time-out
SessionExpiredExceptionHandler
#Override
public void handle() throws FacesException {
for (Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator(); i.hasNext();) {
ExceptionQueuedEvent event = i.next();
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
Throwable t = context.getException();
if (t instanceof ViewExpiredException) {
ViewExpiredException vee = (ViewExpiredException) t;
FacesContext fc = FacesContext.getCurrentInstance();
Map<String, Object> requestMap = fc.getExternalContext().getRequestMap();
NavigationHandler nav = fc.getApplication().getNavigationHandler();
try {
requestMap.put("currentViewId", vee.getViewId());
nav.handleNavigation(fc, null, "Home");
fc.renderResponse();
} finally {
i.remove();
}
}
}
// At this point, the queue will not contain any ViewExpiredEvents.
// Therefore, let the parent handle them.
getWrapped().handle();
}
web.xml
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/home.xhtml</location>
</error-page>
How can I make SessionExpiredExceptionHandler invalidate the session object (Servlet logout) when the Exceptions occur the first time
The session is supposedly to be already invalidated/expired (otherwise a ViewExpiredException wouldn't be thrown at all), so I don't see how it's useful to manually invalidate/expire it yourself. But for the case that, you can invalidate it as follows:
externalContext.invalidateSession();
and how can I invoke a JavaScript (expireDlg.show()) on the client to display a PrimeFaces dialog ?
You can use the PrimeFaces RequestContext API to programmatically instruct PrimeFaces to execute some JS code on complete of ajax response.
RequestContext.getCurrentInstance().execute("expireDlg.show()");
Don't forget to remove the navigation handler block from the exception handler if you actually don't want to navigate.
This solution worked for my case. It seams that Primefaces (3.3) is swallowing the ExceptionQueuedEvent. There are no Exception to handle when my ViewExceptionHandler gets called. So instead I used the p:idleMonitor component with event listner. I also removed the meta refresh tag.
<p:idleMonitor timeout="#{(session.maxInactiveInterval-60)*1000}">
<p:ajax event="idle" process="#this" update="sessionMsg" listener="#{userController.userIdleSession()}" />
<p:ajax event="active" process="#this" update="sessionMsg" listener="#{userController.userActiveSession()}"/>
</p:idleMonitor>
One weird thing is if the timeoutis excatly the same as the web.xmlsession time-out parameter, the listener won't be invoked.
Bean functions
public void userIdleSession() {
if (!userIdleMsgVisable) {
userIdleMsgVisable = true;
JsfUtil.addWarningMessage(JsfUtil.getResourceMessage("session_expire_title"), JsfUtil.getResourceMessage("session_expire_content"));
}
}
public void userActiveSession() {
if (!userSessionDlgVisable) {
userSessionDlgVisable = true;
RequestContext.getCurrentInstance().execute("sessionExipreDlg.show()");
}
}
The dialog (sessionExipreDlg) called the redirect instead of using navigation handler to get new scope and refresh the page.
public void userInactiveRedirect() {
FacesContext fc = FacesContext.getCurrentInstance();
userIdleMsgVisable = false;
userSessionDlgVisable = false;
sessionUser = null;
HttpServletRequest request = (HttpServletRequest) fc.getExternalContext().getRequest();
JsfUtil.findBean("homeController", HomeController.class).clearCurrentValues();
try {
fc.getExternalContext().redirect(JsfUtil.getApplicationPath(request, false, null));
} catch (IOException ex) {
BeanUtil.severe(ex.getLocalizedMessage());
}
}
I've been trying to cache and handle viewExpiredexception where if a `viewExpiredexceptionviewExpiredexception is throw, i have written a custom viewExpiredexception Handler which is suppose to redirect back to the page where the Exception is thrown, i also insert a boolean into session which is used in the redicted page to show "page was refreshed" message.
Below is the relevant part of my ViewExpiredException Handler:
public void handle() throws FacesException {
for (Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator(); i.hasNext();) {
ExceptionQueuedEvent event = i.next();
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
Throwable t = context.getException();
if (t instanceof ViewExpiredException) {
ViewExpiredException vee = (ViewExpiredException) t;
FacesContext fc = FacesContext.getCurrentInstance();
Map<String, Object> requestMap = fc.getExternalContext().getRequestMap();
NavigationHandler nav = fc.getApplication().getNavigationHandler();
try {
requestMap.put("currentViewId", vee.getViewId());
HttpServletRequest orgRequest = (HttpServletRequest) fc.getExternalContext().getRequest();
fc.getExternalContext().redirect(orgRequest.getRequestURI());
Map<String, Object> sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
sessionMap.put("refreshedMaxSesion",true);
fc.renderResponse();
}
catch(IOException e){ }
finally { i.remove(); }
}
}
// here queue will not contain any ViewExpiredEvents, let the parent handle them.
getWrapped().handle();
}
and the Navigation case
<navigation-rule>
<navigation-case>
<from-outcome>/app/ord1</from-outcome>
<to-view-id>/jsp/orderHist.jsp</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
I had limited success with the above, it would work in some cases but in other cases the page wouldn't redirect at all. It works mostly in chrome but in IE it didn't work at all.
I tried making few changes such as using
HttpServletResponse response = (HttpServletResponse) fc.getExternalContext().getResponse();
response.sendRedirect(vee.getViewId());
but i was getting the 500 error pages with exception saying View must Exists... so i stopped experimenting to find out what i am doing wrong first. How can this goal of cahcing a ViewExpiredException, and Redirectign back to the page where the error was thrown be archived?
I'm on myFaces (2.1.7) and richFaces (3.3.3). Thanks
There is some work already done inside MyFaces Community to deal with ViewExpiredException in a graceful way. Some issues has been solved in MyFaces Core (see MYFACES-3530 and MYFACES-3531) Maybe you should try the latest snapshot HERE.
I have following codes in my app which is using JSF 2.0
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext()
.getRequest();
HttpServletResponse response = (HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse();
......
try {
request.getRequestDispatcher("SomePage.xhtml").forward(request, response);
} catch (ServletException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
But when ever the line ..
request.getRequestDispatcher("SomePage.xhtml").forward(request, response);
is executed I'm getting the following exception...
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.RangeCheck(ArrayList.java:547)
at java.util.ArrayList.get(ArrayList.java:322)
at javax.faces.component.AttachedObjectListHolder.restoreState(AttachedObjectListHolder.java:165)
at javax.faces.component.UIComponentBase.restoreState(UIComponentBase.java:1432)
at com.sun.faces.application.view.StateManagementStrategyImpl$1.visit(StateManagementStrategyImpl.java:265)
at com.sun.faces.component.visit.FullVisitContext.invokeVisitCallback(FullVisitContext.java:151)
Due to some appilcation constaints I can not use the
FacesContext.getCurrentInstance().getExternalContext().redirect();
method..
Is it a JSF 2.0 bug??
I never tried to send a redirect this way because this is not the standard solution for navigation in JSF2.
Here are some methods that will work correctly in a JSF 2.0 environment:
1. Specify the redirect command in the navigation link
Attach ?faces-redirect=true to your navigation and this should do the job.
public String someAction(){
// Logic here
return "newPage" + "?faces-redirect=true"
}
<h:form id = "form">
<h:inputText>...</h:inputText>
<h:commandButton action = "#{controller.someAction}" />
</h:form>
After processing the Login in the someAction the navigation flow will be redirected to newPage.xhtml. All you have to do is to correctly call the action from your UI Form.
2. Specify the redirect through external context
This method is closer to what you are looking for:
public void someAction(){
// Logic here
try{
FacesContext.getCurrentInstance().getExternalContext().redirect("newPage");
} catch (Exception e){
e.getMessage();
}
}
First of all, whenever you need to use javax.servlet.* classes inside a JSF managed bean, then changes are big that you're doing things the wrong way or unnecessarily overcomplicated. Keep this in mind for the next time you need to a add a import javax.servlet...; line.
As to your concrete problem, in your particular case, you should just be returning the view ID in order to perform a forward.
public String someAction() {
// ...
return "SomePage";
}
An alternative is ExternalContext#dispatch() method, but this is also unnecessarily overcomplicated.
I'm trying to use the authenticate() in a preRenderView listener method, in order to trigger authentication conditionally, depending on view parameters in the page. I tried adding a simple method:
#Named
#RequestScoped
public class PermissionBean implements java.io.Serializable {
public void preRender() {
System.out.println("IN PRERENDER");
HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
HttpServletResponse response = (HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse();
try {
request.authenticate(response);
} catch (Exception e) { // may throw ServletException or IOException
e.printStackTrace();
}
}
The authenticate method itself doesn't throw an exception, it triggers the redirect to Login.xhtml as expected. However, I'm getting in my server log, I get this exception:
enter code here
INFO: IN PRERENDER
FINEST: GET /Login.xhtml previous[3]
INFO: Exception when handling error trying to reset the response.
java.lang.NullPointerException
at com.sun.faces.facelets.tag.jsf.core.DeclarativeSystemEventListener.processEvent(EventHandler.java:126)
at javax.faces.component.UIComponent$ComponentSystemEventListenerAdapter.processEvent(UIComponent.java:2508)
at javax.faces.event.SystemEvent.processListener(SystemEvent.java:106)
at com.sun.faces.application.ApplicationImpl.processListeners(ApplicationImpl.java:2129)
at com.sun.faces.application.ApplicationImpl.invokeComponentListenersFor(ApplicationImpl.java:2077)
at com.sun.faces.application.ApplicationImpl.publishEvent(ApplicationImpl.java:286)
at com.sun.faces.application.ApplicationImpl.publishEvent(ApplicationImpl.java:244)
at javax.faces.application.ApplicationWrapper.publishEvent(ApplicationWrapper.java:670)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:108)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:594)
So my request is not redirected to Login.xhtml.
My question is - is this something that should work within a JSF managed bean, or is it only legal outside of JSF request lifecycle? I tried calling authenticate() from a WebFilter, and it works as exptected.
Thanks,
Ellen
You need to tell JSF to not render the response which it was initially asked to do, whenever the request has been redirected. You can do that by checking if HttpServletRequest#authenticate() returns false and then invoke FacesContext#responseComplete() accordingly.
if (!request.authenticate(response)) {
FacesContext.getCurrentInstance().responseComplete();
}