All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.primefaces.application.exceptionhandler.PrimeExceptionHandler Maven / Gradle / Ivy

/*
 * The MIT License
 *
 * Copyright (c) 2009-2023 PrimeTek Informatics
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.primefaces.application.exceptionhandler;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.el.ELException;
import javax.faces.FacesException;
import javax.faces.application.ProjectStage;
import javax.faces.application.ViewExpiredException;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.component.visit.VisitContext;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerWrapper;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.PartialResponseWriter;
import javax.faces.event.ExceptionQueuedEvent;
import javax.faces.event.PhaseId;
import javax.faces.view.ViewDeclarationLanguage;
import org.primefaces.component.ajaxexceptionhandler.AjaxExceptionHandler;
import org.primefaces.component.ajaxexceptionhandler.AjaxExceptionHandlerVisitCallback;
import org.primefaces.config.PrimeConfiguration;
import org.primefaces.context.PrimeApplicationContext;
import org.primefaces.context.PrimeRequestContext;
import org.primefaces.csp.CspPhaseListener;
import org.primefaces.expression.SearchExpressionFacade;
import org.primefaces.util.ComponentUtils;
import org.primefaces.util.LangUtils;
import org.primefaces.util.EscapeUtils;
import org.primefaces.util.Lazy;

public class PrimeExceptionHandler extends ExceptionHandlerWrapper {

    private static final Logger LOGGER = Logger.getLogger(PrimeExceptionHandler.class.getName());
    private static final String DATE_FORMAT_PATTERN = "yyyy-MM-dd HH:mm:ss";

    private final ExceptionHandler wrapped;
    private final Lazy config;

    @SuppressWarnings("deprecation") // the default constructor is deprecated in JSF 2.3
    public PrimeExceptionHandler(ExceptionHandler wrapped) {
        this.wrapped = wrapped;
        this.config = new Lazy(() -> PrimeApplicationContext.getCurrentInstance(FacesContext.getCurrentInstance()).getConfig());
    }

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

    @Override
    public void handle() throws FacesException {
        FacesContext context = FacesContext.getCurrentInstance();

        if (context == null || context.getResponseComplete()) {
            return;
        }

        Iterable exceptionQueuedEvents = getUnhandledExceptionQueuedEvents();
        if (exceptionQueuedEvents != null && exceptionQueuedEvents.iterator() != null) {
            Iterator unhandledExceptionQueuedEvents = getUnhandledExceptionQueuedEvents().iterator();

            if (unhandledExceptionQueuedEvents.hasNext()) {
                try {
                    Throwable throwable = unhandledExceptionQueuedEvents.next().getContext().getException();

                    unhandledExceptionQueuedEvents.remove();

                    Throwable rootCause = getRootCause(throwable);
                    ExceptionInfo info = createExceptionInfo(rootCause);

                    // print exception in development stage
                    if (context.getApplication().getProjectStage() == ProjectStage.Development) {
                        rootCause.printStackTrace();
                    }

                    if (isLogException(context, rootCause)) {
                        logException(rootCause);
                    }

                    if (context.getPartialViewContext().isAjaxRequest()) {
                        handleAjaxException(context, rootCause, info);
                    }
                    else {
                        handleRedirect(context, rootCause, info, false);
                    }
                }
                catch (Exception ex) {
                    LOGGER.log(Level.SEVERE, "Could not handle exception!", ex);
                }
            }

            while (unhandledExceptionQueuedEvents.hasNext()) {
                // Any remaining unhandled exceptions are not interesting. First fix the first.
                unhandledExceptionQueuedEvents.next();
                unhandledExceptionQueuedEvents.remove();
            }
        }
    }

    protected void logException(Throwable rootCause) {
        LOGGER.log(Level.SEVERE, rootCause.getMessage(), rootCause);
    }

    protected boolean isLogException(FacesContext context, Throwable rootCause) {

        if (context.isProjectStage(ProjectStage.Production)) {
            if (rootCause instanceof ViewExpiredException) {
                return false;
            }

            if (rootCause != null) {
                for (String ignore : config.get().getExceptionTypesToIgnoreInLogging()) {
                    if (ignore.trim().equals(rootCause.getClass().getName())) {
                        return false;
                    }
                }
            }
        }

        return true;
    }

    @Override
    public Throwable getRootCause(Throwable throwable) {
        while ((ELException.class.isInstance(throwable) || FacesException.class.isInstance(throwable)) && throwable.getCause() != null) {
            throwable = throwable.getCause();
        }

        return throwable;
    }

    protected void handleAjaxException(FacesContext context, Throwable rootCause, ExceptionInfo info) throws IOException {
        ExternalContext externalContext = context.getExternalContext();
        PartialResponseWriter writer = context.getPartialViewContext().getPartialResponseWriter();

        // should not happen actually
        if (writer == null) {
            return;
        }

        CspPhaseListener.initCsp(context, config.get().isCsp(), config.get().isPolicyProvided(),
                config.get().getCspReportOnlyPolicy(), config.get().getCspPolicy());

        boolean isResponseReset = false;

        //mojarra workaround to avoid invalid partial output due to open tags
        if (context.getCurrentPhaseId().equals(PhaseId.RENDER_RESPONSE)) {
            if (!externalContext.isResponseCommitted()) {
                // this doesn't flush, just clears the internal state in mojarra
                writer.flush();

                writer.endCDATA();

                writer.endInsert();
                writer.endUpdate();

                writer.startError("");
                writer.endError();

                writer.getWrapped().endElement("changes");
                writer.getWrapped().endElement("partial-response");


                String characterEncoding = externalContext.getResponseCharacterEncoding();
                externalContext.responseReset();
                externalContext.setResponseCharacterEncoding(characterEncoding);

                isResponseReset = true;
            }
        }

        AjaxExceptionHandler handlerComponent = null;

        try {
            rootCause = buildView(context, rootCause, rootCause);
            handlerComponent = findHandlerComponent(context, rootCause);
        }
        catch (Exception ex) {
            LOGGER.log(Level.WARNING, "Could not build view or lookup a AjaxExceptionHandler component!", ex);
        }

        context.getAttributes().put(ExceptionInfo.ATTRIBUTE_NAME, info);

        // redirect if no AjaxExceptionHandler available
        if (handlerComponent == null) {
            handleRedirect(context, rootCause, info, isResponseReset);
        }
        // handle custom update / onexception callback
        else {
            externalContext.addResponseHeader("Content-Type", "text/xml; charset=" + externalContext.getResponseCharacterEncoding());
            externalContext.addResponseHeader("Cache-Control", "no-cache");
            externalContext.setResponseContentType("text/xml");

            writer.startDocument();

            if (LangUtils.isNotBlank(handlerComponent.getOnexception())) {
                StringBuilder sb = new StringBuilder();
                sb.append("var hf=function(type,message,timestampp){");
                sb.append(handlerComponent.getOnexception());
                sb.append("};hf.call(this,\""
                        + info.getType() + "\",\""
                        + EscapeUtils.forJavaScript(info.getMessage())
                        + "\",\""
                        + info.getFormattedTimestamp()
                        + "\");");

                PrimeRequestContext.getCurrentInstance(context).getScriptsToExecute().add(sb.toString());
            }

            if (LangUtils.isNotBlank(handlerComponent.getUpdate())) {
                List updates = SearchExpressionFacade.resolveComponents(context, handlerComponent, handlerComponent.getUpdate());

                if (updates != null && !updates.isEmpty()) {
                    context.setResponseWriter(writer);

                    for (int i = 0; i < updates.size(); i++) {
                        UIComponent component = updates.get(i);

                        writer.startUpdate(component.getClientId(context));
                        component.encodeAll(context);
                        writer.endUpdate();
                    }
                }
            }

            writer.endDocument();

            context.responseComplete();
        }
    }

    protected ExceptionInfo createExceptionInfo(Throwable rootCause) throws IOException {
        ExceptionInfo info = new ExceptionInfo();
        info.setException(rootCause);
        info.setMessage(rootCause.getMessage());
        info.setStackTrace(rootCause.getStackTrace());
        info.setTimestamp(new Date());
        info.setType(rootCause.getClass().getName());

        try (StringWriter sw = new StringWriter()) {
            PrintWriter pw = new PrintWriter(sw);
            rootCause.printStackTrace(pw);
            info.setFormattedStackTrace(EscapeUtils.forXml(sw.toString()).replaceAll("(\r\n|\n)", "
")); pw.close(); } SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT_PATTERN); info.setFormattedTimestamp(format.format(info.getTimestamp())); return info; } /** * Finds the proper {@link AjaxExceptionHandler} for the given {@link Throwable}. * * @param context The {@link FacesContext}. * @param rootCause The occurred {@link Throwable}. * @return The {@link AjaxExceptionHandler} or null. */ protected AjaxExceptionHandler findHandlerComponent(FacesContext context, Throwable rootCause) { AjaxExceptionHandlerVisitCallback visitCallback = new AjaxExceptionHandlerVisitCallback(rootCause); if (PrimeApplicationContext.getCurrentInstance(context).getEnvironment().isAtLeastJsf21()) { context.getViewRoot().visitTree( VisitContext.createVisitContext(context, null, ComponentUtils.VISIT_HINTS_SKIP_ITERATION.get()), visitCallback); } else { context.getViewRoot().visitTree( VisitContext.createVisitContext(context, null, Collections.emptySet()), visitCallback); } Map handlers = visitCallback.getHandlers(); // get handler by exception type AjaxExceptionHandler handler = handlers.get(rootCause.getClass().getName()); // lookup by inheritance hierarchy if (handler == null) { Class throwableClass = rootCause.getClass(); while (handler == null && throwableClass.getSuperclass() != Object.class) { throwableClass = throwableClass.getSuperclass(); handler = handlers.get(throwableClass.getName()); } } // get default handler if (handler == null) { handler = handlers.get(null); } return handler; } /** * Builds the view if not already available. This is mostly required for ViewExpiredException's. * * @param context The {@link FacesContext}. * @param throwable The occurred {@link Throwable}. * @param rootCause The root cause. * @return The unwrapped {@link Throwable}. * @throws java.io.IOException If building the view fails. */ protected Throwable buildView(FacesContext context, Throwable throwable, Throwable rootCause) throws IOException { if (context.getViewRoot() == null) { ViewHandler viewHandler = context.getApplication().getViewHandler(); String viewId = viewHandler.deriveViewId(context, ComponentUtils.calculateViewId(context)); ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(context, viewId); UIViewRoot viewRoot = vdl.createView(context, viewId); context.setViewRoot(viewRoot); vdl.buildView(context, viewRoot); // Workaround for Mojarra // if UIViewRoot == null -> 'IllegalArgumentException' is throwed instead of 'ViewExpiredException' if (rootCause == null && throwable instanceof IllegalArgumentException) { rootCause = new javax.faces.application.ViewExpiredException(viewId); } } return rootCause; } protected void handleRedirect(FacesContext context, Throwable rootCause, ExceptionInfo info, boolean isResponseReset) throws IOException { ExternalContext externalContext = context.getExternalContext(); externalContext.getSessionMap().put(ExceptionInfo.ATTRIBUTE_NAME, info); Map errorPages = PrimeApplicationContext.getCurrentInstance(context).getConfig().getErrorPages(); String errorPage = evaluateErrorPage(errorPages, rootCause); String url = constructRedirectUrl(context, errorPage); // workaround for mojarra -> mojarra doesn't reset PartialResponseWriter#inChanges if we call externalContext#resetResponse if (isResponseReset && context.getPartialViewContext().isAjaxRequest()) { PartialResponseWriter writer = context.getPartialViewContext().getPartialResponseWriter(); externalContext.addResponseHeader("Content-Type", "text/xml; charset=" + externalContext.getResponseCharacterEncoding()); externalContext.addResponseHeader("Cache-Control", "no-cache"); externalContext.setResponseContentType("text/xml"); writer.write("\n"); writer.startElement("partial-response", null); writer.startElement("redirect", null); writer.writeAttribute("url", url, null); writer.endElement("redirect"); writer.endElement("partial-response"); } else { // workaround for IllegalStateException from redirect of committed response if (externalContext.isResponseCommitted() && !context.getPartialViewContext().isAjaxRequest()) { PartialResponseWriter writer = context.getPartialViewContext().getPartialResponseWriter(); writer.startElement("script", null); writer.write("window.location.href = '" + url + "';"); writer.endElement("script"); writer.getWrapped().endDocument(); } else { externalContext.redirect(url); } } context.responseComplete(); } protected String constructRedirectUrl(FacesContext facesContext, String errorPage) { String url = facesContext.getExternalContext().getRequestContextPath() + errorPage; url = facesContext.getApplication().evaluateExpressionGet(facesContext, url, String.class); url = facesContext.getExternalContext().encodeActionURL(url); return url; } protected String evaluateErrorPage(Map errorPages, Throwable rootCause) { // get error page by exception type String errorPage = errorPages.get(rootCause.getClass().getName()); // lookup by inheritance hierarchy if (errorPage == null) { Class throwableClass = rootCause.getClass(); while (errorPage == null && throwableClass.getSuperclass() != Object.class) { throwableClass = throwableClass.getSuperclass(); errorPage = errorPages.get(throwableClass.getName()); } } // get default error page if (errorPage == null) { errorPage = errorPages.get(null); } if (errorPage == null) { throw new IllegalArgumentException( "No default error page (Status 500 or java.lang.Throwable) and no error page for type \"" + rootCause.getClass() + "\" defined!"); } return errorPage; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy