001 package hirondelle.web4j.webmaster; 002 003 import hirondelle.web4j.ApplicationInfo; 004 import hirondelle.web4j.BuildImpl; 005 import hirondelle.web4j.model.AppException; 006 import hirondelle.web4j.model.DateTime; 007 import hirondelle.web4j.readconfig.Config; 008 import hirondelle.web4j.util.Args; 009 import hirondelle.web4j.util.Consts; 010 import hirondelle.web4j.util.Util; 011 012 import java.io.PrintWriter; 013 import java.io.StringWriter; 014 import java.io.Writer; 015 import java.math.BigDecimal; 016 import java.math.RoundingMode; 017 import java.text.DecimalFormat; 018 import java.util.Arrays; 019 import java.util.Enumeration; 020 import java.util.HashMap; 021 import java.util.Iterator; 022 import java.util.List; 023 import java.util.Locale; 024 import java.util.Map; 025 import java.util.Set; 026 import java.util.TimeZone; 027 import java.util.TreeMap; 028 import java.util.regex.Pattern; 029 030 import javax.servlet.ServletConfig; 031 import javax.servlet.http.Cookie; 032 import javax.servlet.http.HttpServletRequest; 033 import javax.servlet.http.HttpSession; 034 035 /** 036 Email diagnostic information to support staff when an error occurs. 037 038 <P>Uses the following settings in <tt>web.xml</tt>: 039 <ul> 040 <li> <tt>Webmaster</tt> - the 'from' address. 041 <li> <tt>TroubleTicketMailingList</tt> - the 'to' addresses for the support staff. 042 <li> <tt>PoorPerformanceThreshold</tt> - when the response time exceeds this level, then 043 a <tt>TroubleTicket</tt> is sent 044 <li> <tt>MinimumIntervalBetweenTroubleTickets</tt> - throttles down emission of 045 <tt>TroubleTicket</tt>s, where many might be emitted in rapid succession, from the 046 same underlying cause 047 </ul> 048 049 <P>The {@link hirondelle.web4j.Controller} will create and send a <tt>TroubleTicket</tt> when: 050 <ul> 051 <li>an unexpected problem (a bug) occurs. The bug corresponds to an unexpected 052 {@link Throwable} emitted by either the application or the framework. 053 <li>the response time exceeds the <tt>PoorPerformanceThreshold</tt> configured in 054 <tt>web.xml</tt>. 055 </ul> 056 057 <P><em>Warning</em>: some e-mail spam filters may incorrectly treat the default content of these trouble 058 tickets (example below) as spam. 059 060 <P>Example content of a <tt>TroubleTicket</tt>, as returned by {@link #toString()}: 061 <PRE> 062 {@code 063 Error for web application Fish And Chips Club/4.6.2.0 064 *** java.lang.RuntimeException: Testing application behavior upon failure. *** 065 -------------------------------------------------------- 066 Time of error : 2011-09-12 19:59:32 067 Occurred for user : blah 068 Web application Build Date: Sat Jul 09 00:00:00 ADT 2011 069 Web application Author : Hirondelle Systems 070 Web application Link : http://www.web4j.com/ 071 Web application Message : Uses web4j.jar version 4.6.2 072 073 Request Info: 074 -------------------------------------------------------- 075 HTTP Method: GET 076 Context Path: /fish 077 ServletPath: /webmaster/testfailure/ForceFailure.do 078 URI: /fish/webmaster/testfailure/ForceFailure.do 079 URL: http://localhost:8081/fish/webmaster/testfailure/ForceFailure.do 080 Header accept = text/html,application/xhtml+xml,application/xml;q=0.9 081 Header accept-charset = UTF-8,* 082 Header accept-encoding = gzip,deflate 083 Header accept-language = en-us,en;q=0.5 084 Header connection = keep-alive 085 Header cookie = JSESSIONID=2C326412C32F6F823673A5FBD1C883A7 086 Header host = localhost:8081 087 Header keep-alive = 115 088 Header referer = http://localhost:8081/fish/webmaster/performance/ShowPerformance.do 089 Header user-agent = Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.22) ..[elided].. 090 Cookie JSESSIONID=2C326412C32F6F823673A5FBD1C883A7 091 092 Client Info: 093 -------------------------------------------------------- 094 User IP: 127.0.0.1 095 User hostname: 127.0.0.1 096 097 Session Info 098 -------------------------------------------------------- 099 Logged in user name : blah 100 Timeout : 900 seconds. 101 Session Attributes javax.servlet.jsp.jstl.fmt.request.charset = UTF-8 102 Session Attributes web4j_key_for_form_source_id = 214c125310311a6e4eda5aa6448b3c47d0a85d31 103 Session Attributes web4j_key_for_locale = en 104 Session Attributes web4j_key_for_previous_form_source_id = f2d6ae8487e555f90ae7c186f370b05bcf46f0d0 105 106 Memory Info: 107 -------------------------------------------------------- 108 JRE Memory 109 total: 66,650,112 110 used: 7,515,624 (11%) 111 free: 59,134,488 (89%) 112 113 114 Server And Servlet Info: 115 -------------------------------------------------------- 116 Name: localhost 117 Port: 8081 118 Info: Apache Tomcat/6.0.10 119 JRE default TimeZone: America/Halifax 120 JRE default Locale: English (Canada) 121 awt.toolkit: sun.awt.windows.WToolkit 122 catalina.base: C:\johanley\Projects\TomcatInstance 123 catalina.home: C:\Program Files\Tomcat6 124 catalina.useNaming: true 125 common.loader: ${catalina.home}/lib,${catalina.home}/lib/*.jar 126 file.encoding: UTF-8 127 file.encoding.pkg: sun.io 128 file.separator: \ 129 java.awt.graphicsenv: sun.awt.Win32GraphicsEnvironment 130 java.awt.printerjob: sun.awt.windows.WPrinterJob 131 java.class.path: .;C:\Program Files\Java\jre1.6.0_07\lib\ext\QTJava.zip;C:\Program Files\Tomcat6\bin\bootstrap.jar 132 java.class.version: 49.0 133 java.endorsed.dirs: C:\Program Files\Tomcat6\endorsed 134 java.ext.dirs: C:\jdk1.5.0\jre\lib\ext 135 java.home: C:\jdk1.5.0\jre 136 java.io.tmpdir: C:\johanley\Projects\TomcatInstance\temp 137 java.library.path: C:\jdk1.5.0\bin;.;C:\WINDOWS\system32;C:\WINDOWS;C:\jdk1.5.0\bin;..e[lided].. 138 java.naming.factory.initial: org.apache.naming.java.javaURLContextFactory 139 java.naming.factory.url.pkgs: org.apache.naming 140 java.runtime.name: Java(TM) 2 Runtime Environment, Standard Edition 141 java.runtime.version: 1.5.0_07-b03 142 java.specification.name: Java Platform API Specification 143 java.specification.vendor: Sun Microsystems Inc. 144 java.specification.version: 1.5 145 java.util.logging.config.file: C:\johanley\Projects\TomcatInstance\conf\logging.properties 146 java.util.logging.manager: org.apache.juli.ClassLoaderLogManager 147 java.vendor: Sun Microsystems Inc. 148 java.vendor.url: http://java.sun.com/ 149 java.vendor.url.bug: http://java.sun.com/cgi-bin/bugreport.cgi 150 java.version: 1.5.0_07 151 java.vm.info: mixed mode, sharing 152 java.vm.name: Java HotSpot(TM) Client VM 153 java.vm.specification.name: Java Virtual Machine Specification 154 java.vm.specification.vendor: Sun Microsystems Inc. 155 java.vm.specification.version: 1.0 156 java.vm.vendor: Sun Microsystems Inc. 157 java.vm.version: 1.5.0_07-b03 158 line.separator: 159 160 os.arch: x86 161 os.name: Windows XP 162 os.version: 5.1 163 package.access: sun.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.,sun.beans. 164 package.definition: sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper. 165 path.separator: ; 166 server.loader: 167 shared.loader: 168 sun.arch.data.model: 32 169 sun.boot.class.path: C:\jdk1.5.0\jre\lib\rt.jar;...[elided] 170 sun.boot.library.path: C:\jdk1.5.0\jre\bin 171 sun.cpu.endian: little 172 sun.cpu.isalist: 173 sun.desktop: windows 174 sun.io.unicode.encoding: UnicodeLittle 175 sun.jnu.encoding: Cp1252 176 sun.management.compiler: HotSpot Client Compiler 177 sun.os.patch.level: Service Pack 3 178 tomcat.util.buf.StringCache.byte.enabled: true 179 user.country: CA 180 user.dir: C:\johanley\Projects\TomcatInstance 181 user.home: C:\Documents and Settings\John 182 user.language: en 183 user.name: John 184 user.timezone: America/Halifax 185 user.variant: 186 java.class.path: 187 . 188 C:\Program Files\Java\jre1.6.0_07\lib\ext\QTJava.zip 189 C:\Program Files\Tomcat6\bin\bootstrap.jar 190 Servlet : Controller 191 Servlet init-param: AccessControlDbConnectionString = java:comp/env/jdbc/fish_access 192 Servlet init-param: AllowStringAsBuildingBlock = YES 193 Servlet init-param: BigDecimalDisplayFormat = #,##0.00 194 Servlet init-param: BooleanFalseDisplayFormat = <input type='checkbox' name='false' value='false' readonly notab> 195 Servlet init-param: BooleanTrueDisplayFormat = <input type='checkbox' name='true' value='true' checked readonly notab> 196 Servlet init-param: CharacterEncoding = UTF-8 197 Servlet init-param: DateTimeFormatForPassingParamsToDb = YYYY-MM-DD^hh:mm:ss^YYYY-MM-DD hh:mm:ss 198 Servlet init-param: DecimalSeparator = PERIOD 199 Servlet init-param: DecimalStyle = HALF_EVEN,2 200 Servlet init-param: DefaultDbConnectionString = java:comp/env/jdbc/fish 201 Servlet init-param: DefaultLocale = en 202 Servlet init-param: DefaultUserTimeZone = America/Halifax 203 Servlet init-param: EmptyOrNullDisplayFormat = - 204 Servlet init-param: ErrorCodeForDuplicateKey = 1062 205 Servlet init-param: ErrorCodeForForeignKey = 1216,1217,1451,1452 206 Servlet init-param: FetchSize = 25 207 Servlet init-param: FullyValidateFileUploads = ON 208 Servlet init-param: HasAutoGeneratedKeys = true 209 Servlet init-param: IgnorableParamValue = 210 Servlet init-param: ImplementationFor.hirondelle.web4j.security.LoginTasks = hirondelle.fish.all.preferences.Login 211 Servlet init-param: ImplicitMappingRemoveBasePackage = hirondelle.fish 212 Servlet init-param: IntegerDisplayFormat = #,### 213 Servlet init-param: IsSQLPrecompilationAttempted = true 214 Servlet init-param: LoggingDirectory = C:\log\fish\ 215 Servlet init-param: LoggingLevels = hirondelle.fish.level=FINE, hirondelle.web4j.level=FINE 216 Servlet init-param: MailServerConfig = mail.host=mail.blah.com 217 Servlet init-param: MailServerCredentials = NONE 218 Servlet init-param: MaxFileUploadRequestSize = 1048576 219 Servlet init-param: MaxHttpRequestSize = 51200 220 Servlet init-param: MaxRequestParamValueSize = 51200 221 Servlet init-param: MaxRows = 300 222 Servlet init-param: MinimumIntervalBetweenTroubleTickets = 30 223 Servlet init-param: PoorPerformanceThreshold = 20 224 Servlet init-param: SpamDetectionInFirewall = OFF 225 Servlet init-param: SqlEditorDefaultTxIsolationLevel = DATABASE_DEFAULT 226 Servlet init-param: SqlFetcherDefaultTxIsolationLevel = DATABASE_DEFAULT 227 Servlet init-param: TimeZoneHint = NONE 228 Servlet init-param: TranslationDbConnectionString = java:comp/env/jdbc/fish_translation 229 Servlet init-param: TroubleTicketMailingList = blah@blah.com 230 Servlet init-param: Webmaster = blah@blah.com 231 232 Stack Trace: 233 -------------------------------------------------------- 234 java.lang.RuntimeException: Testing application behavior upon failure. 235 at hirondelle.fish.webmaster.testfailure.ForceFailure.execute(ForceFailure.java:29) 236 at hirondelle.web4j.Controller.checkOwnershipThenExecuteAction(Unknown Source) 237 at hirondelle.web4j.Controller.processRequest(Unknown Source) 238 at hirondelle.web4j.Controller.doGet(Unknown Source) 239 ...[elided]... 240 } 241 </PRE> 242 */ 243 public final class TroubleTicket { 244 245 /** Called by the framework upon startup, to save the name of the server (Servlet 3.0 wouldn't need this). */ 246 public static void init(ServletConfig aConfig){ 247 fConfig = aConfig; 248 } 249 250 /** 251 Constructor. 252 253 @param aException has caused the problem. 254 @param aRequest original underlying HTTP request. 255 */ 256 public TroubleTicket(Throwable aException, HttpServletRequest aRequest){ 257 fException = aException; 258 fRequest = aRequest; 259 buildBodyOfMessage(); 260 } 261 262 /** 263 Constuctor sets custom content for the body of the email. 264 265 <P>When using this constructor, the detailed information shown in the class 266 comment is not generated. 267 @param aCustomBody the desired body of the email. 268 */ 269 public TroubleTicket(String aCustomBody) { 270 Args.checkForContent(aCustomBody); 271 fRequest = null; 272 fException = null; 273 fBody.append(aCustomBody); 274 } 275 276 /** 277 Return extensive listing of items which may be useful in solving the problem. 278 279 <P>See example in the class comment. 280 */ 281 @Override public String toString(){ 282 return fBody.toString(); 283 } 284 285 /** 286 Send an email to the <tt>TroubleTicketMailingList</tt> recipients configured in 287 <tt>web.xml</tt>. 288 289 <P>If sufficient time has passed since the last email of a <tt>TroubleTicket</tt>, 290 then send an email to the webmaster whose body is {@link #toString}; otherwise do 291 nothing. 292 293 <P>Here, "sufficient time" is defined by a setting in <tt>web.xml</tt> named 294 <tt>MinimumIntervalBetweenTroubleTickets</tt>. The intent is to throttle down on 295 emails which likely have the same cause. 296 */ 297 public void mailToRecipients() throws AppException { 298 if ( hasEnoughTimePassedSinceLastEmail() ) { 299 sendEmail(); 300 updateMostRecentTime(); 301 } 302 } 303 304 // PRIVATE 305 private static ServletConfig fConfig; 306 private final HttpServletRequest fRequest; 307 private final Throwable fException; 308 private final ApplicationInfo fAppInfo = BuildImpl.forApplicationInfo(); 309 310 private static final boolean DO_NOT_CREATE_SESSION = false; 311 private static final Pattern PASSWORD_PATTERN = Pattern.compile( 312 "password", Pattern.CASE_INSENSITIVE 313 ); 314 315 /** 316 The text which contains all relevant information which may be useful in solving 317 the problem. 318 */ 319 private StringBuilder fBody = new StringBuilder(); 320 321 /** 322 The time of the last send of a TroubleTicket email, expressed in 323 milliseconds since the Java epoch. 324 325 This static data is shared among requests, and all access to this 326 field must be synchronized. This synchronization should not be a problem in practice, 327 since this class is used only when there's a problem. 328 */ 329 private static long fTimeLastEmail; 330 331 /** Build fBody from its various parts. */ 332 private void buildBodyOfMessage() { 333 addExceptionSummary(); 334 addRequestInfo(); 335 addClientInfo(); 336 addSessionInfo(); 337 addMemoryInfo(); 338 addServerInfo(); 339 addStackTrace(); 340 } 341 342 private void addLine(String aLine){ 343 fBody.append(aLine + Consts.NEW_LINE); 344 } 345 346 private void addStartOfSection(String aHeader){ 347 addLine(Consts.EMPTY_STRING); 348 addLine(aHeader); 349 addLine("--------------------------------------------------------"); 350 } 351 352 private void addExceptionSummary(){ 353 addStartOfSection( 354 "Error for web application " + fAppInfo.getName() + "/" + fAppInfo.getVersion() + 355 "." + Consts.NEW_LINE + "*** " + fException.toString() + " ***" 356 ); 357 long nowMillis = BuildImpl.forTimeSource().currentTimeMillis(); 358 TimeZone tz = new Config().getDefaultUserTimeZone(); 359 DateTime now = DateTime.forInstant(nowMillis, tz); 360 addLine("Time of error : " + now.format("YYYY-MM-DD hh:mm:ss")); 361 addLine("Occurred for user : " + getLoggedInUser() ); 362 addLine("Web application Build Date: " + fAppInfo.getBuildDate()); 363 addLine("Web application Author : " + fAppInfo.getAuthor()); 364 addLine("Web application Link : " + fAppInfo.getLink()); 365 addLine("Web application Message : " + fAppInfo.getMessage()); 366 if ( fException instanceof AppException ) { 367 AppException appEx = (AppException)fException; 368 Iterator errorsIter = appEx.getMessages().iterator(); 369 while ( errorsIter.hasNext() ) { 370 addLine( errorsIter.next().toString() ); 371 } 372 } 373 } 374 375 private void addRequestInfo(){ 376 addStartOfSection("Request Info:"); 377 addLine("HTTP Method: " + fRequest.getMethod()); 378 addLine("Context Path: " + fRequest.getContextPath()); 379 addLine("ServletPath: " + fRequest.getServletPath()); 380 addLine("URI: " + fRequest.getRequestURI()); 381 addLine("URL: " + fRequest.getRequestURL().toString()); 382 addRequestParams(); 383 addRequestHeaders(); 384 addCookies(); 385 } 386 387 private void addClientInfo(){ 388 addStartOfSection("Client Info:"); 389 addLine("User IP: " + fRequest.getRemoteAddr()); 390 addLine("User hostname: " + fRequest.getRemoteHost()); 391 } 392 393 private void addServerInfo(){ 394 addStartOfSection("Server And Servlet Info:"); 395 addLine("Name: " + fRequest.getServerName()); 396 addLine("Port: " + fRequest.getServerPort()); 397 addLine("Info: " + fConfig.getServletContext().getServerInfo()); //in Servlet 3.0 this is attached to the request 398 addLine("JRE default TimeZone: " + TimeZone.getDefault().getID()); 399 addLine("JRE default Locale: " + Locale.getDefault().getDisplayName()); 400 401 addAllSystemProperties(); 402 addClassPath(); 403 addLine("Servlet : " + fConfig.getServletName()); 404 405 Map<String, String> servletParams = new Config().getRawMap(); 406 servletParams = sortMap(servletParams); 407 addMap(servletParams, "Servlet init-param: "); 408 } 409 410 private void addAllSystemProperties(){ 411 Map properties = sortMap(System.getProperties()); 412 Set props = properties.entrySet(); 413 Iterator iter = props.iterator(); 414 while ( iter.hasNext() ) { 415 Map.Entry entry = (Map.Entry)iter.next(); 416 addLine(entry.getKey() + ": " + entry.getValue()); 417 } 418 } 419 420 /** 421 Since this item tends to be very long, it is useful to place each entry 422 on a separate line. 423 */ 424 private void addClassPath(){ 425 String JAVA_CLASS_PATH = "java.class.path"; 426 String classPath = System.getProperty(JAVA_CLASS_PATH); 427 List pathElements = Arrays.asList( classPath.split(Consts.PATH_SEPARATOR) ); 428 StringBuilder result = new StringBuilder(Consts.NEW_LINE); 429 Iterator pathElementsIter = pathElements.iterator(); 430 while ( pathElementsIter.hasNext() ) { 431 String pathElement = (String)pathElementsIter.next(); 432 result.append(pathElement); 433 if ( pathElementsIter.hasNext() ) { 434 result.append(Consts.NEW_LINE); 435 } 436 } 437 addLine(JAVA_CLASS_PATH + ": " + result.toString()); 438 } 439 440 private void addStackTrace(){ 441 addStartOfSection("Stack Trace:"); 442 addLine( getStackTrace(fException) ); 443 } 444 445 private void addRequestParams(){ 446 Map paramMap = new HashMap(); 447 Enumeration namesEnum = fRequest.getParameterNames(); 448 while ( namesEnum.hasMoreElements() ){ 449 String name = (String)namesEnum.nextElement(); 450 String values = Util.getArrayAsString( fRequest.getParameterValues(name) ); 451 if( isPassword(name)){ 452 paramMap.put(name, "***(masked)***"); 453 } 454 else { 455 paramMap.put(name, values); 456 } 457 } 458 paramMap = sortMap(paramMap); 459 addMap(paramMap, "Req Param"); 460 } 461 462 private void addMemoryInfo(){ 463 addStartOfSection("Memory Info:"); 464 addLine(getMemory()); 465 } 466 467 private String getMemory(){ 468 return getTotalUsedFree(Runtime.getRuntime().totalMemory(), Runtime.getRuntime().freeMemory(), "JRE Memory"); 469 } 470 471 private String getTotalUsedFree(long aTotal, long aFree, String aDescription){ 472 StringBuilder result = new StringBuilder(); 473 BigDecimal total = new BigDecimal(aTotal); 474 BigDecimal used = new BigDecimal(aTotal - aFree); 475 BigDecimal free = new BigDecimal(aFree); 476 477 BigDecimal percentUsed = percent(used, total); 478 BigDecimal percentFree = percent(free, total); 479 480 result.append(aDescription + Consts.NEW_LINE) ; 481 String totalText = format(total); 482 String format = "%-9s%" + totalText.length() + "s"; 483 result.append(String.format(format, " total: ", totalText) + Consts.NEW_LINE) ; 484 result.append(String.format(format, " used: ", format(used)) + " (" + percentUsed.intValue() + "%)" + Consts.NEW_LINE) ; 485 result.append(String.format(format, " free: ", format(free)) + " (" + percentFree.intValue() + "%)" + Consts.NEW_LINE) ; 486 return result.toString(); 487 } 488 489 private BigDecimal percent(BigDecimal aA, BigDecimal aB){ 490 BigDecimal ZERO = new BigDecimal("0"); 491 BigDecimal result = ZERO; 492 BigDecimal HUNDRED = new BigDecimal("100"); 493 if( aB.compareTo(ZERO) != 0) { 494 result = aA.divide(aB, 2, RoundingMode.HALF_EVEN); 495 result = result.multiply(HUNDRED); 496 } 497 return result; 498 } 499 500 private String format(BigDecimal aNumber){ 501 DecimalFormat format = new DecimalFormat("#,###"); 502 return format.format(aNumber); 503 } 504 505 private boolean isPassword(String aName){ 506 return Util.contains(PASSWORD_PATTERN, aName); 507 } 508 509 private void addRequestHeaders(){ 510 Map headerMap = new HashMap(); 511 Enumeration namesEnum = fRequest.getHeaderNames(); 512 while ( namesEnum.hasMoreElements() ) { 513 String name = (String) namesEnum.nextElement(); 514 Enumeration valuesEnum = fRequest.getHeaders(name); 515 while ( valuesEnum.hasMoreElements() ) { 516 String value = (String)valuesEnum.nextElement(); 517 headerMap.put(name, value); 518 } 519 } 520 headerMap = sortMap(headerMap); 521 addMap(headerMap, "Header"); 522 } 523 524 private void addCookies(){ 525 if (fRequest.getCookies() == null) return; 526 527 List cookies = Arrays.asList(fRequest.getCookies()); 528 Iterator cookiesIter = cookies.iterator(); 529 while ( cookiesIter.hasNext() ) { 530 Cookie cookie = (Cookie)cookiesIter.next(); 531 addLine("Cookie " + cookie.getName() + "=" + cookie.getValue()); 532 } 533 } 534 535 private String getStackTrace( Throwable aThrowable ) { 536 final Writer result = new StringWriter(); 537 final PrintWriter printWriter = new PrintWriter( result ); 538 aThrowable.printStackTrace( printWriter ); 539 return result.toString(); 540 } 541 542 private void addSessionInfo(){ 543 addStartOfSection("Session Info"); 544 545 HttpSession session = fRequest.getSession(DO_NOT_CREATE_SESSION); 546 if ( session == null ){ 547 addLine("No session existed for this request."); 548 } 549 else { 550 addLine("Logged in user name : " + getLoggedInUser()); 551 addLine("Timeout : " + session.getMaxInactiveInterval() + " seconds."); 552 Map<String, String> sessionMap = new HashMap<String, String>(); 553 Enumeration sessionAttrs = session.getAttributeNames(); 554 while (sessionAttrs.hasMoreElements()){ 555 String name = (String)sessionAttrs.nextElement(); 556 Object value = session.getAttribute(name); 557 if( isPassword(name) ){ 558 sessionMap.put(name, "***(masked)***"); 559 } 560 else { 561 sessionMap.put(name, value.toString()); 562 } 563 } 564 sessionMap = sortMap(sessionMap); 565 addMap(sessionMap, "Session Attributes"); 566 } 567 } 568 569 private String getLoggedInUser(){ 570 String result = null; 571 if (fRequest.getUserPrincipal() != null) { 572 result = fRequest.getUserPrincipal().getName(); 573 } 574 else { 575 result = "NONE"; 576 } 577 return result; 578 } 579 580 private static synchronized boolean hasEnoughTimePassedSinceLastEmail(){ 581 return (System.currentTimeMillis()-fTimeLastEmail >getMinimumIntervalBetweenEmails()); 582 } 583 584 private static synchronized void updateMostRecentTime(){ 585 fTimeLastEmail = System.currentTimeMillis(); 586 } 587 588 private void sendEmail() throws AppException { 589 Emailer emailer = BuildImpl.forEmailer(); 590 emailer.sendFromWebmaster(new Config().getTroubleTicketMailingList(), getSubject(), toString()); 591 } 592 593 /** Text to appear in all TroubleTicket emails as the "Subject" of the email. */ 594 private String getSubject(){ 595 return 596 "Servlet Error. Application : " + fAppInfo.getName() + "" + 597 "/" + fAppInfo.getVersion() 598 ; 599 } 600 601 /** 602 Convert the number of minutes configured in web.xml into milliseconds. 603 */ 604 private static long getMinimumIntervalBetweenEmails(){ 605 final long MILLISECONDS_PER_SECOND = 1000; 606 final int SECONDS_PER_MINUTE = 60; 607 Long MINIMUM_INTERVAL_BETWEEN_TICKETS = new Config().getMinimumIntervalBetweenTroubleTickets(); 608 return 609 MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * 610 MINIMUM_INTERVAL_BETWEEN_TICKETS 611 ; 612 } 613 614 private void addMap(Map aMap, String aLineHeader){ 615 Iterator iter = aMap.keySet().iterator(); 616 while (iter.hasNext()){ 617 String name = (String)iter.next(); 618 String value = (String)aMap.get(name); 619 addLine(aLineHeader + " " + name + " = " + value); 620 } 621 } 622 623 private Map sortMap(Map aInput){ 624 Map result = new TreeMap(String.CASE_INSENSITIVE_ORDER); 625 result.putAll(aInput); 626 return result; 627 } 628 }