Package hirondelle.web4j.webmaster

Source Code of hirondelle.web4j.webmaster.TroubleTicket

package hirondelle.web4j.webmaster;

import hirondelle.web4j.ApplicationInfo;
import hirondelle.web4j.BuildImpl;
import hirondelle.web4j.Controller;
import hirondelle.web4j.model.AppException;
import hirondelle.web4j.model.DateTime;
import hirondelle.web4j.readconfig.InitParam;
import hirondelle.web4j.util.Args;
import hirondelle.web4j.util.Consts;
import hirondelle.web4j.util.Util;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.regex.Pattern;

import javax.servlet.ServletConfig;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
Email diagnostic information to support staff when an error occurs.
<P>Uses the following settings in <tt>web.xml</tt> :
<ul>
<li> <tt>Webmaster</tt> - the 'from' address.
<li> <tt>TroubleTicketMailingList</tt> - the 'to' addresses for the support staff.
<li> <tt>PoorPerformanceThreshold</tt> - when the response time exceeds this level, then
a <tt>TroubleTicket</tt>
is sent
<li>  <tt>MinimumIntervalBetweenTroubleTickets</tt> - throttles down emission of
<tt>TroubleTicket</tt>s, where many might be emitted in rapid succession, from the
same underlying cause
</ul>

<P>The {@link hirondelle.web4j.Controller} will create and send a <tt>TroubleTicket</tt> when :
<ul>
<li>an unexpected problem (a bug) occurs. The bug corresponds to an unexpected
{@link Throwable} emitted by either the application or the framework.
<li>the response time exceeds the <tt>PoorPerformanceThreshold</tt> configured in
<tt>web.xml</tt>.
</ul>
<P>Example content of a <tt>TroubleTicket</tt>, as returned by {@link #toString()} :
<PRE>
{@code
Error for web application Fish And Chips/3.0.0.
java.lang.RuntimeException: Testing application behavior upon failure.
--------------------------------------------------------
Time of error : 2010-01-27 20:54:49
Occurred for user : blah
Web application Build Date: Fri Mar 16 00:00:00 ADT 2007
Web application Author : Hirondelle Systems
Web application Link : http://www.javapractices.com/apps/web4j/javadoc/summary.html
Web application Message :

Request Info:
--------------------------------------------------------
HTTP Method: GET
Context Path: /fish
ServletPath: /webmaster/testfailure/ForceFailure.do
URI: /fish/webmaster/testfailure/ForceFailure.do
URL: http://localhost:8081/fish/webmaster/testfailure/ForceFailure.do
Header accept = text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;
Header accept-charset = UTF-8,*
Header accept-language = en-us,en;q=0.5
Header connection = keep-alive
Header cookie = JSESSIONID=3969C9FD46EKLJDD0ASDKLFJAS
Header host = localhost:8081
Header keep-alive = 300
Header referer = http://localhost:8081/fish/webmaster/performance/ShowPerformance.do
Header user-agent = Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.5) Gecko/
Header x-mcproxyfilter = ************
Cookie JSESSIONID=AS3213ASD5F1AS

Client Info:
--------------------------------------------------------
User IP: 127.0.0.1
User hostname: 127.0.0.1

Session Info
--------------------------------------------------------
Logged in user name : blah
Timeout : 900 seconds.
Session Attributes javax.servlet.jsp.jstl.fmt.request.charset = ISO-8859-1
Session Attributes web4j_key_for_errors = Messages : + [] Has Been Displayed  : false
Session Attributes web4j_key_for_locale = en
Session Attributes web4j_key_for_messages = Messages : + [] Has Been Displayed  : false

Server And Servlet Info:
--------------------------------------------------------
Name: localhost
Port: 8081
Info: Apache Tomcat/5.5.23
JRE default TimeZone: America/Halifax
JRE default Locale: English (United States)
awt.toolkit: sun.awt.windows.WToolkit
catalina.base: C:\Program Files\Tomcat5\Tomcat 5.5
catalina.home: C:\Program Files\Tomcat5\Tomcat 5.5
catalina.useNaming: true
common.loader: ${catalina.home}/common/classes
file.encoding: Cp1252
file.encoding.pkg: sun.io
file.separator: \
java.awt.graphicsenv: sun.awt.Win32GraphicsEnvironment
java.awt.printerjob: sun.awt.windows.WPrinterJob
java.class.path: C:\Program Files\Tomcat5\Tomcat 5.5\bin\bootstrap.jar
java.class.version: 49.0
java.endorsed.dirs: C:\Program Files\Tomcat5\Tomcat 5.5\common\endorsed
java.ext.dirs: C:\Program Files\Java\jre1.5.0_10\lib\ext
java.home: C:\Program Files\Java\jre1.5.0_10
java.io.tmpdir: C:\Program Files\Tomcat5\Tomcat 5.5\temp
java.library.path: C:\Program Files\Tomcat5\Tomcat 5.5\bin;
java.naming.factory.initial: org.apache.naming.java.javaURLContextFactory
java.naming.factory.url.pkgs: org.apache.naming
java.runtime.name: Java(TM) 2 Runtime Environment, Standard Edition
java.runtime.version: 1.5.0_10-b03
java.specification.name: Java Platform API Specification
java.specification.vendor: Sun Microsystems Inc.
java.specification.version: 1.5
java.util.logging.config.file: C:\Program Files\Tomcat5\Tomcat 5.5\conf\logging.properties
java.util.logging.manager: org.apache.juli.ClassLoaderLogManager
java.vendor: Sun Microsystems Inc.
java.vendor.url: http://java.sun.com/
java.vendor.url.bug: http://java.sun.com/cgi-bin/bugreport.cgi
java.version: 1.5.0_10
java.vm.info: mixed mode
java.vm.name: Java HotSpot(TM) Client VM
java.vm.specification.name: Java Virtual Machine Specification
java.vm.specification.vendor: Sun Microsystems Inc.
java.vm.specification.version: 1.0
java.vm.vendor: Sun Microsystems Inc.
java.vm.version: 1.5.0_10-b03
line.separator:

os.arch: x86
os.name: Windows XP
os.version: 5.1
path.separator: ;
server.loader: ${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar
shared.loader: ${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar
sun.arch.data.model: 32
sun.boot.class.path: C:\Program Files\Java\jre1.5.0_10\lib\rt.jar;
sun.boot.library.path: C:\Program Files\Java\jre1.5.0_10\bin
sun.cpu.endian: little
sun.cpu.isalist:
sun.desktop: windows
sun.io.unicode.encoding: UnicodeLittle
sun.jnu.encoding: Cp1252
sun.management.compiler: HotSpot Client Compiler
sun.os.patch.level: Service Pack 2
tomcat.util.buf.StringCache.byte.enabled: true
user.country: US
user.dir: C:\Program Files\Tomcat5\Tomcat 5.5
user.home: C:\
user.language: en
user.name: SYSTEM
user.timezone: America/Halifax
user.variant:
java.class.path:
C:\Program Files\Tomcat5\Tomcat 5.5\bin\bootstrap.jar
Servlet : Controller
Servlet init-param:  BigDecimalDisplayFormat = #,##0.00
Servlet init-param:  CharacterEncoding = ISO-8859-1
Servlet init-param:  DecimalSeparator = PERIOD
Servlet init-param:  DefaultLocale = en
Servlet init-param:  DefaultUserTimeZone = America/Halifax
Servlet init-param:  EmailInSeparateThread = YES
Servlet init-param:  EmptyOrNullDisplayFormat = -
Servlet init-param:  ErrorCodeForDuplicateKey = 1062
Servlet init-param:  FetchSize = 25
Servlet init-param:  FloatDisplayFormat = #,###.00
Servlet init-param:  HasAutoGeneratedKeys = true
Servlet init-param:  IgnorableParamValue =
Servlet init-param:  ImplicitMappingAddSuffix = .do
Servlet init-param:  ImplicitMappingRemoveBasePackage = hirondelle.fish
Servlet init-param:  IntegerDisplayFormat = #,###
Servlet init-param:  IsSQLPrecompilationAttempted = true
Servlet init-param:  LoggingDirectory = C:\log\fish\
Servlet init-param:  LoggingLevels = hirondelle.fish.level=FINE, hirondelle.web4j.level=FINE
Servlet init-param:  MaxFileUploadRequestSize = 1048576
Servlet init-param:  MaxHttpRequestSize = 51200
Servlet init-param:  MaxRequestParamValueSize = 51200
Servlet init-param:  MaxRows = 300
Servlet init-param:  MinimumIntervalBetweenTroubleTickets = 30
Servlet init-param:  PoorPerformanceThreshold = 20
Servlet init-param:  SpamDetectionInFirewall = OFF
Servlet init-param:  SqlEditorDefaultTxIsolationLevel = DATABASE_DEFAULT
Servlet init-param:  SqlFetcherDefaultTxIsolationLevel = DATABASE_DEFAULT
Servlet init-param:  TimeZoneHint = NONE
Servlet init-param:  TroubleTicketMailingList = blah@blah.com
Servlet init-param:  Webmaster = blah@blah.com

Stack Trace:
--------------------------------------------------------
java.lang.RuntimeException: Testing application behavior upon failure.
  at hirondelle.fish.webmaster.testfailure.ForceFailure.execute(ForceFailure.java:21)
  at hirondelle.web4j.Controller.processRequest(Unknown Source)
  at hirondelle.web4j.Controller.doGet(Unknown Source)
  ...elided...
  }
</PRE>
*/
public final class TroubleTicket {

  /**
   Called by the framework upon startup, to extract config information from
   <tt>web.xml</tt>.  
  */
  public static void init(ServletConfig aConfig, ApplicationInfo aAppInfo){
    fConfig = aConfig;
    fMINIMUM_INTERVAL_BETWEEN_TICKETS = new Long(
      fMinimumIntervalBetweenTickets.fetch(aConfig).getValue()
    );
   
    String timeZoneSetting = fDefaultTimeZone.fetch(aConfig).getValue();
    fDEFAULT_TIME_ZONE = TimeZone.getTimeZone(timeZoneSetting);
   
    fAppInfo = aAppInfo;
    setTroubleTicketMailingList(aConfig);
  }

  /**
   Constructor.
  
   @param aException has caused the problem.
   @param aRequest original underlying HTTP request.
  */
  public TroubleTicket(Throwable aException, HttpServletRequest aRequest){
    fRequest = aRequest;
    fException = aException;
    buildBodyOfMessage();
  }
 
  /**
   Constuctor sets custom content for the body of the email.
  
   <P>When using this constructor, the detailed information shown in the class
   comment is not generated.
   @param aCustomBody the desired body of the email.
  */
  public TroubleTicket(String aCustomBody) {
    Args.checkForContent(aCustomBody);
    fRequest = null;
    fException = null;
    fBody.append(aCustomBody);
  }
 
  /**
   Return extensive listing of items which may be useful in solving the problem.
 
   <P>See example in the class comment.
  */
  @Override public String toString(){
    return fBody.toString();
  }

  /**
   Send an email to the <tt>TroubleTicketMailingList</tt> recipients configured in
   <tt>web.xml</tt>.
   
   <P>If sufficient time has passed since the last email of a <tt>TroubleTicket</tt>,
   then send an email to the webmaster whose body is {@link #toString}; otherwise do
   nothing.
 
   <P>Here, "sufficient time" is defined by a setting in <tt>web.xml</tt> named
   <tt>MinimumIntervalBetweenTroubleTickets</tt>. The intent is to throttle down on
   emails which likely have the same cause.
  */
  public void mailToWebmaster() throws AppException {
    if ( hasEnoughTimePassedSinceLastEmail() ) {
      sendEmail();
      updateMostRecentTime();
    }
  }
 
  // PRIVATE //
  private static ServletConfig fConfig;
  private static ApplicationInfo fAppInfo; 
  private final HttpServletRequest fRequest;
  private final Throwable fException;
  private static final boolean DO_NOT_CREATE = false;
  private static final Pattern PASSWORD_PATTERN = Pattern.compile(
    "password", Pattern.CASE_INSENSITIVE
  );
 
  /**
   The text which contains all relevant information which may be useful in solving
   the problem.
  */
  private StringBuilder fBody = new StringBuilder();
 
  /**
   The time of the last send of a TroubleTicket email, expressed in
   milliseconds since the Java epoch.
 
   This static data is shared among requests, and all access to this
   field must be synchronized.
  */
  private static long fTimeLastEmail;
 
  /** Item configured in web.xml.  */
  private static InitParam fMinimumIntervalBetweenTickets = new InitParam(
    "MinimumIntervalBetweenTroubleTickets", "30"
  );
 
  /** Minimum number of minutes between Trouble Tickets.   */
  private static Long fMINIMUM_INTERVAL_BETWEEN_TICKETS;
 
  /** The DefaultUserTimeZone setting in web.xml. Defaults to GMT. */
  private static final InitParam fDefaultTimeZone = new InitParam("DefaultUserTimeZone", "GMT");
  private static TimeZone fDEFAULT_TIME_ZONE;
 
  /** Item configured in web.xml.  */
  private static InitParam fTroubleTicketMailingList = new InitParam(
    "TroubleTicketMailingList", "NONE"
  );
 
  /**
   List or email addresses, for all receivers of TroubleTickets.
   If empty, then not sent at all. 
  */
  private static List<String> fTROUBLE_TICKET_MAILING_LIST = new ArrayList<String>();
 
  private static void setTroubleTicketMailingList(ServletConfig aConfig){
    String rawList = fTroubleTicketMailingList.fetch(aConfig).getValue();
    StringTokenizer parser = new StringTokenizer(rawList, ",");
    while (parser.hasMoreElements()){
      String emailAddr = (String)parser.nextElement();
      fTROUBLE_TICKET_MAILING_LIST.add(emailAddr);
    }
  }

  /** Build fBody from its various parts.  */
  private void buildBodyOfMessage() {
    addExceptionSummary();
    addRequestInfo();
    addClientInfo();
    addSessionInfo();
    addServerInfo();
    addStackTrace();
  }
 
  private void addLine(String aLine){
    fBody.append(aLine + Consts.NEW_LINE);
  }
 
  private void addStartOfSection(String aHeader){
    addLine(Consts.EMPTY_STRING);
    addLine(aHeader);
    addLine("--------------------------------------------------------");
  }
 
  private void addExceptionSummary(){
    addStartOfSection(
      "Error for web application " + fAppInfo.getName() + "/" + fAppInfo.getVersion() +
      "." + Consts.NEW_LINE + "*** "  + fException.toString() + " ***"
    );
    long now = BuildImpl.forTimeSource().currentTimeMillis();
    addLine("Time of error : " + DateTime.now(fDEFAULT_TIME_ZONE).format("YYYY-MM-DD hh:mm:ss"));
    addLine("Occurred for user : " + getLoggedInUser() );
    addLine("Web application Build Date: " + fAppInfo.getBuildDate());
    addLine("Web application Author : " + fAppInfo.getAuthor());
    addLine("Web application Link : " + fAppInfo.getLink());
    addLine("Web application Message : " + fAppInfo.getMessage());
    if ( fException instanceof AppException ) {
      AppException appEx = (AppException)fException;
      Iterator errorsIter = appEx.getMessages().iterator();
      while ( errorsIter.hasNext() ) {
        addLine( errorsIter.next().toString() );
      }
    }
  }
 
  private void addRequestInfo(){
    addStartOfSection("Request Info:");
    addLine("HTTP Method: " + fRequest.getMethod());
    addLine("Context Path: " + fRequest.getContextPath());
    addLine("ServletPath: " + fRequest.getServletPath());
    addLine("URI: " + fRequest.getRequestURI());
    addLine("URL: " + fRequest.getRequestURL().toString());
    addRequestParams();
    addRequestHeaders();
    addCookies();
  }
 
  private void addClientInfo(){
    addStartOfSection("Client Info:");
    addLine("User IP: " + fRequest.getRemoteAddr());
    addLine("User hostname: " + fRequest.getRemoteHost());
  }
 
  private void addServerInfo(){
    addStartOfSection("Server And Servlet Info:");
    addLine("Name: " + fRequest.getServerName());
    addLine("Port: " + fRequest.getServerPort());
    addLine("Info: " + fConfig.getServletContext().getServerInfo());
    addLine("JRE default TimeZone: " + TimeZone.getDefault().getID());
    addLine("JRE default Locale: " + Locale.getDefault().getDisplayName());
   
    addAllSystemProperties();
    addClassPath();
    addLine("Servlet : " + fConfig.getServletName());
   
    Map<String, Object> servletParams = new HashMap<String, Object>();
    Enumeration paramNames = fConfig.getInitParameterNames();
    while (paramNames.hasMoreElements()){
      String name = (String)paramNames.nextElement();
      String value = fConfig.getInitParameter(name);
      servletParams.put(name, value);
    }
    servletParams = sortMap(servletParams);
    addMap(servletParams, "Servlet init-param: ");
  }
 
  private void addAllSystemProperties(){
    Map properties = sortMap(System.getProperties());
    Set props = properties.entrySet();
    Iterator iter = props.iterator();
    while ( iter.hasNext() ) {
      Map.Entry entry = (Map.Entry)iter.next();
      addLine(entry.getKey() + ": " + entry.getValue());
    }
  }

  /**
   Since this item tends to be very long, it is useful to place each entry
   on a separate line.
  */
  private void addClassPath(){
    String JAVA_CLASS_PATH = "java.class.path";
    String classPath = System.getProperty(JAVA_CLASS_PATH);
    List pathElements = Arrays.asList( classPath.split(Consts.PATH_SEPARATOR) );
    StringBuilder result = new StringBuilder(Consts.NEW_LINE);
    Iterator pathElementsIter = pathElements.iterator();
    while ( pathElementsIter.hasNext() ) {
      String pathElement = (String)pathElementsIter.next();
      result.append(pathElement);
      if ( pathElementsIter.hasNext() ) {
        result.append(Consts.NEW_LINE);
      }
    }
    addLine(JAVA_CLASS_PATH + ": " + result.toString());
  }
 
  private void addStackTrace(){
    addStartOfSection("Stack Trace:");
    addLine( getStackTrace(fException) );
  }
 
  private void  addRequestParams(){
    Map paramMap = new HashMap();
    Enumeration namesEnum = fRequest.getParameterNames();
    while ( namesEnum.hasMoreElements() ){
      String name = (String)namesEnum.nextElement();
      String values = Util.getArrayAsString( fRequest.getParameterValues(name) );
      if( isPassword(name)){
        paramMap.put(name, "***(masked)***");
      }
      else {
        paramMap.put(name, values);
      }
    }
    paramMap = sortMap(paramMap);
    addMap(paramMap, "Req Param");
  }
 
  private boolean isPassword(String aName){
    return Util.contains(PASSWORD_PATTERN, aName);
  }
 
  private void addRequestHeaders(){
    Map headerMap = new HashMap();
    Enumeration namesEnum = fRequest.getHeaderNames();
    while ( namesEnum.hasMoreElements() ) {
      String name = (String) namesEnum.nextElement();
      Enumeration valuesEnum = fRequest.getHeaders(name);
      while ( valuesEnum.hasMoreElements() ) {
        String value = (String)valuesEnum.nextElement();
        headerMap.put(name, value);
      }
    }
    headerMap = sortMap(headerMap);
    addMap(headerMap, "Header");
  }
 
  private void addCookies(){
    if (fRequest.getCookies() == null) return;
   
    List cookies = Arrays.asList(fRequest.getCookies());
    Iterator cookiesIter = cookies.iterator();
    while ( cookiesIter.hasNext() ) {
      Cookie cookie = (Cookie)cookiesIter.next();
      addLine("Cookie " + cookie.getName() + "=" + cookie.getValue());
    }
  }
 
  private String getStackTrace( Throwable aThrowable ) {
    final Writer result = new StringWriter();
    final PrintWriter printWriter = new PrintWriter( result );
    aThrowable.printStackTrace( printWriter );
    return result.toString();
 
 
  private void addSessionInfo(){
    addStartOfSection("Session Info");
   
    HttpSession session = fRequest.getSession(DO_NOT_CREATE);
    if ( session == null ){
      addLine("No session existed for this request.");
    }
    else {
      addLine("Logged in user name : " + getLoggedInUser());
      addLine("Timeout : " + session.getMaxInactiveInterval() + " seconds.");
      Map<String, String> sessionMap = new HashMap<String, String>();
      Enumeration sessionAttrs = session.getAttributeNames();
      while (sessionAttrs.hasMoreElements()){
        String name = (String)sessionAttrs.nextElement();
        Object value = session.getAttribute(name);
        if( isPassword(name) ){
          sessionMap.put(name, "***(masked)***");
        }
        else {
          sessionMap.put(name, value.toString());
        }
      }
      sessionMap = sortMap(sessionMap);
      addMap(sessionMap, "Session Attributes");
    }
  }
 
  private String getLoggedInUser(){
    String result = null;
    if (fRequest.getUserPrincipal() != null) {
      result = fRequest.getUserPrincipal().getName();
    }
    else {
      result = "NONE";
    }
    return result;
  }
 
  private static synchronized boolean hasEnoughTimePassedSinceLastEmail(){
    return (System.currentTimeMillis()-fTimeLastEmail >getMinimumIntervalBetweenEmails());
  }
 
  private void sendEmail() throws AppException {
    Emailer emailer = BuildImpl.forEmailer();
    emailer.sendFromWebmaster(fTROUBLE_TICKET_MAILING_LIST, getSubject(), toString());
  }
 
  /**
   Text to appear in all TroubleTicket emails as the "Subject" of the email.
  */
  private String getSubject(){
    return
      "Servlet Error. Application : " + fAppInfo.getName() + "" +
      "/" + fAppInfo.getVersion()
    ;
  }
     
  private static synchronized void updateMostRecentTime(){
    fTimeLastEmail = System.currentTimeMillis();
  }

  /**
   Convert the number of minutes configured in web.xml into milliseconds.
  */
  private static long getMinimumIntervalBetweenEmails(){
    final long MILLISECONDS_PER_SECOND = 1000;
    final int SECONDS_PER_MINUTE = 60;
    return
      MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE *
      fMINIMUM_INTERVAL_BETWEEN_TICKETS.longValue()
    ;
  }
 
  private void addMap(Map aMap, String aLineHeader){
    Iterator iter = aMap.keySet().iterator();
    while (iter.hasNext()){
      String name = (String)iter.next();
      String value = (String)aMap.get(name);
      addLine(aLineHeader + " " + name + " = " + value);
    }
  }
 
  private Map sortMap(Map aInput){
    Map result = new TreeMap(String.CASE_INSENSITIVE_ORDER);
    result.putAll(aInput);
    return result;
  }
}
TOP

Related Classes of hirondelle.web4j.webmaster.TroubleTicket

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.