Board index JavaServer Faces General ajax ppr redirect on error

ajax ppr redirect on error

Components, Ajax Framework, Utilities and More.

Post 07 Jun 2010, 10:26

Posts: 240
Location: Belgium
I 've written a single page app with only navigation from login page through app and vice versa.
The only problem I faced is the view expired exception (session timeout).
When this happens I want to redirect to the login page (if possible with a message for the user)
but I can't get it implemented.

I've tried to extend the FacesServlet, to add a SessionListener, to use the onerror of <p:ajaxStatus>, ...

Any help is appreciated very much
;)
JSF-2.0, mojarra-2.0.2-FCS and PrimeFaces-2.1 on GlassFish v3.0.1 (build 22)

Post 07 Jun 2010, 10:40
Oleg User avatar
Expert Member

Posts: 3688
Location: Russia, Siberia => Germany, Black Forest
michiel wrote:
I 've written a single page app with only navigation from login page through app and vice versa.
The only problem I faced is the view expired exception (session timeout).
When this happens I want to redirect to the login page (if possible with a message for the user)
but I can't get it implemented.

I've tried to extend the FacesServlet, to add a SessionListener, to use the onerror of <p:ajaxStatus>, ...

Any help is appreciated very much
;)

Do you use JSF2? I have a solution for JSF2 with a special phase listener and default error handler.
PrimeFaces 4.x, 5.x, Mojarra 2.2.x, JBoss WildFly, WebSphere, Windows 8.1, IntelliJ IDEA
PrimeFaces Cookbook: http://ova2.github.com/primefaces-cookbook/ PrimeFaces Extensions on GitHub: http://primefaces-extensions.github.com/

Post 07 Jun 2010, 10:45

Posts: 15036
Location: Cybertron

Check out Ed's blog to handle viewExpiredExceptions;

http://weblogs.java.net/blog/edburns/ar ... ption-jsf2
PrimeFaces Lead

Post 07 Jun 2010, 11:18

Posts: 240
Location: Belgium
I do use JSF-2.0, mojarra-2.0.2-FCS and PrimeFaces-2.0.3-SNAPSHOT on GlassFishv3.

I'll try to http://weblogs.java.net/blog/edburns/archive/2009/09/03/dealing-gracefully-viewexpiredexception-jsf2

Thanks
JSF-2.0, mojarra-2.0.2-FCS and PrimeFaces-2.1 on GlassFish v3.0.1 (build 22)

Post 07 Jun 2010, 11:36
Oleg User avatar
Expert Member

Posts: 3688
Location: Russia, Siberia => Germany, Black Forest
Hello,

For Ajax redirects you needs a little bit more code than in Ed's blog. I'm going to post here my solution (just three centric classes to show the idea). I hope it will help you.

DefaultExceptionHandlerFactory

/**
 * This is a factory class that creates (if needed) and returns a new {@link DefaultExceptionHandler} instance.
 *
 * <p>Using this functionality requires the following section in the faces-config.xml:</p>
 *
 * <pre>
   &lt;factory&gt;
       <exception-handler-factory>ip.client.jsftoolkit.exceptions.DefaultExceptionHandlerFactory</exception-handler-factory>
   &lt;/factory&gt;
 * </pre>
 */
public class DefaultExceptionHandlerFactory extends ExceptionHandlerFactory
{
   //~ Instance fields --------------------------------------------------------

   private ExceptionHandlerFactory parent;

   //~ Constructors -----------------------------------------------------------

   /**
    * Creates a new DefaultExceptionHandlerFactory object.
    *
    * @param parent parent instance of {@link ExceptionHandlerFactory}
    */
   public DefaultExceptionHandlerFactory(ExceptionHandlerFactory parent)
   {
      this.parent = parent;
   }

   //~ Methods ----------------------------------------------------------------

   /**
    * @see javax.faces.context.ExceptionHandlerFactory#getExceptionHandler()
    */
   @Override
   public ExceptionHandler getExceptionHandler()
   {
      ExceptionHandler eh = parent.getExceptionHandler();
      eh = new DefaultExceptionHandler(eh);

      return eh;
   }
}

DefaultExceptionHandler

/**
 * Default implementation of exception handler to catch unchecked / unexpected exceptions in order to proper display.
 * The method {@link #handleUnexpected(FacesContext, Throwable)} can be overwritten in derived class in order to
 * customization. Handling of various unexpected exceptions can be done there. Example of using:
 *
 * <pre>
    protected String handleUnexpected(FacesContext facesContext, final Throwable t)
    {
        if (t instanceof IllegalStateException) {
            // some special handling here
            ...
            return "key.exception.IllegalStateException";
        } else {
            return super.handleUnexpected(facesContext, t);
        }
    }
   </pre>
 */
public class DefaultExceptionHandler extends ExceptionHandlerWrapper
{
   //~ Static fields/initializers ---------------------------------------------

   /** logger */
   private static final Log LOG = LogFactory.getLog(DefaultExceptionHandler.class);

   /** key for session scoped message detail */
   public static final String MESSAGE_DETAIL_KEY = "ip.client.jsftoolkit.messageDetail";

   //~ Instance fields --------------------------------------------------------

   private ExceptionHandler wrapped;

   //~ Constructors -----------------------------------------------------------

   /**
    * Creates a new DefaultExceptionHandler object.
    *
    * @param wrapped wrapped exception handler instance
    */
   public DefaultExceptionHandler(ExceptionHandler wrapped)
   {
      this.wrapped = wrapped;
   }

   //~ Methods ----------------------------------------------------------------

   /**
    * @see javax.faces.context.ExceptionHandlerWrapper#getWrapped()
    */
   @Override
   public ExceptionHandler getWrapped()
   {
      return this.wrapped;
   }

   @Override
   public void handle() throws FacesException
   {
      for (Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator(); i.hasNext();) {
         ExceptionQueuedEvent event = i.next();
         ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();

         String redirectPage = null;
         FacesContext fc = FacesContext.getCurrentInstance();
         Throwable t = context.getException();

         try {
            if (t instanceof AbortProcessingException) {
               // about AbortProcessingException see JSF 2 spec.
               LOG.error("An unexpected exception has occurred by event listener(s)", t);
               redirectPage = "/views/error.jsf?statusCode=jsftoolkit.exception.UncheckedException";
               fc.getExternalContext().getSessionMap()
                 .put(DefaultExceptionHandler.MESSAGE_DETAIL_KEY, t.getLocalizedMessage());
            } else if (t instanceof ViewExpiredException) {
               if (LOG.isDebugEnabled()) {
                  LOG.debug("View '" + ((ViewExpiredException) t).getViewId() + "' is expired", t);
               }

               ApplicationConfiguration appConfiguration =
                   (ApplicationConfiguration) FacesAccessor.accessManagedBean(
                       fc, ApplicationConfiguration.BEAN_NAME_APPLICATION_CONFIGURATION);
               HttpSession session = (HttpSession) fc.getExternalContext().getSession(false);
               if (session != null) {
                  // should not happen
                  session.invalidate();
               }

               if (appConfiguration.getBoolean(ConfigKeys.KEY_LOGOFF_2_LOGOUT_PAGE, false)) {
                  // redirect to the specified logout page
                  redirectPage = "/views/logout.jsf";
               } else {
                  // redirect to the login page
                  redirectPage = "";
               }
            } else if (t instanceof ServiceNotAvailableException) {
               LOG.error("'" + ((ServiceNotAvailableException) t).getServiceName() + "' is not available", t);
               redirectPage = "/views/error.jsf?statusCode=jsftoolkit.exception.ServiceNotAvailableException";
            } else {
               // custom handling of unexpected exceptions can be done in the method handleUnexpected
               String messageKey = handleUnexpected(fc, t);
               redirectPage = "/views/error.jsf?statusCode=" + messageKey;
               fc.getExternalContext().getSessionMap()
                 .put(DefaultExceptionHandler.MESSAGE_DETAIL_KEY, t.getLocalizedMessage());
            }
         } finally {
            i.remove();
         }

         SecurityPhaseListener spl = new SecurityPhaseListener();
         spl.doRedirect(fc, redirectPage);

         break;
      }
   }

   /**
    * This method can be overwritten in derived classe in order to customization. Handling of various unexpected
    * exceptions can be done here.
    *
    * @param  facesContext current faces context {@link FacesContext}
    * @param  t            Throwable exception
    * @return String key for error message
    */
   protected String handleUnexpected(FacesContext facesContext, final Throwable t)
   {
      LOG.error("An unexpected internal error has occurred", t);

      return "jsftoolkit.exception.UncheckedException";
   }
}

SecurityPhaseListener with redirect functionality (unfortunately with a little workaround for PrimeFaces, Cagatay? :-))

/**
 * Phase listener for the Restore View Phase which manages display of login page.
 */
public class SecurityPhaseListener implements PhaseListener
{
   //~ Static fields/initializers ---------------------------------------------

   /** logger */
   private static final Log LOG = LogFactory.getLog(SecurityPhaseListener.class);

   //~ Methods ----------------------------------------------------------------

   public void afterPhase(PhaseEvent event)
   {
      ;
   }

   public void beforePhase(PhaseEvent event)
   {
      FacesContext fc = event.getFacesContext();

      String loginPage = (String) fc.getExternalContext().getRequestMap().get(Authenticator.SHOW_LOGIN_ATTRIBUTE);
      if (StringUtils.isNotBlank(loginPage)) {
         doRedirect(fc, loginPage);
      }
   }

   public PhaseId getPhaseId()
   {
      return PhaseId.RESTORE_VIEW;
   }

   /**
    * Does a regular or ajax redirect.
    *
    * @param  fc        current faces context
    * @param  loginPage context relative login page URL
    * @throws FacesException if redirect failed
    */
   public void doRedirect(FacesContext fc, String loginPage) throws FacesException
   {
      ExternalContext ec = fc.getExternalContext();

      try {
         // workaround for PrimeFaces
         new RequestContextImpl(ec);
         if (ec.getRequestParameterMap().containsKey(Constants.PARTIAL_PROCESS_PARAM)
             && !ec.getRequestParameterMap().get(Constants.PARTIAL_PROCESS_PARAM).equals("@all")) {
            fc.setViewRoot(new PartialViewRoot(new UIViewRoot()));
         }

         // fix for renderer kit (Mojarra's and PrimeFaces's ajax redirect)
         if ((RequestContext.getCurrentInstance().isAjaxRequest()
              || fc.getPartialViewContext().isPartialRequest())
             && fc.getResponseWriter() == null
             && fc.getRenderKit() == null) {
            ServletResponse response = (ServletResponse) ec.getResponse();
            ServletRequest request = (ServletRequest) ec.getRequest();
            response.setCharacterEncoding(request.getCharacterEncoding());

            RenderKitFactory factory =
                (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);

            //RenderKit renderKit = factory.getRenderKit(fc, RenderKitFactory.HTML_BASIC_RENDER_KIT);
            RenderKit renderKit =
                factory.getRenderKit(fc, fc.getApplication().getViewHandler().calculateRenderKitId(fc));

            ResponseWriter responseWriter =
                renderKit.createResponseWriter(response.getWriter(), null, request.getCharacterEncoding());
            fc.setResponseWriter(responseWriter);
         }

         ec.redirect(ec.getRequestContextPath() + (loginPage != null ? loginPage : ""));
      } catch (IOException e) {
         LOG.error("Redirect to the specified page '" + loginPage + "' failed");
         throw new FacesException(e);
      }
   }
}

Configuration

<?xml version="1.0"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
      version="2.0">

   <absolute-ordering>
      <others />
      <name>jsftoolkit</name>
   </absolute-ordering>
   <factory>
      <exception-handler-factory>
         ip.client.jsftoolkit.commons.DefaultExceptionHandlerFactory
      </exception-handler-factory>
   </factory>
   <lifecycle>
      <phase-listener>
         ip.client.jsftoolkit.commons.SecurityPhaseListener
      </phase-listener>
   </lifecycle>
        .....

</faces-config>
PrimeFaces 4.x, 5.x, Mojarra 2.2.x, JBoss WildFly, WebSphere, Windows 8.1, IntelliJ IDEA
PrimeFaces Cookbook: http://ova2.github.com/primefaces-cookbook/ PrimeFaces Extensions on GitHub: http://primefaces-extensions.github.com/

Post 07 Jun 2010, 11:42

Posts: 240
Location: Belgium
okay,

first of all thanks for the quick responses.
I let you know of my progress
JSF-2.0, mojarra-2.0.2-FCS and PrimeFaces-2.1 on GlassFish v3.0.1 (build 22)

Post 08 Jun 2010, 09:26

Posts: 240
Location: Belgium
with your help I finally figured it out.
I took the the solution from Ed Burns blog as base and fixed it with Oleg's response.

Here is the code.

ViewExpiredExceptionExceptionHandlerFactory
public class ViewExpiredExceptionExceptionHandlerFactory extends ExceptionHandlerFactory {
    private ExceptionHandlerFactory parent;
    public ViewExpiredExceptionExceptionHandlerFactory(ExceptionHandlerFactory parent) {
        this.parent = parent;
    }

    @Override
    public ExceptionHandler getExceptionHandler() {
        ExceptionHandler result = parent.getExceptionHandler();
        result = new ViewExpiredExceptionExceptionHandler(result);
        return result;
    }
}


ViewExpiredExceptionExceptionHandler
public class ViewExpiredExceptionExceptionHandler extends ExceptionHandlerWrapper {
    private ExceptionHandler wrapped;

    public ViewExpiredExceptionExceptionHandler(ExceptionHandler wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public ExceptionHandler getWrapped() {
        return this.wrapped;
    }

    @Override
    public void handle() throws FacesException {
        String redirectPage = null;
        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();
                HttpSession session = (HttpSession) fc.getExternalContext().getSession(true);
                try {
                    // Push some useful stuff to the request scope for use in the page
                    session.setAttribute("currentViewId", vee.getViewId());
                    System.out.println("currentViewId to put = " + vee.getViewId());
                    redirectPage = "/viewExpired.caw";
                } finally {
                    i.remove();
                }
                doRedirect(fc, redirectPage);
            }
        }

        // At this point, the queue will not contain any ViewExpiredEvents.
        // Therefore, let the parent handle them.
        getWrapped().handle();
    }

    public void doRedirect(FacesContext fc, String redirectPage) throws FacesException{
        ExternalContext ec = fc.getExternalContext();

        try {
            // workaround for PrimeFaces
            new RequestContextImpl(ec);
            if (ec.getRequestParameterMap().containsKey(Constants.PARTIAL_PROCESS_PARAM)
                    && !ec.getRequestParameterMap().get(Constants.PARTIAL_PROCESS_PARAM).equals("@all")) {
                fc.setViewRoot(new PartialViewRoot(new UIViewRoot()));
            }

            // fix for renderer kit (Mojarra's and PrimeFaces's ajax redirect)
            if ((RequestContext.getCurrentInstance().isAjaxRequest()
                    || fc.getPartialViewContext().isPartialRequest())
                    && fc.getResponseWriter() == null
                    && fc.getRenderKit() == null) {
                ServletResponse response = (ServletResponse) ec.getResponse();
                ServletRequest request = (ServletRequest) ec.getRequest();
                response.setCharacterEncoding(request.getCharacterEncoding());

                RenderKitFactory factory =
                        (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
                RenderKit renderKit =
                        factory.getRenderKit(fc, fc.getApplication().getViewHandler().calculateRenderKitId(fc));
                ResponseWriter responseWriter =
                        renderKit.createResponseWriter(response.getWriter(), null, request.getCharacterEncoding());
                fc.setResponseWriter(responseWriter);
            }

            ec.redirect(ec.getRequestContextPath() + (redirectPage != null ? redirectPage : ""));
        } catch (IOException e) {
            System.out.println("Redirect to the specified page '" + redirectPage + "' failed");
            throw new FacesException(e);
        }
    }
}


faces-config.xml

<?xml version='1.0' encoding='UTF-8'?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
              version="2.0">

    <!-- NAVIGATION RULES -->

    <!-- ViewExpiration Handler -->
    <factory>
        <exception-handler-factory>be.oac.caw.web.utils.ViewExpiredExceptionExceptionHandlerFactory</exception-handler-factory>
    </factory>

</faces-config>


Hope this might help someone else.

Thanks for al the help

Post 09 Jun 2010, 11:31

Posts: 15036
Location: Cybertron

Thanks for sharing your solution with other users Michiel.
PrimeFaces Lead

Post 24 Nov 2010, 15:31

Posts: 723
Location: United States
I'm not seeing any problems with the PrimeFaces AJAX redirect in PrimeFaces 2.2. I assume this issue was resolved and the workaround is no longer needed?

The workaround no longer would compile in its current form because the RequestContextImpl appears to have been replaced with DefaultRequestContext among other changes.

Post 26 Nov 2010, 15:19

Posts: 5
i have the same problem but using
jsf 1.2 (mojarra)
jboss seam 2.2
primefaces 1.2 snapshot
tomcat 6

i want to do a page-redirect after session timeout (@topic -> the observed error is: ViewExpiredException). so i installed a sessionlistener + sessiontimeoutfilter. next step is to send a redirect like:
...
httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/home.seam");
...

to send the user to the default loginpage after timeout.

everything is triggered well and works, except i use ajax.
i got a <p:commandButton ...> for example. if it is triggered after sessiontimeout the code above is executed, but since the button got ajax support, there is no redirect made.
if i explicit say <p:commandButton ajax="false" ...> then the redirect works (but.. ajax would be nice to have ;) )


in the filter i have no access to the FacesContext so i cant go the way described for jsf 2.0
i also can't use the <exception-handler-factory> (not available in jsf 1.2)



so... is there a way to use the benefits of ajax support + get propperly redirected using jsf 1.2?

(i found a "hack" here.. http://stackoverflow.com/questions/199099/how-to-manage-a-redirect-request-after-a-jquery-ajax-call but (still? i try to look into that deeper) don't know where to apply it ;) )

thanks in advance

Next

Return to General