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