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