001: package com.jamonapi.http;
002:
003: import java.lang.reflect.Method;
004:
005: import com.jamonapi.MonKeyImp;
006: import com.jamonapi.MonKey;
007: import com.jamonapi.MonitorFactory;
008: import com.jamonapi.Monitor;
009: import com.jamonapi.NullMonitor;
010: import com.jamonapi.utils.Misc;
011:
012: /** Stateless class used by HttpMonRequest to represent anything a request is monitoring.
013: * There will be one HttpMonItem in HttpMonFactory for each thing we are monitoring. The state information
014: * needed for monitoring a particular request is kept in HttpMonRequest. Note any of the mehtods that take
015: * a HttpMonRequest need some form of state associated with the request. By making HttpMonRequest stateful
016: * and HttpMonItem stateless a significant number of object creations were saved.
017: *
018: * @author Steven Souza
019: *
020: */
021:
022: class HttpMonItem {
023:
024: private boolean isTimeMon;//used in JettyHttpMonItem
025: private String units = "noUnitsProvided";// units used by jamon
026: private String label;// label used as a base by jamon
027: private String methodName;// method name that monitoring calls. getStatus, getRequestURI etc.
028: private String additionToLabel = "";// u=url, v=value. possibilities are empty, u,v,uv,vu
029: private boolean isResponse = true;// Are we monitoring an HttpServletRequest or an HttpServletResponse
030: private Method method;
031: // containers put jessionid (and other params) as part of what is returned by getRequestURI, and getRequestURL. This can make
032: // many pages not unique enough to benefit from jamon, so by default this part of the url is removed from the monitoring label
033: // i.e. /myapp/mypage.jsp;jsessionid=320sljsdofou
034: // becomes the following to jamon
035: // /myapp/mypage.jsp
036: private boolean removeHttpParams = false;// remove httpParams of request from jamon label.
037: private static final Monitor NULL_MON = new NullMonitor();// used when disabled
038:
039: HttpMonItem() {
040: }
041:
042: /** Valid constructor arguments are case insensitive, and have to start with request, response to differentiate whether to use an object
043: * that inhertis from HttpServletRequest or HttpServletResponse respectively. This must be follwed by the methodname, units, an optional '.value',
044: * and a units. The method name will be executed. Note a unit of 'ms' would cause a timed monitor to be called.
045: *
046: * Examples: request.getRequestURI().ms, request.getRequestURI().ms.value, request.getRequestURI().myUnits
047: * response.getBufferSize().bytes, response.getBufferSize().bytes.value
048: *
049: * @param label
050: */
051: HttpMonItem(String label, HttpMonFactory httpMonFactory) {
052: parseLabel(label, httpMonFactory);
053:
054: }
055:
056: // parse passed in string and build HttpMonItems from it to be used to monitor
057: // The request. Note general form is request or response followed by a method name and units. Special defined tokes are value, contextpath, url and units
058: private void parseLabel(String localLabel,
059: HttpMonFactory httpMonFactory) {
060: String colAlias = colAlias(localLabel); // brings back anything after 'as', if it exists, null otherwise: request.getMethod().bytes as ColName
061: String nonAlias = nonAlias(localLabel); // brings back what is before 'as alias'. i.e. request.getMethod().bytes
062:
063: String[] parsedLabel = nonAlias.split("[.]");
064:
065: label = httpMonFactory.getLabelPrefix() + ".response.";// default label
066:
067: for (int i = 0; i < parsedLabel.length; i++) {
068:
069: String token = parsedLabel[i].trim();
070:
071: if ("request".equalsIgnoreCase(token)) { // request.
072: label = httpMonFactory.getLabelPrefix() + ".request.";
073: isResponse = false;
074: } else if ("response".equalsIgnoreCase(token)) { // response.
075: label = httpMonFactory.getLabelPrefix() + ".response.";
076: isResponse = true;
077: } else if (token.indexOf("()") != -1) { // response.methodName()
078: label += (colAlias == null) ? token : colAlias;// if this is of the format: response.methodName().value.ms as aliasName then use aliasName in jamon label
079: methodName = token.replaceFirst("[(][)]", "");
080: } else if ("value".equalsIgnoreCase(token)) { // response.methodName().value
081: additionToLabel += "v";// indicates value
082: } else if ("url".equalsIgnoreCase(token)) { // response.methodName().url (note both value and url can be on the same line)
083: additionToLabel += "u";// indicates url
084: } else if ("contextpath".equalsIgnoreCase(token)) { // response.methodName().url (note both value and url can be on the same line)
085: additionToLabel += "p";// indicates path such as /jamon
086: } else if ("ms".equalsIgnoreCase(token)) { // convert ms to ms. a unit of 'ms.' causes start/stop to be called (i.e. it is a time monitor)
087: units = "ms.";
088: isTimeMon = true;
089: } else { // any thing else is the units
090: units = token;
091: isTimeMon = false;
092: }
093:
094: }
095:
096: // gets rid of jsessionid from jamon summary labels if ignorehttpparams is true and we are monitoring
097: // the pagehits.
098: if (httpMonFactory.getIgnoreHttpParams()
099: && ("getRequestURI".equals(methodName) || "getRequestURL"
100: .equals(methodName)))
101: removeHttpParams = true;
102:
103: }
104:
105: String getUnits() {
106: return units;
107: }
108:
109: boolean isResponse() {
110: return isResponse;
111: }
112:
113: boolean isTimeMon() {
114: return isTimeMon;
115: }
116:
117: // This method checks to see if the size threshold has been exceeded. If this is a time monitor and the threshold
118: // has not been exceeded or if the monitor of the given time has already been created once
119: // then a time monitor is started. The method is called by HttpMonRequest for each item that is being monitored. Due to the
120: // fact that this class has no state it is being passed in via the HttpMonRequest object.
121: void start(HttpMonRequest httpMonBase) {
122:
123: if (isTimeMon) {
124: Monitor timeMon = null;
125: // if this element puts number of monitors over the threshold size then don't create a new monitor.
126: if (sizeThresholdExceeded(httpMonBase)) {
127: createSizeThresholdExceededMon(httpMonBase);
128: timeMon = NULL_MON;
129: }
130:
131: if (timeMon == null) {
132: timeMon = startTimeMon(httpMonBase);
133: }
134:
135: httpMonBase.setTimeMon(timeMon);
136: }
137: }
138:
139: // Stop any started monitors.
140: void stop(HttpMonRequest httpMonBase) {
141: if (isTimeMon) {
142: stopTimeMon(httpMonBase);
143: } else if (sizeThresholdExceeded(httpMonBase)) {// note thresholds indicators have already been created for time monitors
144: createSizeThresholdExceededMon(httpMonBase);
145: } else {
146: MonitorFactory.add(getMonKey(httpMonBase),
147: getValueToAdd(httpMonBase));
148: }
149: }
150:
151: // This method is needed as the jetty version of HttpMonItem has to track time differently.
152: Monitor startTimeMon(HttpMonRequest httpMonBase) {
153: return MonitorFactory.start(getMonKey(httpMonBase));
154: }
155:
156: // This method is also needed as the jetty version of HttpMonItem tracks time differently
157: void stopTimeMon(HttpMonRequest httpMonBase) {
158: httpMonBase.stopTimeMon();
159: }
160:
161: private boolean sizeThresholdExceeded(HttpMonRequest httpMonBase) {
162: // if number of jamon rows is greater than configured size (as well as size has been defined i.e. > 0) then don't create a monitor.
163: // if the monitor already exists then you can proceed as no more monitors will be created.
164: return MonitorFactory.getNumRows() > httpMonBase.getSize()
165: && httpMonBase.getSize() > 0
166: && !MonitorFactory.exists(getMonKey(httpMonBase));
167: }
168:
169: // Track anytime the monitor threshold has been exceeded.
170: private void createSizeThresholdExceededMon(
171: HttpMonRequest httpMonBase) {
172: String label = new StringBuffer(httpMonBase.getLabelPrefix())
173: .append(".HttpMonFactory.sizeExceeded.").append(
174: httpMonBase.getSize()).toString();
175: String detailLabel = new StringBuffer(getLabel(httpMonBase))
176: .append(", ").append(httpMonBase.getRequestURI())
177: .append(", ").append(getUnits()).toString();
178: MonitorFactory.add(new MonKeyImp(label, detailLabel, "Count"),
179: httpMonBase.getSize());
180: }
181:
182: // return key. note we are passing in details that can be used in the detail buffer. Details consist of the uri as well as a stack trace
183: // if one occured.
184: MonKey getMonKey(HttpMonRequest httpMonBase) {
185: return new MonKeyImp(getLabel(httpMonBase), httpMonBase
186: .getDetails(), getUnits());
187: }
188:
189: /** If a value label is to be used then append it to the end of the label, else just return the label. The results will be used to create
190: * a jamon record. Ex: request.getMethod() or request.getMethod().post
191: * @return
192: */
193: String getLabel(HttpMonRequest httpMonBase) {
194: if ("".equals(additionToLabel))
195: return label;
196:
197: // add value and/or url if they are required in the label.
198: StringBuffer sb = new StringBuffer(label);
199: if (!(label.charAt(label.length() - 1) == '.'))// in some cases if this isn't done then the label has no dots, or 2 consecutive ones.
200: sb.append(".");
201:
202: for (int i = 0; i < additionToLabel.length(); i++) {
203: if (i >= 1)// i.e. both u and v are specified
204: sb.append(", ");
205:
206: if (additionToLabel.charAt(i) == 'v')//value
207: sb.append("value: ").append(getValueLabel(httpMonBase));
208: else if (additionToLabel.charAt(i) == 'u')//url
209: sb.append("url: ").append(httpMonBase.getKeyReadyURI());
210: else if (additionToLabel.charAt(i) == 'p')//path
211: sb.append("contextpath: ").append(
212: httpMonBase.getContextPath());
213:
214: }
215:
216: return sb.toString();
217: }
218:
219: /** Used to create a jamon label like request.getMethod().post */
220:
221: Object getValueLabel(HttpMonRequest httpMonBase) {
222: Object valLabel = executeMethod(httpMonBase);
223: if (removeHttpParams)
224: valLabel = removeHttpParams(valLabel);
225:
226: return valLabel;
227: }
228:
229: /* Return value to be added to jamon. Note this is not called when it is a time monitor. If the object returned is a number
230: * then add that value to jamon else just add the number 1. */
231: double getValueToAdd(HttpMonRequest httpMonBase) {
232: Object obj = executeMethod(httpMonBase);
233: if (obj instanceof Number)
234: return ((Number) obj).doubleValue();
235: else
236: return 1.0;
237: }
238:
239: // Execute the request or responses method.
240: private Object executeMethod(HttpMonRequest httpMonBase) {
241: Object retValue = null;
242: try {
243: retValue = getMethod(httpMonBase).invoke(
244: getObjectToExecute(httpMonBase), (Object[]) null);// null is noargs.
245: } catch (Throwable e) {// note I don't want the program to abort due to monitoring so the exception is not being passed upstream
246: MonitorFactory.add(new MonKeyImp(httpMonBase
247: .getLabelPrefix()
248: + ".monError", new Object[] { this ,
249: Misc.getExceptionTrace(e) }, "Exception"), 1);
250: }
251:
252: return retValue;
253:
254: }
255:
256: // return method that we are getting monitoring info from.
257: Method getMethod(HttpMonRequest httpMonBase) {
258:
259: try {
260: if (method == null)
261: method = getObjectToExecute(httpMonBase).getClass()
262: .getMethod(methodName, (Class[]) null);
263: } catch (NoSuchMethodException e) {
264: }
265:
266: return method;
267:
268: }
269:
270: // get the object we are calling the monitoring method from
271: Object getObjectToExecute(HttpMonRequest httpMonBase) {
272: if (isResponse())
273: return httpMonBase.getResponse();
274: else
275: return httpMonBase.getRequest();
276: }
277:
278: public String toString() {
279: return new StringBuffer("label=").append(label).append(
280: ", units=").append(units).append(", methodName=")
281: .append(methodName).toString();
282: }
283:
284: // get alias portion of 'request.getRequestURI() AS myAlias.
285: // this is used to put a more descriptive name in jamon.
286: private static String colAlias(String str) {
287: if (str == null)
288: return null;
289:
290: String[] arr = str.split(" [aA][sS] ");
291: if (arr.length == 2) // returns pageHits when 'request.getRequestURI() as pageHits' is passed
292: return arr[1].trim();
293: else
294: return null; // returns request.getRequestURI() (no alias was used)
295: }
296:
297: // return nonalias portion of string.
298: private static String nonAlias(String str) {
299: if (str == null)
300: return null;
301:
302: String[] arr = str.split(" [aA][sS] ");
303: return arr[0].trim(); // returns request.getRequestURI() (no alias was used)
304:
305: }
306:
307: // passing this: http://www.mypage.com:8080/page;jsessionid=5akalsdjfflj;other=9s0f0udsf?pageName=my.jsp
308: // would return this: http://www.mypage.com:8080/page
309: // used to remove jsessionid from page hits.
310: private static String removeHttpParams(Object url) {
311: if (url == null)
312: return null;
313:
314: String urlStr = url.toString();
315: int paramIndex = urlStr.indexOf(";");
316: if (paramIndex == -1)// ; isn't in string
317: return urlStr;
318: else
319: return urlStr.substring(0, paramIndex);
320:
321: }
322:
323: public static void main(String[] args) {
324:
325: System.out
326: .println(colAlias("request.getMethod().units.value as MyLabel"));
327: System.out.println(colAlias("request.getMethod().units.value")); // null no alias
328: System.out.println(colAlias("request.getMethod()"));// null no alias
329: System.out
330: .println(nonAlias("request.getMethod().units.value as MyLabel"));
331:
332: System.out
333: .println(removeHttpParams("http://www.mypage.com:8080/page;jsessionid=5akalsdjfflj;other=9s0f0udsf?pageName=my.jsp"));
334: System.out
335: .println(removeHttpParams("http://www.mypage.com:8080/page?pageName=my.jsp"));
336:
337: }
338:
339: }
|