/*
* Copyright 2011 SpringSource
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.grails.exceptions.reporting;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Default implementation of StackTraceFilterer.
*
* @since 2.0
* @author Graeme Rocher
*/
public class DefaultStackTraceFilterer implements StackTraceFilterer {
public static final String STACK_LOG_NAME = "StackTrace";
public static final Log STACK_LOG = LogFactory.getLog(STACK_LOG_NAME);
private static final String[] DEFAULT_INTERNAL_PACKAGES = new String[] {
"org.codehaus.groovy.runtime.",
"org.codehaus.groovy.reflection.",
"org.codehaus.groovy.ast.",
"org.springframework.web.filter",
"org.springframework.boot.actuate",
"org.mortbay.",
"groovy.lang.",
"org.apache.catalina.",
"org.apache.coyote.",
"org.apache.tomcat.",
"net.sf.cglib.proxy.",
"sun.",
"java.lang.reflect.",
"org.springsource.loaded.",
"com.opensymphony.",
"javax.servlet."
};
private List<String> packagesToFilter = new ArrayList<String>();
private boolean shouldFilter;
private String cutOffPackage = null;
public DefaultStackTraceFilterer() {
this(!Boolean.getBoolean(SYS_PROP_DISPLAY_FULL_STACKTRACE));
}
public DefaultStackTraceFilterer(boolean shouldFilter) {
this.shouldFilter = shouldFilter;
packagesToFilter.addAll(Arrays.asList(DEFAULT_INTERNAL_PACKAGES));
}
public void addInternalPackage(String name) {
if (name == null) throw new IllegalArgumentException("Package name cannot be null");
packagesToFilter.add(name);
}
public void setCutOffPackage(String cutOffPackage) {
this.cutOffPackage = cutOffPackage;
}
public Throwable filter(Throwable source, boolean recursive) {
if (recursive) {
Throwable current = source;
while (current != null) {
current = filter(current);
current = current.getCause();
}
}
return filter(source);
}
public Throwable filter(Throwable source) {
if (shouldFilter) {
StackTraceElement[] trace = source.getStackTrace();
List<StackTraceElement> newTrace = filterTraceWithCutOff(trace, cutOffPackage);
if (newTrace.isEmpty()) {
// filter with no cut-off so at least there is some trace
newTrace = filterTraceWithCutOff(trace, null);
}
// Only trim the trace if there was some application trace on the stack
// if not we will just skip sanitizing and leave it as is
if (!newTrace.isEmpty()) {
// We don't want to lose anything, so log it
STACK_LOG.error(FULL_STACK_TRACE_MESSAGE, source);
StackTraceElement[] clean = new StackTraceElement[newTrace.size()];
newTrace.toArray(clean);
source.setStackTrace(clean);
}
}
return source;
}
private List<StackTraceElement> filterTraceWithCutOff(StackTraceElement[] trace, String endPackage) {
List<StackTraceElement> newTrace = new ArrayList<StackTraceElement>();
boolean foundGroovy = false;
for (StackTraceElement stackTraceElement : trace) {
String className = stackTraceElement.getClassName();
String fileName = stackTraceElement.getFileName();
if (!foundGroovy && fileName != null && fileName.endsWith(".groovy")) {
foundGroovy = true;
}
if (endPackage != null && className.startsWith(endPackage) && foundGroovy) break;
if (isApplicationClass(className)) {
if (stackTraceElement.getLineNumber() > -1) {
newTrace.add(stackTraceElement);
}
}
}
return newTrace;
}
/**
* Whether the given class name is an internal class and should be filtered
* @param className The class name
* @return true if is internal
*/
protected boolean isApplicationClass(String className) {
for (String packageName : packagesToFilter) {
if (className.startsWith(packageName)) return false;
}
return true;
}
public void setShouldFilter(boolean shouldFilter) {
this.shouldFilter = shouldFilter;
}
}