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 import static griffon.util.GriffonNameUtils.getShortName;
026 import static java.util.Arrays.asList;
027
028 /**
029 * Catches and sanitizes all uncaught exceptions.
030 *
031 * @author Danno Ferrin
032 * @author Andres Almiray
033 */
034 public class GriffonExceptionHandler implements ExceptionHandler {
035 private static final Logger LOG = LoggerFactory.getLogger(GriffonExceptionHandler.class);
036
037 private static final String[] CONFIG_OPTIONS = {
038 GRIFFON_FULL_STACKTRACE,
039 GRIFFON_EXCEPTION_OUTPUT
040 };
041
042 private static final String[] GRIFFON_PACKAGES =
043 System.getProperty("griffon.sanitized.stacktraces",
044 "org.codehaus.groovy.," +
045 "org.codehaus.griffon.," +
046 "groovy.," +
047 "java.," +
048 "javax.," +
049 "sun.," +
050 "com.sun.,"
051 ).split("(\\s|,)+");
052
053 private static final List<CallableWithArgs<Boolean>> TESTS = new ArrayList<>();
054
055 public static void addClassTest(CallableWithArgs<Boolean> test) {
056 TESTS.add(test);
057 }
058
059 private GriffonApplication application;
060
061 public GriffonExceptionHandler() {
062 Thread.setDefaultUncaughtExceptionHandler(this);
063 }
064
065 // @Inject
066 public GriffonExceptionHandler(GriffonApplication application) {
067 this.application = application;
068 Thread.setDefaultUncaughtExceptionHandler(this);
069 }
070
071 @Override
072 public void uncaughtException(Thread t, Throwable e) {
073 handle(e);
074 }
075
076 @Override
077 @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
078 public void handle(Throwable throwable) {
079 try {
080 sanitize(throwable);
081 if (isOutputEnabled()) throwable.printStackTrace(System.err);
082 LOG.error("Uncaught Exception", throwable);
083 if (application != null) {
084 application.getEventRouter().publishEvent("Uncaught" + getShortName(throwable.getClass()), asList(throwable));
085 application.getEventRouter().publishEvent(ApplicationEvent.UNCAUGHT_EXCEPTION_THROWN.getName(), asList(throwable));
086 }
087 } catch (Throwable t) {
088 sanitize(t);
089 if (isOutputEnabled()) t.printStackTrace(System.err);
090 LOG.error("An error occurred while handling uncaught exception " + throwable, t);
091 }
092 }
093
094 @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
095 public static Throwable sanitize(Throwable throwable) {
096 try {
097 if (!Boolean.getBoolean(GRIFFON_FULL_STACKTRACE)) {
098 deepSanitize(throwable);
099 }
100 } catch (Throwable t) {
101 // don't let the exception get thrown out, will cause infinite looping!
102 }
103 return throwable;
104 }
105
106 @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
107 public static StackTraceElement[] sanitize(StackTraceElement[] stackTrace) {
108 try {
109 if (!Boolean.getBoolean(GRIFFON_FULL_STACKTRACE)) {
110 Throwable t = new Throwable();
111 t.setStackTrace(stackTrace);
112 sanitize(t);
113 stackTrace = t.getStackTrace();
114 }
115 } catch (Throwable o) {
116 // don't let the exception get thrown out, will cause infinite looping!
117 }
118 return stackTrace;
119 }
120
121 public static boolean isOutputEnabled() {
122 return Boolean.getBoolean(GRIFFON_EXCEPTION_OUTPUT);
123 }
124
125 public static void configure(Map<String, Object> config) {
126 for (String option : CONFIG_OPTIONS) {
127 if (config.containsKey(option)) {
128 System.setProperty(option, String.valueOf(config.get(option)));
129 }
130 }
131 }
132
133 public static void registerExceptionHandler() {
134 Thread.setDefaultUncaughtExceptionHandler(new GriffonExceptionHandler());
135 System.setProperty("sun.awt.exception.handler", GriffonExceptionHandler.class.getName());
136 }
137
138 public static void handleThrowable(Throwable t) {
139 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(
140 Thread.currentThread(),
141 t
142 );
143 }
144
145 /**
146 * Sanitize the exception and ALL nested causes
147 * <p/>
148 * This will MODIFY the stacktrace of the exception instance and all its causes irreversibly
149 *
150 * @param t a throwable
151 * @return The root cause exception instances, with stack trace modified to filter out groovy runtime classes
152 */
153 @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
154 public static Throwable deepSanitize(Throwable t) {
155 Throwable current = t;
156 while (current.getCause() != null) {
157 current = doSanitize(current.getCause());
158 }
159 return doSanitize(t);
160 }
161
162 private static Throwable doSanitize(Throwable t) {
163 StackTraceElement[] trace = t.getStackTrace();
164 List<StackTraceElement> newTrace = new ArrayList<>();
165 for (StackTraceElement stackTraceElement : trace) {
166 if (isApplicationClass(stackTraceElement.getClassName())) {
167 newTrace.add(stackTraceElement);
168 }
169 }
170
171 StackTraceElement[] clean = new StackTraceElement[newTrace.size()];
172 newTrace.toArray(clean);
173 t.setStackTrace(clean);
174 return t;
175 }
176
177 private static boolean isApplicationClass(String className) {
178 for (CallableWithArgs<Boolean> test : TESTS) {
179 if (test.call(className)) {
180 return false;
181 }
182 }
183
184 for (String excludedPackage : GRIFFON_PACKAGES) {
185 if (className.startsWith(excludedPackage)) {
186 return false;
187 }
188 }
189 return true;
190 }
191 }
|