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 }