| 
001 /*002  * Copyright 2008-2015 the original author or authors.
 003  *
 004  * Licensed under the Apache License, Version 2.0 (the "License");
 005  * you may not use this file except in compliance with the License.
 006  * You may obtain a copy of the License at
 007  *
 008  *     http://www.apache.org/licenses/LICENSE-2.0
 009  *
 010  * Unless required by applicable law or agreed to in writing, software
 011  * distributed under the License is distributed on an "AS IS" BASIS,
 012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 013  * See the License for the specific language governing permissions and
 014  * limitations under the License.
 015  */
 016 package griffon.core;
 017
 018 import org.slf4j.Logger;
 019 import org.slf4j.LoggerFactory;
 020
 021 import java.util.ArrayList;
 022 import java.util.List;
 023 import java.util.Map;
 024
 025 /**
 026  * Catches and sanitizes all uncaught exceptions.
 027  *
 028  * @author Danno Ferrin
 029  * @author Andres Almiray
 030  */
 031 public class GriffonExceptionHandler implements Thread.UncaughtExceptionHandler {
 032     /**
 033      * "griffon.full.stacktrace"
 034      */
 035     public static final String GRIFFON_FULL_STACKTRACE = "griffon.full.stacktrace";
 036
 037     /**
 038      * "griffon.exception.output"
 039      */
 040     public static final String GRIFFON_EXCEPTION_OUTPUT = "griffon.exception.output";
 041
 042     private static final Logger LOG = LoggerFactory.getLogger(GriffonExceptionHandler.class);
 043     private static final String[] CONFIG_OPTIONS = {
 044         GRIFFON_FULL_STACKTRACE,
 045         GRIFFON_EXCEPTION_OUTPUT
 046     };
 047
 048     private static final String[] GRIFFON_PACKAGES =
 049         System.getProperty("griffon.sanitized.stacktraces",
 050             "org.codehaus.groovy.," +
 051                 "org.codehaus.griffon.," +
 052                 "groovy.," +
 053                 "java.," +
 054                 "javax.," +
 055                 "sun.," +
 056                 "com.sun.,"
 057         ).split("(\\s|,)+");
 058
 059     private static final List<CallableWithArgs<Boolean>> TESTS = new ArrayList<>();
 060
 061     public static void addClassTest(CallableWithArgs<Boolean> test) {
 062         TESTS.add(test);
 063     }
 064
 065     public void uncaughtException(Thread t, Throwable e) {
 066         handle(e);
 067     }
 068
 069     @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
 070     public void handle(Throwable throwable) {
 071         try {
 072             sanitize(throwable);
 073             if (isOutputEnabled()) throwable.printStackTrace(System.err);
 074             // GriffonApplication app = ApplicationHolder.getApplication();
 075             LOG.error("Uncaught Exception", throwable);
 076             //if (app != null) {
 077             //    app.event("Uncaught" + GriffonNameUtils.getShortName(throwable.getClass()), asList(throwable));
 078             //    app.event(ApplicationEvent.UNCAUGHT_EXCEPTION_THROWN.getName(), asList(throwable));
 079             //}
 080         } catch (Throwable t) {
 081             sanitize(t);
 082             if (isOutputEnabled()) t.printStackTrace(System.err);
 083             LOG.error("An error occurred while handling uncaught exception " + throwable, t);
 084         }
 085     }
 086
 087     @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
 088     public static Throwable sanitize(Throwable throwable) {
 089         try {
 090             if (!Boolean.getBoolean(GRIFFON_FULL_STACKTRACE)) {
 091                 deepSanitize(throwable);
 092             }
 093         } catch (Throwable t) {
 094             // don't let the exception get thrown out, will cause infinite looping!
 095         }
 096         return throwable;
 097     }
 098
 099     @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
 100     public static StackTraceElement[] sanitize(StackTraceElement[] stackTrace) {
 101         try {
 102             if (!Boolean.getBoolean(GRIFFON_FULL_STACKTRACE)) {
 103                 Throwable t = new Throwable();
 104                 t.setStackTrace(stackTrace);
 105                 sanitize(t);
 106                 stackTrace = t.getStackTrace();
 107             }
 108         } catch (Throwable o) {
 109             // don't let the exception get thrown out, will cause infinite looping!
 110         }
 111         return stackTrace;
 112     }
 113
 114     public static boolean isOutputEnabled() {
 115         return Boolean.getBoolean(GRIFFON_EXCEPTION_OUTPUT);
 116     }
 117
 118     public static void configure(Map<String, Object> config) {
 119         for (String option : CONFIG_OPTIONS) {
 120             if (config.containsKey(option)) {
 121                 System.setProperty(option, String.valueOf(config.get(option)));
 122             }
 123         }
 124     }
 125
 126     public static void registerExceptionHandler() {
 127         Thread.setDefaultUncaughtExceptionHandler(new GriffonExceptionHandler());
 128         System.setProperty("sun.awt.exception.handler", GriffonExceptionHandler.class.getName());
 129     }
 130
 131     public static void handleThrowable(Throwable t) {
 132         Thread.getDefaultUncaughtExceptionHandler().uncaughtException(
 133             Thread.currentThread(),
 134             t
 135         );
 136     }
 137
 138     /**
 139      * Sanitize the exception and ALL nested causes
 140      * <p/>
 141      * This will MODIFY the stacktrace of the exception instance and all its causes irreversibly
 142      *
 143      * @param t a throwable
 144      * @return The root cause exception instances, with stack trace modified to filter out groovy runtime classes
 145      */
 146     @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
 147     public static Throwable deepSanitize(Throwable t) {
 148         Throwable current = t;
 149         while (current.getCause() != null) {
 150             current = doSanitize(current.getCause());
 151         }
 152         return doSanitize(t);
 153     }
 154
 155     private static Throwable doSanitize(Throwable t) {
 156         StackTraceElement[] trace = t.getStackTrace();
 157         List<StackTraceElement> newTrace = new ArrayList<>();
 158         for (StackTraceElement stackTraceElement : trace) {
 159             if (isApplicationClass(stackTraceElement.getClassName())) {
 160                 newTrace.add(stackTraceElement);
 161             }
 162         }
 163
 164         StackTraceElement[] clean = new StackTraceElement[newTrace.size()];
 165         newTrace.toArray(clean);
 166         t.setStackTrace(clean);
 167         return t;
 168     }
 169
 170     private static boolean isApplicationClass(String className) {
 171         for (CallableWithArgs<Boolean> test : TESTS) {
 172             if (test.call(className)) {
 173                 return false;
 174             }
 175         }
 176
 177         for (String excludedPackage : GRIFFON_PACKAGES) {
 178             if (className.startsWith(excludedPackage)) {
 179                 return false;
 180             }
 181         }
 182         return true;
 183     }
 184 }
 |