/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.deltaspike.jsf.impl.config.view; import org.apache.deltaspike.core.api.config.view.DefaultErrorView; import org.apache.deltaspike.core.api.config.view.metadata.ViewConfigResolver; import org.apache.deltaspike.core.api.config.view.navigation.ViewNavigationHandler; import org.apache.deltaspike.core.api.projectstage.ProjectStage; import org.apache.deltaspike.core.api.projectstage.TestStage; import org.apache.deltaspike.core.api.provider.BeanProvider; import org.apache.deltaspike.core.spi.activation.Deactivatable; import org.apache.deltaspike.core.util.ProjectStageProducer; import javax.enterprise.context.ContextNotActiveException; import javax.faces.FacesException; import javax.faces.application.ViewExpiredException; import javax.faces.component.UIViewRoot; import javax.faces.context.ExceptionHandler; import javax.faces.context.ExceptionHandlerWrapper; import javax.faces.context.FacesContext; import javax.faces.context.Flash; import javax.faces.event.ExceptionQueuedEvent; import javax.faces.event.ExceptionQueuedEventContext; import java.util.Iterator; public class DefaultErrorViewAwareExceptionHandlerWrapper extends ExceptionHandlerWrapper implements Deactivatable { private ExceptionHandler wrapped; private ViewNavigationHandler viewNavigationHandler; /** * Constructor used by proxy libs */ protected DefaultErrorViewAwareExceptionHandlerWrapper() { } public DefaultErrorViewAwareExceptionHandlerWrapper(ExceptionHandler wrapped) { this.wrapped = wrapped; } @Override public void handle() throws FacesException { lazyInit(); Iterator<ExceptionQueuedEvent> exceptionQueuedEventIterator = getUnhandledExceptionQueuedEvents().iterator(); while (exceptionQueuedEventIterator.hasNext()) { ExceptionQueuedEventContext exceptionQueuedEventContext = (ExceptionQueuedEventContext) exceptionQueuedEventIterator.next().getSource(); @SuppressWarnings({ "ThrowableResultOfMethodCallIgnored" }) Throwable throwable = exceptionQueuedEventContext.getException(); String viewId = null; if (!isExceptionToHandle(throwable)) { continue; } FacesContext facesContext = exceptionQueuedEventContext.getContext(); Flash flash = facesContext.getExternalContext().getFlash(); if (throwable instanceof ViewExpiredException) { viewId = ((ViewExpiredException) throwable).getViewId(); } else if (throwable instanceof ContextNotActiveException) { //the error page uses a cdi scope which isn't active as well //(it's recorded below - see flash.put(throwable.getClass().getName(), throwable);) if (flash.containsKey(ContextNotActiveException.class.getName())) { //TODO show it in case of project-stage development break; } if (facesContext.getViewRoot() != null) { viewId = facesContext.getViewRoot().getViewId(); } else { viewId = BeanProvider.getContextualReference(ViewConfigResolver.class) //has to return a value otherwise this handler wouldn't be active .getDefaultErrorViewConfigDescriptor().getViewId(); } } if (viewId != null) { UIViewRoot uiViewRoot = facesContext.getApplication().getViewHandler().createView(facesContext, viewId); if (uiViewRoot == null) { continue; } if (facesContext.isProjectStage(javax.faces.application.ProjectStage.Development) || ProjectStageProducer.getInstance().getProjectStage() == ProjectStage.Development || ProjectStageProducer.getInstance().getProjectStage() instanceof TestStage) { throwable.printStackTrace(); } facesContext.setViewRoot(uiViewRoot); exceptionQueuedEventIterator.remove(); //record the current exception -> to check it at the next call or to use it on the error-page flash.put(throwable.getClass().getName(), throwable); flash.keep(throwable.getClass().getName()); this.viewNavigationHandler.navigateTo(DefaultErrorView.class); break; } } this.wrapped.handle(); } //TODO it should be possible to configure the exceptions we handle //e.g. @View could specify which exception/s a page // - can recover from (-> redirect in case of a POST to refresh the state + optional message) // - can't recover from (-> redirect to the default-error page) protected boolean isExceptionToHandle(Throwable throwable) { return throwable instanceof ViewExpiredException || throwable instanceof ContextNotActiveException; } private void lazyInit() { if (this.viewNavigationHandler == null) { this.viewNavigationHandler = BeanProvider.getContextualReference(ViewNavigationHandler.class); } } @Override public ExceptionHandler getWrapped() { lazyInit(); return wrapped; } }