001    package hirondelle.web4jtools.webmaster.diagnostics; 
002    
003    import hirondelle.web4j.Controller;
004    import hirondelle.web4j.action.ActionImpl;
005    import hirondelle.web4j.action.ResponsePage;
006    import hirondelle.web4j.database.DAOException;
007    import hirondelle.web4j.model.AppException;
008    import hirondelle.web4j.request.RequestParser;
009    import hirondelle.web4j.util.Stopwatch;
010    import hirondelle.web4j.util.Util;
011    
012    import java.io.IOException;
013    import java.util.Arrays;
014    import java.util.Enumeration;
015    import java.util.HashMap;
016    import java.util.Iterator;
017    import java.util.LinkedHashMap;
018    import java.util.List;
019    import java.util.Map;
020    import java.util.Set;
021    import java.util.TreeMap;
022    import java.util.jar.Attributes;
023    import java.util.jar.JarInputStream;
024    import java.util.jar.Manifest;
025    import java.util.logging.Level;
026    import java.util.logging.LogManager;
027    import java.util.logging.Logger;
028    
029    import javax.servlet.ServletContext;
030    import javax.servlet.http.Cookie;
031    import javax.servlet.http.HttpServletRequest;
032    import javax.servlet.http.HttpServletResponse;
033    
034    /**
035    * Show an extensive listing of diagnostic information, useful for solving problems.
036    */
037    public final class ShowDiagnostics extends ActionImpl {
038      
039      /** Constructor. */
040      public ShowDiagnostics(RequestParser aRequestParser){
041        super(FORWARD, aRequestParser);
042      }
043      
044      /** 
045      * Retrieve diagnostic information and display it to the user.
046      * 
047      * <P>The diagnostic information includes :
048      * <ul>
049      * <li>up time
050      * <li>logging configuration
051      * <li>jar versions
052      * <li>system properties
053      * <li>context init params
054      * <li>server information
055      * <li>application scope items
056      * <li>session scope items
057      * <li>request information
058      * <li>request headers
059      * <li>request cookies
060      * <li>response encoding
061      * </ul> 
062      */
063      public ResponsePage execute() throws AppException {
064        placeDiagnosticDataInScope(getRequestParser().getRequest(), getRequestParser().getResponse());
065        return getResponsePage();
066      }
067      
068      // PRIVATE //
069      private static final ResponsePage FORWARD = new ResponsePage(
070        "Diagnostics", "view.jsp", ShowDiagnostics.class
071      );
072      private static final String SPECIFICATION_TITLE = "Specification-Title";
073      private static final String SPECIFICATION_VERSION = "Specification-Version";
074      private static final String UNSPECIFIED = "Unspecified in Manifest";
075      private static final long MILLISECONDS_PER_DAY = 1000*60*60*24L;
076      private static final Logger fLogger = Util.getLogger(ShowDiagnostics.class);
077      
078      private void placeDiagnosticDataInScope(HttpServletRequest aRequest, HttpServletResponse aResponse) throws DAOException {
079        Stopwatch stopwatch = new Stopwatch();
080        stopwatch.start();
081        
082        fLogger.fine("Adding system properties.");
083        addToRequest("systemProperties", sortMap(System.getProperties()));
084        fLogger.fine("Adding Context init-params.");
085        addToRequest("contextInitParams", sortMap(getContextInitParams(aRequest)));
086        fLogger.fine("Adding application scope items.");
087        addToRequest("appScopeItems", getAppScope(aRequest));
088        fLogger.fine("Adding session scope items.");
089        addToRequest("sessionScopeItems", getSessionScope(aRequest));
090        fLogger.fine("Adding container/servlet info.");
091        addToRequest("containerServletInfo", getContainerServletInfo(aRequest));
092        fLogger.fine("Adding request info.");
093        addToRequest("requestInfo", getRequestInfo(aRequest));
094        fLogger.fine("Adding loggers.");
095        addToRequest("loggers", getLoggers());
096        fLogger.fine("Adding JAR versions.");
097        addToRequest("jarVersions", getJarVersions(aRequest));
098        fLogger.fine("Adding uptime.");
099        addToRequest("uptime", getUptime(aRequest));
100        fLogger.fine("Adding request headers.");
101        addToRequest("headers", getHeaders(aRequest));
102        addToRequest("responseEncoding", getResponseEncoding(aResponse));
103        fLogger.fine("Adding cookies.");
104        addToRequest("cookies", getCookies(aRequest));
105        
106        fLogger.fine("Finished retrieving data.");
107        stopwatch.stop();
108        addToRequest("stopwatch", stopwatch.toString());
109      }
110      
111      private ServletContext getContext(HttpServletRequest aRequest){
112        return aRequest.getSession().getServletContext();
113      }
114      
115      private Map sortMap(Map aInput){
116        Map result = new TreeMap(String.CASE_INSENSITIVE_ORDER);
117        result.putAll(aInput);
118        return result;
119      }
120      
121      private Map<Object, Object> getAppScope(HttpServletRequest aRequest){
122        Map<Object, Object> result = new HashMap<Object, Object>();
123        Enumeration keys = getContext(aRequest).getAttributeNames();
124        while ( keys.hasMoreElements() ) {
125          Object key = keys.nextElement();
126          Object value = getContext(aRequest).getAttribute(key.toString());
127          result.put(key, value);
128        }
129        return sortMap(result);
130      }
131      
132      
133      private Map<Object, Object> getSessionScope(HttpServletRequest aRequest){
134        Map<Object, Object> result = new HashMap<Object, Object>();
135        Enumeration keys = aRequest.getSession(true).getAttributeNames();
136        while ( keys.hasMoreElements() ) {
137          Object key = keys.nextElement();
138          Object value = aRequest.getSession(true).getAttribute(key.toString());
139          result.put(key, value);
140        }
141        return sortMap(result);
142      }
143      
144      private Map<String, String> getContainerServletInfo(HttpServletRequest aRequest){
145        Map<String, String> result = new LinkedHashMap<String, String>();
146        ServletContext context = getContext(aRequest);
147        result.put("Host Name", aRequest.getServerName());
148        result.put("Operating System", System.getProperty("os.arch") + " " +  System.getProperty("os.name") + " " + System.getProperty("os.version"));
149        result.put("Container", context.getServerInfo());
150        result.put("Controller Name", context.getClass().getName());
151        result.put("Home URL", getHome(aRequest));
152        return result;
153      }
154      
155      private Map<String, Object> getRequestInfo(HttpServletRequest aRequest){
156        Map<String, Object> result = new HashMap<String, Object>();
157        result.put("Server Port", new Integer(aRequest.getServerPort()));
158        result.put("Client IP", aRequest.getRemoteAddr());
159        result.put("Character Encoding", aRequest.getCharacterEncoding());
160        result.put("Protocol", aRequest.getProtocol());
161        result.put("Server Name", aRequest.getServerName());
162        result.put("Content Length", new Integer(aRequest.getContentLength()));
163        return sortMap(result);
164      }
165    
166      private String getHome(HttpServletRequest aRequest) {
167        String url =  aRequest.getRequestURL().toString();
168        String contextPath = aRequest.getContextPath();
169        fLogger.fine( url );
170        fLogger.fine( contextPath );
171        int endIndex = url.indexOf(contextPath) + contextPath.length();
172        return url.substring(0, endIndex);
173      }
174      
175      private Map<String, Level> getLoggers() {
176        Map<String, Level> result = new HashMap<String, Level>();
177        LogManager logManager = LogManager.getLogManager();
178        Enumeration loggerNames = logManager.getLoggerNames();
179        while ( loggerNames.hasMoreElements() ){
180          String loggerName = (String)loggerNames.nextElement();
181          Logger logger = logManager.getLogger(loggerName);
182          result.put(loggerName, logger.getLevel());
183        }
184        return sortMap(result);
185      }
186      
187      /**
188      * Get all .jar files in <tt>/WEB-INF/lib/</tt> directory, scan the manifest for its 
189      * <tt>Specification-Version</tt>, and report it along with the file name. 
190      * 
191      * <P>Some jars will not have a value for <tt>Specification-Version</tt>. These will be 
192      * reported as {@link #UNSPECIFIED}.
193      */  
194      private Map<String, String> getJarVersions(HttpServletRequest aRequest){
195        Map<String, String> result = new HashMap<String, String>();
196        ServletContext context = getContext(aRequest); 
197        Set paths = context.getResourcePaths("/WEB-INF/lib/");
198        fLogger.fine(paths.toString());
199        Iterator iter = paths.iterator();
200        while ( iter.hasNext() ) {
201          String jarFileName = iter.next().toString();
202          JarInputStream jarStream = null;
203          try {
204            jarStream = new JarInputStream(context.getResourceAsStream(jarFileName));
205          }
206          catch (IOException ex){
207            fLogger.severe("Cannot open jar file (to fetch Specification-Version from the jar Manifest).");
208          }
209          result.put(jarFileName, fetchSpecNamesAndVersions(jarStream));
210        }
211        return sortMap(result);
212      }
213    
214      /** 
215      * A Jar can have more than one entry for spec name and version. 
216      * For example, the servlet jar implements both servlet spec and jsp spec.
217      * 
218      * <P>Search for all attributes named Specification-Title and Specification-Version, 
219      * and simply return them in the order they appear.
220      * 
221      * <P>If none of these appear, simply return an empty String.
222      */
223      private String fetchSpecNamesAndVersions(JarInputStream aJarStream) {
224        StringBuffer result = new StringBuffer();
225        Manifest manifest = aJarStream.getManifest();
226        if ( manifest != null ){
227          Attributes attrs = (Attributes)manifest.getMainAttributes();
228          for (Iterator iter = attrs.keySet().iterator(); iter.hasNext(); ) {
229            Attributes.Name attrName = (Attributes.Name)iter.next();
230            addSpecAttrIfPresent(SPECIFICATION_TITLE, result, attrs, attrName);
231            addSpecAttrIfPresent(SPECIFICATION_VERSION, result, attrs, attrName);
232          }
233          fLogger.fine("Specification-Version: " + result);
234        }
235        else {
236          fLogger.fine("No manifest.");
237        }
238        return result.toString();
239      }
240    
241      private void addSpecAttrIfPresent(String aName, StringBuffer aResult, Attributes aAttrs, Attributes.Name aAttrName) {
242        if ( aName.equalsIgnoreCase(aAttrName.toString()) ) {
243          if ( Util.textHasContent(aAttrs.getValue(aAttrName)) ){
244            aResult.append(aAttrs.getValue(aAttrName) + " ");
245          }
246        }
247      }
248      
249      private Float getUptime(HttpServletRequest aRequest){
250        java.util.Date now = new java.util.Date();
251        java.util.Date startup = (java.util.Date)aRequest.getSession(true).getServletContext().getAttribute(Controller.START_TIME);
252        float msecs = now.getTime() - startup.getTime();
253        return new Float(msecs/MILLISECONDS_PER_DAY);
254      }
255      
256      private Map<String, String> getHeaders(HttpServletRequest aRequest){
257        Map<String, String> result = new HashMap<String, String>();
258        Enumeration items = aRequest.getHeaderNames();
259        while( items.hasMoreElements() ){
260          String name = (String)items.nextElement();
261          String value = aRequest.getHeader(name);
262          result.put(name, value);
263        }
264        return sortMap(result);
265      }
266    
267      private Map<String, String> getResponseEncoding(HttpServletResponse aResponse){
268        Map<String, String> result = new HashMap<String, String>();
269        result.put("Character Encoding", aResponse.getCharacterEncoding());
270        return sortMap(result);
271      }
272      
273      private Map<String, String> getCookies(HttpServletRequest aRequest){
274        Map<String, String> result = new HashMap<String, String>();
275        Cookie[] cookies = aRequest.getCookies();
276        if ( cookies != null ){
277          List cookieList = Arrays.asList(cookies);
278          Iterator iter = cookieList.iterator();
279          while ( iter.hasNext() ) {
280            Cookie cookie = (Cookie)iter.next();
281            result.put(cookie.getName(), "Value:" + cookie.getValue() + ", Comment:" + cookie.getComment() + ", Domain:" + cookie.getDomain() + ", Max-Age:" + cookie.getMaxAge() + ", Path:" + cookie.getPath() + ", Spec- Version:" + cookie.getVersion());
282          }
283          result = sortMap(result);
284        }
285        return result;
286      }
287      
288      private Map<String, Object> getContextInitParams(HttpServletRequest aRequest){
289        Map<String, Object> result = new HashMap<String, Object>();
290        Enumeration initParams = aRequest.getSession().getServletContext().getInitParameterNames();
291        while(initParams.hasMoreElements()) {
292          String key = (String)initParams.nextElement();
293          Object value = aRequest.getSession().getServletContext().getInitParameter(key);
294          result.put(key, value);
295        }
296        return result;
297      }
298    }