001 /* 002 * Copyright 2008-2009 the original author or authors. 003 * The contents of this file are subject to the Mozilla Public License 004 * Version 1.1 (the "License"); you may not use this file except in 005 * compliance with the License. You may obtain a copy of the License at 006 * http://www.mozilla.org/MPL/ 007 * 008 * Software distributed under the License is distributed on an "AS IS" 009 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 010 * License for the specific language governing rights and limitations 011 * under the License. 012 */ 013 014 package com.mtgi.analytics.aop; 015 016 import java.lang.reflect.Array; 017 import java.lang.reflect.Method; 018 019 import org.aopalliance.intercept.MethodInterceptor; 020 import org.aopalliance.intercept.MethodInvocation; 021 import org.springframework.beans.factory.annotation.Required; 022 023 import com.mtgi.analytics.BehaviorEvent; 024 import com.mtgi.analytics.BehaviorTrackingManager; 025 import com.mtgi.analytics.EventDataElement; 026 027 /** 028 * Aspect-Oriented "around" advisor that logs method invocations 029 * to a backing instance of {@link BehaviorTrackingManager}. Method 030 * parameters and result value are included in the event data. 031 * The eventType attribute of generated events is set to <code>method</code> 032 * unless overridden with {@link #setEventType(String)}. 033 */ 034 public class BehaviorTrackingAdvice implements MethodInterceptor { 035 036 private String eventType = "method"; 037 private BehaviorTrackingManager trackingManager; 038 039 private String application; 040 041 public String getApplication() { 042 return application; 043 } 044 045 @Required 046 public void setApplication(String application) { 047 this.application = application; 048 } 049 050 public void setEventType(String eventType) { 051 this.eventType = eventType; 052 } 053 054 @Required 055 public void setTrackingManager(BehaviorTrackingManager manager) { 056 this.trackingManager = manager; 057 } 058 059 public BehaviorTrackingManager getTrackingManager() { 060 return trackingManager; 061 } 062 063 064 public Object invoke(MethodInvocation invocation) throws Throwable { 065 066 Method m = invocation.getMethod(); 067 String eventName = m.getDeclaringClass().getName() + "." + m.getName(); 068 069 BehaviorEvent event = trackingManager.createEvent(eventType, eventName); 070 071 //log method parameters. would be nice if we could figure 072 //out parameter names here. 073 EventDataElement data = event.addData(); 074 EventDataElement parameters = data.addElement("parameters"); 075 Class<?>[] sig = m.getParameterTypes(); 076 Object[] args = invocation.getArguments(); 077 078 for (int i = 0; i < sig.length; ++i) { 079 Object val = args[i]; 080 logValue(parameters, "param", sig[i], val); 081 } 082 083 trackingManager.start(event); 084 try { 085 Object ret = invocation.proceed(); 086 logValue(data, "result", m.getReturnType(), ret); 087 return ret; 088 } catch (Throwable error) { 089 event.setError(error); 090 throw error; 091 } finally { 092 trackingManager.stop(event); 093 } 094 } 095 096 /** 097 * marshal the given value to a String. Only java builtin types, 098 * and arrays of those types, are converted; everything else is 099 * represented as "[qualified.type.name]" to avoid invoking 100 * costly (or buggy) toString() methods. 101 */ 102 private static final void logValue(EventDataElement parent, String elementName, Class<?> expectedType, Object arg) { 103 EventDataElement element = parent.addElement(elementName); 104 if (arg == null) 105 return; 106 107 Class<?> type = arg.getClass(); 108 //log the concrete type of the argument if it differs from the expected type (i.e. is a subclass) 109 //the primitive type checks avoid logging redundant type info for autoboxed values 110 if (type != expectedType && !(expectedType.isPrimitive() || type.isPrimitive())) 111 element.add("type", type.getName()); 112 113 //TODO: use annotations or some other configuration for custom 114 //parameter logging? 115 String value = "{object}"; 116 if (type.isArray()) { 117 if (shouldLog(type.getComponentType())) 118 value = toStringArray(arg); 119 } else { 120 if (shouldLog(type)) 121 value = arg.toString(); 122 } 123 element.setText(value); 124 } 125 126 protected static final String toStringArray(Object array) { 127 StringBuffer ret = new StringBuffer("["); 128 int len = Array.getLength(array); 129 int maxLen = Math.min(len, 100); 130 for (int i = 0; i < maxLen; ++i) { 131 if (i > 0) 132 ret.append(", "); 133 ret.append(String.valueOf(Array.get(array, i))); 134 } 135 if (maxLen < len) 136 ret.append(", ... (").append(len - maxLen).append(" more)"); 137 ret.append("]"); 138 return ret.toString(); 139 } 140 141 private static final boolean shouldLog(Class<?> type) { 142 return (type.isPrimitive()) || type.isEnum() || type.getName().startsWith("java.lang"); 143 } 144 145 }