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 }
|