GriffonExceptionHandler.java
001 /*
002  * Copyright 2008-2014 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 }