001package org.javasimon.javaee; 002 003import javax.servlet.http.HttpServletRequest; 004 005import org.javasimon.Manager; 006import org.javasimon.Stopwatch; 007import org.javasimon.source.AbstractStopwatchSource; 008import org.javasimon.source.CachedStopwatchSource; 009import org.javasimon.source.StopwatchSource; 010import org.javasimon.utils.Replacer; 011 012/** 013 * Provide stopwatch source for HTTP Servlet request. 014 * Used by {@link SimonServletFilter} as default stopwatch source. 015 * Can be overridden to customize monitored HTTP Requests and their 016 * related Simon name. 017 * <p/> 018 * To select which HTTP Request should be monitored method {@link #isMonitored} can be overridden. Default implementation monitors everything except for 019 * typical resource-like requests (images, JS/CSS, ...). 020 * 021 * @author gquintana 022 * @author <a href="mailto:virgo47@gmail.com">Richard "Virgo" Richter</a> 023 */ 024public class HttpStopwatchSource extends AbstractStopwatchSource<HttpServletRequest> { 025 026 /** 027 * Enum that represents modes of preserving HTTP methods names in Simons' names 028 */ 029 public static enum IncludeHttpMethodName { 030 // Always append name of HTTP method to simon name 031 ALWAYS, 032 // Never append name of HTTP method to simon name 033 NEVER, 034 // Append name of all HTTP methods except GET method 035 NON_GET 036 } 037 038 /** 039 * Default prefix for web filter Simons if no "prefix" init parameter is used. 040 */ 041 public static final String DEFAULT_SIMON_PREFIX = "org.javasimon.web"; 042 043 /** 044 * Name of HTTP GET method. 045 */ 046 private static final String GET_METHOD = "GET"; 047 048 /** 049 * Simon prefix, can be set to {@code null}. 050 */ 051 private String prefix = DEFAULT_SIMON_PREFIX; 052 053 private IncludeHttpMethodName includeHttpMethodName = IncludeHttpMethodName.NEVER; 054 055 private Replacer unallowedCharacterReplacer = SimonServletFilterUtils.createUnallowedCharsReplacer("_"); 056 private Replacer jsessionParameterReplacer = new Replacer("[;&]?JSESSIONID=[^;?/&]*", "", Replacer.Modificator.IGNORE_CASE); 057 private Replacer trailingStuffReplacer = new Replacer("/[^a-zA-Z]*$", ""); 058 059 public HttpStopwatchSource(Manager manager) { 060 super(manager); 061 } 062 063 public String getPrefix() { 064 return prefix; 065 } 066 067 public void setPrefix(String prefix) { 068 this.prefix = prefix; 069 } 070 071 public String getReplaceUnallowed() { 072 return unallowedCharacterReplacer.getTo(); 073 } 074 075 public void setReplaceUnallowed(String replaceUnallowed) { 076 unallowedCharacterReplacer.setTo(replaceUnallowed); 077 } 078 079 /** 080 * Returns current mode of preserving HTTP method names in simons' names 081 * 082 * @return current mode of preserving HTTP method names 083 */ 084 public IncludeHttpMethodName getIncludeHttpMethodName() { 085 return includeHttpMethodName; 086 } 087 088 /** 089 * Set current mode of preserving HTTP method names in simons' names 090 * 091 * @param includeHttpMethodName current mode of preserving HTTP method names 092 */ 093 public void setIncludeHttpMethodName(IncludeHttpMethodName includeHttpMethodName) { 094 this.includeHttpMethodName = includeHttpMethodName; 095 } 096 097 /** 098 * Returns Simon name for the specified HTTP request with the specified prefix. By default it contains URI without parameters with 099 * all slashes replaced for dots (slashes then determines position in Simon hierarchy). Method can NOT be overridden, but some of the 100 * following steps can: 101 * <ol> 102 * <li>the request is transformed to the string ({@link #requestToStringForMonitorName(javax.servlet.http.HttpServletRequest)}, can be overridden),</li> 103 * <li>the characters that are not allowed as part of the Simon name are replaced with underscore (_) - replacement regex can be changed with {@link #setReplaceUnallowed(String)},</li> 104 * <li>any subsequent slashes and dots are replaced with a single dot ({@link org.javasimon.Manager#HIERARCHY_DELIMITER})</li> 105 * </ol> 106 * 107 * @param request HTTP request 108 * @return fully qualified name of the Simon 109 * @see #requestToStringForMonitorName(javax.servlet.http.HttpServletRequest) 110 */ 111 protected String getMonitorName(HttpServletRequest request) { 112 String uri = requestToStringForMonitorName(request); 113 String localName = SimonServletFilterUtils.getSimonName(uri, unallowedCharacterReplacer); 114 String monitorName; 115 if (prefix == null || prefix.isEmpty()) { 116 monitorName = localName; 117 } else { 118 monitorName = prefix + Manager.HIERARCHY_DELIMITER + localName; 119 } 120 121 if (includeMethodName(request)) { 122 monitorName += Manager.HIERARCHY_DELIMITER + request.getMethod(); 123 } 124 125 return monitorName; 126 } 127 128 private boolean includeMethodName(HttpServletRequest request) { 129 return includeHttpMethodName == IncludeHttpMethodName.ALWAYS || 130 (includeHttpMethodName == IncludeHttpMethodName.NON_GET && !request.getMethod().equals(GET_METHOD)); 131 } 132 133 /** 134 * Performs the first step in getting the monitor name from the specified HTTP request - here any custom ignore logic should happen. 135 * By default the name is URI (without parameters - see {@link javax.servlet.http.HttpServletRequest#getRequestURI()}) with JSessionID 136 * removed (see {@link #removeJSessionIdFromUri(String)}) and any trailing stuff removed (see {@link #removeTrailingStuff(String)}). 137 * This method can be overridden for two typical reasons: 138 * <ul> 139 * <li>Name of the monitor (Stopwatch) should be based on something else then URI,</li> 140 * <li>there are other parts of the name that should be modified or ignored (e.g., REST parameters that are part of the URI).</li> 141 * </ul> 142 * 143 * @param request HTTP request 144 * @return preprocessed URI that will be converted to the Simon name 145 * @see #getMonitorName(javax.servlet.http.HttpServletRequest) 146 * @see #removeJSessionIdFromUri(String) 147 */ 148 protected String requestToStringForMonitorName(HttpServletRequest request) { 149 String uri = request.getRequestURI(); 150 uri = removeJSessionIdFromUri(uri); 151 uri = removeTrailingStuff(uri); 152 return uri; 153 } 154 155 /** 156 * Removes JSESSIONID parameter from URI. By default it is not necessary to handle parameters, as incoming URI already is without 157 * parameters, but JSESSIONID sometimes come before parameters in other forms and this method tries to remove such forms. 158 * <p/> 159 * Called by default implementation of {@link #requestToStringForMonitorName(javax.servlet.http.HttpServletRequest)} and extracted 160 * so it can be used by any overriding implementation of the same method. Method can be overridden if the default behavior is not 161 * sufficient. 162 * 163 * @param uri preprocessed URI that may contain JSessionID 164 * @return preprocessed URI without JSessionID 165 * @see #requestToStringForMonitorName(javax.servlet.http.HttpServletRequest) 166 */ 167 protected String removeJSessionIdFromUri(String uri) { 168 return jsessionParameterReplacer.process(uri); 169 } 170 171 /** 172 * Removes any trailing slashes followed by other characters if none of them is alphabetic. This should take care of some REST 173 * parameters (numeric id-s) and it also removes trailing slashes to avoid empty local Simon names which is forbidden. 174 * <p/> 175 * Called by default implementation of {@link #requestToStringForMonitorName(javax.servlet.http.HttpServletRequest)} and extracted 176 * so it can be used by any overriding implementation of the same method. Method can be overridden if the default behavior is not 177 * sufficient. 178 * 179 * @param uri preprocessed URI that may contain JSessionID 180 * @return preprocessed URI without JSessionID 181 * @see #requestToStringForMonitorName(javax.servlet.http.HttpServletRequest) 182 */ 183 protected String removeTrailingStuff(String uri) { 184 return trailingStuffReplacer.process(uri); 185 } 186 187 /** 188 * Indicates whether the HTTP Request should be monitored - method is intended for override. 189 * Default behavior ignores URIs ending with .css, .png, .gif, .jpg and .js (ignores casing). 190 * 191 * @param httpServletRequest HTTP Request 192 * @return true to enable request monitoring, false either 193 */ 194 @Override 195 public boolean isMonitored(HttpServletRequest httpServletRequest) { 196 String uri = httpServletRequest.getRequestURI().toLowerCase(); 197 return !(uri.endsWith(".css") || uri.endsWith(".png") || uri.endsWith(".gif") || uri.endsWith(".jpg") || uri.endsWith(".js")); 198 } 199 200 /** 201 * Get a stopwatch for given HTTP request. 202 * 203 * @param request Method HTTP request 204 * @return Stopwatch for the HTTP request 205 */ 206 @Override 207 public Stopwatch getMonitor(HttpServletRequest request) { 208 final Stopwatch stopwatch = super.getMonitor(request); 209 if (stopwatch.getNote() == null) { 210 stopwatch.setNote(request.getRequestURI()); 211 } 212 return stopwatch; 213 } 214 215 /** 216 * Wraps given stop watch source in a cache. 217 * 218 * @param stopwatchSource Stopwatch source 219 * @return Cached stopwatch source 220 */ 221 public static StopwatchSource<HttpServletRequest> newCacheStopwatchSource(StopwatchSource<HttpServletRequest> stopwatchSource) { 222 return new CachedStopwatchSource<HttpServletRequest, String>(stopwatchSource) { 223 @Override 224 protected String getLocationKey(HttpServletRequest location) { 225 return location.getRequestURI(); 226 } 227 }; 228 } 229}