001 package org.javasimon.javaee;
002
003 import javax.servlet.http.HttpServletRequest;
004
005 import org.javasimon.Manager;
006 import org.javasimon.Stopwatch;
007 import org.javasimon.source.AbstractStopwatchSource;
008 import org.javasimon.source.CachedStopwatchSource;
009 import org.javasimon.source.StopwatchSource;
010 import 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 */
024 public 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 }