001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.internal.services.exceptions; 014 015import org.apache.tapestry5.SymbolConstants; 016import org.apache.tapestry5.func.F; 017import org.apache.tapestry5.func.Flow; 018import org.apache.tapestry5.func.Mapper; 019import org.apache.tapestry5.func.Reducer; 020import org.apache.tapestry5.internal.TapestryInternalUtils; 021import org.apache.tapestry5.ioc.annotations.Inject; 022import org.apache.tapestry5.ioc.annotations.Symbol; 023import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 024import org.apache.tapestry5.ioc.internal.util.InternalUtils; 025import org.apache.tapestry5.ioc.services.ExceptionAnalysis; 026import org.apache.tapestry5.ioc.services.ExceptionAnalyzer; 027import org.apache.tapestry5.ioc.services.ExceptionInfo; 028import org.apache.tapestry5.services.ExceptionReportWriter; 029import org.apache.tapestry5.services.Request; 030import org.apache.tapestry5.services.RequestGlobals; 031import org.apache.tapestry5.services.Session; 032 033import java.io.PrintWriter; 034import java.lang.reflect.Array; 035import java.util.ArrayList; 036import java.util.Iterator; 037import java.util.List; 038 039public class ExceptionReportWriterImpl implements ExceptionReportWriter 040{ 041 private static final Reducer<Integer, Integer> MAX = new Reducer<Integer, Integer>() 042 { 043 @Override 044 public Integer reduce(Integer accumulator, Integer element) 045 { 046 return Math.max(accumulator, element); 047 } 048 }; 049 050 private static final Mapper<String, Integer> STRING_TO_LENGTH = new Mapper<String, Integer>() 051 { 052 @Override 053 public Integer map(String element) 054 { 055 return element.length(); 056 } 057 }; 058 059 private final static Mapper<ExceptionInfo, Flow<String>> EXCEPTION_INFO_TO_PROPERTY_NAMES = 060 new Mapper<ExceptionInfo, Flow<String>>() 061 { 062 @Override 063 public Flow<String> map(ExceptionInfo element) 064 { 065 return F.flow(element.getPropertyNames()); 066 } 067 }; 068 069 /** 070 * A little closure that understands how to write a key/value pair representing a property. 071 */ 072 interface PropertyWriter 073 { 074 void write(String name, Object value); 075 } 076 077 @Inject 078 private ExceptionAnalyzer analyzer; 079 080 @Inject 081 private RequestGlobals requestGlobals; 082 083 @Inject 084 @Symbol(SymbolConstants.CONTEXT_PATH) 085 private String contextPath; 086 087 @Override 088 public void writeReport(PrintWriter writer, Throwable exception) 089 { 090 writeReport(writer, analyzer.analyze(exception)); 091 } 092 093 private PropertyWriter newPropertyWriter(final PrintWriter writer, Iterable<String> names) 094 { 095 final int maxPropertyNameLength = F.flow(names).map(STRING_TO_LENGTH).reduce(MAX, 0); 096 097 final String propertyNameFormat = " %" + maxPropertyNameLength + "s: %s%n"; 098 099 return new PropertyWriter() 100 { 101 @SuppressWarnings("rawtypes") 102 @Override 103 public void write(String name, Object value) 104 { 105 if (value.getClass().isArray()) 106 { 107 write(name, toList(value)); 108 return; 109 } 110 111 if (value instanceof Iterable) 112 { 113 boolean first = true; 114 Iterable iterable = (Iterable) value; 115 Iterator i = iterable.iterator(); 116 while (i.hasNext()) 117 { 118 if (first) 119 { 120 writer.printf(propertyNameFormat, name, i.next()); 121 first = false; 122 } else 123 { 124 for (int j = 0; j < maxPropertyNameLength + 4; j++) 125 writer.write(' '); 126 127 writer.println(i.next()); 128 } 129 } 130 return; 131 } 132 133 writer.printf(propertyNameFormat, name, value); 134 } 135 136 @SuppressWarnings({"rawtypes", "unchecked"}) 137 private List toList(Object array) 138 { 139 int count = Array.getLength(array); 140 List result = new ArrayList(count); 141 for (int i = 0; i < count; i++) 142 { 143 result.add(Array.get(array, i)); 144 } 145 return result; 146 } 147 }; 148 } 149 150 @Override 151 public void writeReport(final PrintWriter writer, ExceptionAnalysis analysis) 152 { 153 writer.printf("EXCEPTION STACK:%n%n"); 154 155 // Figure out what all the property names are so that we can set the width of the column that lists 156 // property names. 157 Flow<String> propertyNames = F.flow(analysis.getExceptionInfos()) 158 .mapcat(EXCEPTION_INFO_TO_PROPERTY_NAMES).append("Exception", "Message"); 159 160 PropertyWriter pw = newPropertyWriter(writer, propertyNames); 161 162 boolean first = true; 163 164 for (ExceptionInfo info : analysis.getExceptionInfos()) 165 { 166 if (first) 167 { 168 writer.println(); 169 first = false; 170 } 171 172 pw.write("Exception", info.getClassName()); 173 pw.write("Message", info.getMessage()); 174 175 for (String name : info.getPropertyNames()) 176 { 177 pw.write(name, info.getProperty(name)); 178 } 179 if (!info.getStackTrace().isEmpty()) 180 { 181 writer.printf("%n Stack trace:%n%n"); 182 for (StackTraceElement e : info.getStackTrace()) 183 { 184 writer.printf(" - %s%n", e.toString()); 185 } 186 } 187 writer.println(); 188 } 189 190 Request request = requestGlobals.getRequest(); 191 192 if (request != null) 193 { 194 // New PropertyWriter based on the lengths of parameter names and header names, and a sample of 195 // the literal keys. 196 197 pw = newPropertyWriter(writer, 198 F.flow(request.getParameterNames()) 199 .concat(request.getHeaderNames()) 200 .append("serverName", "removeHost")); 201 202 writer.printf("REQUEST:%n%nBasic Information:%n%n"); 203 204 List<String> flags = CollectionFactory.newList(); 205 if (request.isXHR()) 206 { 207 flags.add("XHR"); 208 } 209 if (request.isRequestedSessionIdValid()) 210 { 211 flags.add("requestedSessionIdValid"); 212 } 213 if (request.isSecure()) 214 { 215 flags.add("secure"); 216 } 217 pw.write("contextPath", contextPath); 218 219 if (!flags.isEmpty()) 220 { 221 pw.write("flags", InternalUtils.joinSorted(flags)); 222 } 223 pw.write("method", request.getMethod()); 224 pw.write("path", request.getPath()); 225 pw.write("locale", request.getLocale()); 226 pw.write("serverName", request.getServerName()); 227 pw.write("remoteHost", request.getRemoteHost()); 228 229 writer.printf("%nHeaders:%n%n"); 230 231 for (String name : request.getHeaderNames()) 232 { 233 pw.write(name, request.getHeader(name)); 234 } 235 if (!request.getParameterNames().isEmpty()) 236 { 237 writer.printf("%nParameters:%n"); 238 for (String name : request.getParameterNames()) 239 { 240 // TODO: Support multi-value parameters 241 pw.write(name, request.getParameters(name)); 242 } 243 } 244 245 Session session = request.getSession(false); 246 247 if (session != null) 248 { 249 pw = newPropertyWriter(writer, session.getAttributeNames()); 250 251 writer.printf("%nSESSION:%n%n"); 252 253 for (String name : session.getAttributeNames()) 254 { 255 pw.write(name, session.getAttribute(name)); 256 } 257 } 258 } 259 260 writer.printf("%nSYSTEM INFORMATION:"); 261 262 Runtime runtime = Runtime.getRuntime(); 263 264 writer.printf("%n%nMemory:%n %,15d bytes free%n %,15d bytes total%n %,15d bytes max%n", 265 runtime.freeMemory(), 266 runtime.totalMemory(), 267 runtime.maxMemory()); 268 269 Thread[] threads = TapestryInternalUtils.getAllThreads(); 270 271 int maxThreadNameLength = 0; 272 273 for (Thread t : threads) 274 { 275 maxThreadNameLength = Math.max(maxThreadNameLength, t.getName().length()); 276 } 277 278 String format = "%n%s %" + maxThreadNameLength + "s %s"; 279 280 writer.printf("%n%,d Threads:", threads.length); 281 282 for (Thread t : threads) 283 { 284 writer.printf(format, 285 Thread.currentThread() == t ? "*" : " ", 286 t.getName(), 287 t.getState().name()); 288 289 if (t.isDaemon()) 290 { 291 writer.write(", daemon"); 292 } 293 294 if (!t.isAlive()) 295 { 296 writer.write(", NOT alive"); 297 } 298 299 if (t.isInterrupted()) 300 { 301 writer.write(", interrupted"); 302 } 303 304 if (t.getPriority() != Thread.NORM_PRIORITY) 305 { 306 writer.printf(", priority %d", t.getPriority()); 307 } 308 } 309 310 // Finish the final line. 311 writer.println(); 312 } 313}