Page 1 of 2

ajax ppr redirect on error

Posted: 07 Jun 2010, 10:26
by michiel
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
;)

Re: ajax ppr redirect on error

Posted: 07 Jun 2010, 10:40
by Oleg
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.

Re: ajax ppr redirect on error

Posted: 07 Jun 2010, 10:45
by cagatay.civici
Check out Ed's blog to handle viewExpiredExceptions;

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

Re: ajax ppr redirect on error

Posted: 07 Jun 2010, 11:18
by michiel
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/ar ... ption-jsf2

Thanks

Re: ajax ppr redirect on error

Posted: 07 Jun 2010, 11:36
by Oleg
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

Code: Select all

/**
 * 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>
   <factory>
       <exception-handler-factory>ip.client.jsftoolkit.exceptions.DefaultExceptionHandlerFactory</exception-handler-factory>
   </factory>
 * </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

Code: Select all

/**
 * 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? :-))

Code: Select all

/**
 * 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

Code: Select all

<?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>

Re: ajax ppr redirect on error

Posted: 07 Jun 2010, 11:42
by michiel
okay,

first of all thanks for the quick responses.
I let you know of my progress

Re: ajax ppr redirect on error

Posted: 08 Jun 2010, 09:26
by michiel
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

Code: Select all

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

Code: Select all

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

Code: Select all

<?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

Re: ajax ppr redirect on error

Posted: 09 Jun 2010, 11:31
by cagatay.civici
Thanks for sharing your solution with other users Michiel.

Re: ajax ppr redirect on error

Posted: 24 Nov 2010, 15:31
by bumble.bee
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.

Re: ajax ppr redirect on error

Posted: 26 Nov 2010, 15:19
by sideswipe
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:

Code: Select all

...
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/1990 ... -ajax-call but (still? i try to look into that deeper) don't know where to apply it ;) )

thanks in advance