package org.jboss.seam.exception; import static org.jboss.seam.annotations.Install.BUILT_IN; import static org.jboss.seam.exception.ExceptionHandler.LogLevel; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import javax.faces.application.FacesMessage; import javax.faces.application.FacesMessage.Severity; import org.dom4j.DocumentException; import org.dom4j.Element; import org.jboss.seam.Component; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Create; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.contexts.Contexts; import org.jboss.seam.core.Events; import org.jboss.seam.core.Expressions; import org.jboss.seam.core.Init; import org.jboss.seam.core.ResourceLoader; import org.jboss.seam.log.LogProvider; import org.jboss.seam.log.Logging; import org.jboss.seam.navigation.Pages; import org.jboss.seam.util.Reflections; import org.jboss.seam.util.Resources; import org.jboss.seam.util.Strings; import org.jboss.seam.util.XML; /** * Manages the exception handler chain * * @author Gavin King */ @Scope(ScopeType.APPLICATION) @BypassInterceptors @Install(precedence = BUILT_IN, classDependencies = "javax.faces.context.FacesContext") @Name("org.jboss.seam.exception.exceptions") public class Exceptions { private static final LogProvider log = Logging.getLogProvider(Exceptions.class); private List<ExceptionHandler> exceptionHandlers = new ArrayList<ExceptionHandler>(); public void handle(Exception e) throws Exception { if (Contexts.isConversationContextActive()) { Contexts.getConversationContext().set("org.jboss.seam.caughtException", e); } // build a list of the nested exceptions List<Exception> causes = new ArrayList<Exception>(); for (Exception cause = e; cause != null; cause = org.jboss.seam.util.Exceptions.getCause(cause)) { causes.add(cause); } // try to match each handler in turn for (ExceptionHandler eh : exceptionHandlers) { // Try to handle most-nested exception before least-nested for (int i = causes.size() - 1; i >= 0; i--) { Exception cause = causes.get(i); if (eh.isHandler(cause)) { if (Contexts.isConversationContextActive()) { Contexts.getConversationContext().set("org.jboss.seam.handledException", cause); } eh.handle(cause); if (eh.isLogEnabled() && eh.getLogLevel() != null) { switch (eh.getLogLevel()) { case fatal: log.fatal("handled and logged exception", e); break; case error: log.error("handled and logged exception", e); break; case warn: log.warn("handled and logged exception", e); break; case info: log.info("handled and logged exception", e); break; case debug: log.debug("handled and logged exception", e); break; case trace: log.trace("handled and logged exception", e); } } if(Contexts.isEventContextActive()) { Events.instance().raiseEvent("org.jboss.seam.exceptionHandled." + cause.getClass().getName(), cause); Events.instance().raiseEvent("org.jboss.seam.exceptionHandled", cause); } return; } } } // finally, rethrow it, since no handler was found Events.instance().raiseEvent("org.jboss.seam.exceptionNotHandled", e); throw e; } @Create public void initialize() throws Exception { List<ExceptionHandler> deferredHandlers = new ArrayList<ExceptionHandler>(); deferredHandlers.add(parse("/WEB-INF/exceptions.xml")); // deprecated for (String pageFile : Pages.instance().getResources()) { deferredHandlers.add(parse(pageFile)); } addHandler(new AnnotationRedirectHandler()); addHandler(new AnnotationErrorHandler()); if (Init.instance().isDebugPageAvailable()) { addHandler(new DebugPageHandler()); } for (ExceptionHandler handler : deferredHandlers) { addHandler(handler); } } private void addHandler(ExceptionHandler handler) { if (handler != null) exceptionHandlers.add(handler); } private ExceptionHandler parse(String fileName) throws DocumentException, ClassNotFoundException { ExceptionHandler anyhandler = null; InputStream stream = ResourceLoader.instance().getResourceAsStream(fileName); if (stream != null) { log.debug("reading exception mappings from " + fileName); List<Element> elements = null; try { elements = XML.getRootElement(stream).elements("exception"); } finally { Resources.closeStream(stream); } for (final Element exception : elements) { String className = exception.attributeValue("class"); boolean logEnabled = exception.attributeValue("log") != null ? Boolean.valueOf(exception.attributeValue("log")) : true; LogLevel logLevel = LogLevel.error; try { String levelValue = exception.attributeValue("log-level"); if (levelValue == null) { levelValue = exception.attributeValue("logLevel"); } if (levelValue != null) { logLevel = LogLevel.valueOf(levelValue.toLowerCase()); } } catch (IllegalArgumentException ex) { StringBuilder sb = new StringBuilder(); sb.append("Exception handler"); if (className != null) sb.append(" for class " + className); sb.append(" is configured with an invalid log-level. Acceptable " + "values are: fatal,error,warn,info,debug,trace. " + "A default level of 'error' has been configured instead."); log.warn(sb.toString()); } ExceptionHandler handler = null; try { if(className==null) handler = createHandler(exception, null); else handler = createHandler(exception, Reflections.classForName(className)); if (handler != null) { handler.setLogEnabled(logEnabled); handler.setLogLevel(logLevel); } } catch (ClassNotFoundException e) { log.error("Can't find exception class for exception handler", e); } if (handler != null) exceptionHandlers.add(handler); else if(className==null) { anyhandler = createHandler(exception, Exception.class); anyhandler.setLogEnabled(logEnabled); anyhandler.setLogLevel(logLevel); } } } return anyhandler; } private ExceptionHandler createHandler(Element exception, final Class clazz) { final boolean endConversation = exception.elementIterator("end-conversation").hasNext(); Element redirect = exception.element("redirect"); if (redirect != null) { String viewId = redirect.attributeValue("view-id"); Element messageElement = redirect.element("message"); final String message = messageElement == null ? null : messageElement.getTextTrim(); String severityName = messageElement == null ? null : messageElement.attributeValue("severity"); Severity severity = severityName == null ? FacesMessage.SEVERITY_INFO : Pages.getFacesMessageValuesMap().get(severityName.toUpperCase()); return new ConfigRedirectHandler(viewId == null ? null : Expressions.instance().createValueExpression(viewId, String.class), clazz, endConversation, message, severity); } Element error = exception.element("http-error"); if (error != null) { String errorCode = error.attributeValue("error-code"); final int code = Strings.isEmpty(errorCode) ? 500 : Integer.parseInt(errorCode); Element messageElement = error.element("message"); final String message = messageElement == null ? null : messageElement.getTextTrim(); return new ConfigErrorHandler(message, endConversation, clazz, code); } Element handleClass = exception.element("handle-class"); if (handleClass != null) { try { String className = handleClass.attributeValue("class-name"); Class<?> handleClazz = Reflections.classForName(className); Object ins = handleClazz.newInstance(); return (ExceptionHandler) ins; } catch (Exception e) { } } return null; } /** * @return the exception handler list, which supports addition and removal of * handlers */ public List<ExceptionHandler> getHandlers() { return exceptionHandlers; } public static Exceptions instance() { if (!Contexts.isApplicationContextActive()) { throw new IllegalStateException("No active application context"); } return (Exceptions) Component.getInstance(Exceptions.class, ScopeType.APPLICATION); } }