2

I am using the following stack :

  • PrimeFaces 5.3.1
  • MyFaces 2.2.8
  • OpenWebBeans 1.6.2
  • OmniFaces 1.10
  • DeltaSpike 1.5.1
  • Tomcat 8.0.28

AFAI understand, only MyFaces and OmniFaces are important there.

I have a bug when an ajax request is performed by a client whose session is expired and when the access to the page is controlled by a <security-constraint> in the webapp web.xml.

In that case, OmniPartialViewContext#startDocument perform a "transparent redirect", to provide a better explanation (see https://github.com/omnifaces/omnifaces/blob/master/src/main/java/org/omnifaces/context/OmniPartialViewContext.java#L275 ) (this function is unchanged from version 1.10 to current 1.11).

    @Override
    public void startDocument() throws IOException {
        wrapped.startDocument();
        String loginURL = WebXml.INSTANCE.getFormLoginPage();

        if (loginURL != null) {
            FacesContext facesContext = FacesContext.getCurrentInstance();
            String loginViewId = normalizeViewId(facesContext, loginURL);

            if (loginViewId.equals(getViewId(facesContext))) {
                String originalURL = getRequestAttribute(facesContext, "javax.servlet.forward.request_uri");

                if (originalURL != null) {
                    redirect(originalURL);
                }
            }
        }
    }

This is a problem because, higher in the stack, org.apache.myfaces.context.servlet.PartialViewContextImpl.processPartialRendering does lines 466 and following :

    {
        String currentEncoding = writer.getCharacterEncoding();
        writer.writePreamble("<?xml version=\"1.0\" encoding=\""+
            (currentEncoding == null ? "UTF-8" : currentEncoding) +"\"?>");
        writer.startDocument();

        writer.writeAttribute("id", viewRoot.getContainerClientId(_facesContext),"id");

So, an exception like the following is raised when trying to write the id, because no element is currently opened.

16-Nov-2015 16:36:35.980 SEVERE [http-apr-8444-exec-10] org.omnifaces.exceptionhandler.FullAjaxExceptionHandler.logException FullAjaxExceptionHandler: An exception occurred during rendering JSF ajax response. Error page '/error.xhtml' will be shown.
java.lang.IllegalStateException: Must be called before the start element is closed (attribute 'id')
        at org.apache.myfaces.shared.renderkit.html.HtmlResponseWriterImpl.writeAttribute(HtmlResponseWriterImpl.java:816)
        at javax.faces.context.ResponseWriterWrapper.writeAttribute(ResponseWriterWrapper.java:109)
        at org.apache.myfaces.context.PartialResponseWriterImpl.writeAttribute(PartialResponseWriterImpl.java:407)
        at javax.faces.context.ResponseWriterWrapper.writeAttribute(ResponseWriterWrapper.java:109)
        at javax.faces.context.ResponseWriterWrapper.writeAttribute(ResponseWriterWrapper.java:109)
        at org.apache.myfaces.context.servlet.PartialViewContextImpl.processPartialRendering(PartialViewContextImpl.java:473)
        at org.apache.myfaces.context.servlet.PartialViewContextImpl.processPartial(PartialViewContextImpl.java:415)
        at org.primefaces.context.PrimePartialViewContext.processPartial(PrimePartialViewContext.java:60)
        at javax.faces.context.PartialViewContextWrapper.processPartial(PartialViewContextWrapper.java:85)
        at javax.faces.component.UIViewRoot.encodeChildren(UIViewRoot.java:516)
        at javax.faces.component.UIComponentBase.encodeAll(UIComponentBase.java:541)
        at org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguage.renderView(FaceletViewDeclarationLanguage.java:1891)
        at org.apache.myfaces.application.ViewHandlerImpl.renderView(ViewHandlerImpl.java:313)
        at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:58)
        at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:58)
        at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:58)
        at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:58)
        at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:58)
        at org.apache.myfaces.lifecycle.RenderResponseExecutor.execute(RenderResponseExecutor.java:116)
        at org.apache.myfaces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:267)
        at org.apache.deltaspike.jsf.impl.listener.request.DeltaSpikeLifecycleWrapper.render(DeltaSpikeLifecycleWrapper.java:111)
        at javax.faces.lifecycle.LifecycleWrapper.render(LifecycleWrapper.java:31)
        at org.apache.deltaspike.jsf.impl.listener.request.JsfClientWindowAwareLifecycleWrapper.render(JsfClientWindowAwareLifecycleWrapper.java:160)
        at org.apache.deltaspike.jsf.impl.listener.request.DeltaSpikeLifecycleWrapper.render(DeltaSpikeLifecycleWrapper.java:111)
        at javax.faces.lifecycle.LifecycleWrapper.render(LifecycleWrapper.java:31)
        at org.apache.deltaspike.jsf.impl.listener.request.JsfClientWindowAwareLifecycleWrapper.render(JsfClientWindowAwareLifecycleWrapper.java:160)
        at javax.faces.webapp.FacesServlet.service(FacesServlet.java:200)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:720)
        at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:466)
        at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:391)
        at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:318)
        at org.apache.catalina.authenticator.FormAuthenticator.forwardToLoginPage(FormAuthenticator.java:384)
        at org.apache.catalina.authenticator.FormAuthenticator.authenticate(FormAuthenticator.java:229)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:577)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
        at org.apache.catalina.authenticator.SingleSignOn.invoke(SingleSignOn.java:291)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673)
        at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2503)
        at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2492)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

How can I correct that (appart from dropping OmniFaces, which is really cool, and that I would really like to keep. :-) ) ?

Ludovic Pénet
  • 1,136
  • 1
  • 16
  • 32

1 Answers1

2

Eventually, I am using the following solution, which can be seen as a work around, as patching MyFaces or OmniFaces is out of my reach...

I am adding my own PartialViewContext at the top of the processing chain. This way, I can perform a clean redirect on an ajax request when I can see it is trying to get the login page.

To do so, one has to :

  • implement a PartialViewContextFactory
  • implement a PartialViewContext
  • declare the PartialViewContextFactory in the faces-config.xml file

So, in my webapp faces-config.xml, I put :

<factory>
    <partial-view-context-factory>fr.senat.context.SenatPartialViewContextFactory</partial-view-context-factory>
</factory>    

My PartialViewContextFactory is dead simple :

package fr.senat.context;

import javax.faces.context.FacesContext;
import javax.faces.context.PartialViewContext;
import javax.faces.context.PartialViewContextFactory;
import lombok.Getter;

/**
*
* @author lpenet
*/
public class SenatPartialViewContextFactory extends PartialViewContextFactory {
    @Getter
    private final PartialViewContextFactory wrapped;

    public SenatPartialViewContextFactory(PartialViewContextFactory wrapped) {
            this.wrapped = wrapped;
    }

    @Override
    public PartialViewContext getPartialViewContext(FacesContext context) {
            return new SenatPartialViewContext(wrapped.getPartialViewContext(context));
    }
}

and the PartialViewContext is quite simple too :

package fr.senat.context;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.PartialResponseWriter;
import javax.faces.context.PartialViewContext;
import javax.faces.context.PartialViewContextWrapper;
import javax.faces.event.PhaseId;
import javax.servlet.http.HttpServletRequest;
import lombok.Getter;
import org.apache.myfaces.context.servlet.PartialViewContextImpl;
import org.omnifaces.config.WebXml;
import static org.omnifaces.util.FacesLocal.getRequestAttribute;
import static org.omnifaces.util.FacesLocal.getViewId;
import static org.omnifaces.util.FacesLocal.normalizeViewId;

/**
 *
 * @author lpenet
 */
public class SenatPartialViewContext  extends PartialViewContextWrapper {

    @Getter
    private final PartialViewContext wrapped;

    public SenatPartialViewContext(PartialViewContext wrapped) {
            this.wrapped = wrapped;
    }

    private void processPartialRendering() throws IOException
    {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        UIViewRoot viewRoot = facesContext.getViewRoot();

        String loginURL = WebXml.INSTANCE.getFormLoginPage();
        if (loginURL != null) {
            String loginViewId = normalizeViewId(facesContext, loginURL);

            if (loginViewId.equals(getViewId(facesContext))) {
                    String originalURL = getRequestAttribute(facesContext, "javax.servlet.forward.request_uri");

                    if (originalURL != null) {
                        PartialResponseWriter writer = facesContext.getPartialViewContext().getPartialResponseWriter();
                        writer.startDocument();
                        HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext().getRequest();
                        writer.redirect(request.getContextPath() + loginURL);
                        writer.endDocument();
                        return;
                    }
            }
        }

        wrapped.processPartial(PhaseId.RENDER_RESPONSE);
    }

    @Override
    public void processPartial(PhaseId phaseId)
    {
        if (phaseId == PhaseId.RENDER_RESPONSE)
        {
            try {
                processPartialRendering();
            }
        catch (IOException ex)
        {
            Logger log = Logger.getLogger(PartialViewContextImpl.class.getName());
            if (log.isLoggable(Level.SEVERE))
            {
                log.log(Level.SEVERE, "", ex);
            }

        }
        } else {
            wrapped.processPartial(phaseId);
        }
    }

}
Ludovic Pénet
  • 1,136
  • 1
  • 16
  • 32
  • 1
    Cool, thank you for the solution! I happened to have reproduced the problem at exactly the same moment you posted the answer. I'll test first if this also works on Mojarra and if so then integrate in OmniFaces 2.2. – BalusC Nov 20 '15 at 10:08
  • 1
    Yes, works in Mojarra too. This will end up in totay's 2.2 snapshot. – BalusC Nov 20 '15 at 10:11
  • In the associated ticket https://github.com/omnifaces/omnifaces/issues/183#issuecomment-158347582 , BalusC indicated that it will be in 1.12 too. – Ludovic Pénet Nov 20 '15 at 13:14