001 // Copyright 2006, 2007, 2008, 2009, 2010 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry5.ioc.internal.services; 016 017 import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 018 import org.apache.tapestry5.ioc.services.*; 019 020 import java.util.*; 021 022 public class ExceptionAnalyzerImpl implements ExceptionAnalyzer 023 { 024 private final PropertyAccess propertyAccess; 025 026 private final Set<String> throwableProperties; 027 028 /** 029 * A tuple used to communicate up a lavel both the exception info 030 * and the next exception in the stack. 031 */ 032 private class ExceptionData 033 { 034 final ExceptionInfo exceptionInfo; 035 final Throwable cause; 036 037 public ExceptionData(ExceptionInfo exceptionInfo, Throwable cause) 038 { 039 this.exceptionInfo = exceptionInfo; 040 this.cause = cause; 041 } 042 } 043 044 public ExceptionAnalyzerImpl(PropertyAccess propertyAccess) 045 { 046 this.propertyAccess = propertyAccess; 047 048 throwableProperties = CollectionFactory.newSet(this.propertyAccess.getAdapter(Throwable.class) 049 .getPropertyNames()); 050 } 051 052 public ExceptionAnalysis analyze(Throwable rootException) 053 { 054 List<ExceptionInfo> list = CollectionFactory.newList(); 055 056 Throwable t = rootException; 057 058 ExceptionInfo previousInfo = null; 059 060 while (t != null) 061 { 062 ExceptionData data = extractData(t); 063 064 ExceptionInfo info = data.exceptionInfo; 065 066 if (addsValue(previousInfo, info)) 067 { 068 list.add(info); 069 previousInfo = info; 070 } 071 072 t = data.cause; 073 } 074 075 return new ExceptionAnalysisImpl(list); 076 } 077 078 /** 079 * We want to filter out exceptions that do not provide any additional value. Additional value includes: an 080 * exception message not present in the containing exception or a property value not present in the containing 081 * exception. Also the first exception is always valued and the last exception (with the stack trace) is valued. 082 * 083 * @param previousInfo 084 * @param info 085 * @return 086 */ 087 private boolean addsValue(ExceptionInfo previousInfo, ExceptionInfo info) 088 { 089 if (previousInfo == null) 090 return true; 091 092 if (!info.getStackTrace().isEmpty()) 093 return true; 094 095 // TAP5-508: This adds back in a large number of frames that used to be squashed. 096 if (!info.getClassName().equals(previousInfo.getClassName())) 097 return true; 098 099 if (!previousInfo.getMessage().contains(info.getMessage())) 100 return true; 101 102 for (String name : info.getPropertyNames()) 103 { 104 if (info.getProperty(name).equals(previousInfo.getProperty(name))) 105 continue; 106 107 // Found something new and different at this level. 108 109 return true; 110 } 111 112 // This exception adds nothing that is not present at a higher level. 113 114 return false; 115 } 116 117 private ExceptionData extractData(Throwable t) 118 { 119 Map<String, Object> properties = CollectionFactory.newMap(); 120 121 ClassPropertyAdapter adapter = propertyAccess.getAdapter(t); 122 123 Throwable cause = null; 124 125 for (String name : adapter.getPropertyNames()) 126 { 127 PropertyAdapter pa = adapter.getPropertyAdapter(name); 128 129 if (!pa.isRead()) 130 continue; 131 132 if (cause == null && Throwable.class.isAssignableFrom(pa.getType())) 133 { 134 // Ignore the property, but track it as the cause. 135 136 Throwable nestedException = (Throwable) pa.get(t); 137 138 // Handle the case where an exception is its own cause (avoid endless loop!) 139 if (t != nestedException) 140 cause = nestedException; 141 142 continue; 143 } 144 145 // Otherwise, ignore properties defined by the Throwable class 146 147 if (throwableProperties.contains(name)) 148 continue; 149 150 Object value = pa.get(t); 151 152 if (value == null) 153 continue; 154 155 // An interesting property, let's save it for the analysis. 156 157 properties.put(name, value); 158 } 159 160 // Provide the stack trace only at the deepest exception. 161 162 List<StackTraceElement> stackTrace = Collections.emptyList(); 163 164 // Usually, I'd use a terniary expression here, but Generics gets in 165 // the way here. 166 167 if (cause == null) 168 stackTrace = Arrays.asList(t.getStackTrace()); 169 170 ExceptionInfo info = new ExceptionInfoImpl(t, properties, stackTrace); 171 172 return new ExceptionData(info, cause); 173 } 174 }