View Javadoc

1   /*
2    * EL4J, the Extension Library for the J2EE, adds incremental enhancements to
3    * the spring framework, http://el4j.sf.net
4    * Copyright (C) 2005 by ELCA Informatique SA, Av. de la Harpe 22-24,
5    * 1000 Lausanne, Switzerland, http://www.elca.ch
6    *
7    * EL4J is published under the GNU Lesser General Public License (LGPL)
8    * Version 2.1. See http://www.gnu.org/licenses/
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13   * GNU Lesser General Public License for more details.
14   *
15   * For alternative licensing, please contact info@elca.ch
16   */
17  package ch.elca.el4j.services.statistics.detailed.contextpassing;
18  
19  import java.lang.reflect.InvocationHandler;
20  import java.lang.reflect.Proxy;
21  import java.net.InetAddress;
22  import java.net.UnknownHostException;
23  import java.util.Map;
24  
25  import org.aopalliance.intercept.MethodInterceptor;
26  import org.aopalliance.intercept.MethodInvocation;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import ch.elca.el4j.services.statistics.detailed.MeasureId;
31  import ch.elca.el4j.services.statistics.detailed.MeasureItem;
32  import ch.elca.el4j.services.statistics.detailed.processing.MeasureCollectorService;
33  
34  /**
35   * Invoker for measuring the time of calls to a EL4J service.
36   * <p>
37   * This invoker is not limited to a position. It may be used
38   * as Stub invoker or Proxy invoker.
39   * <p>
40   * Its configuration defines :
41   * <ul>
42   * <li>the associated collector service name with <code>collector</code> entry
43   * </li>
44   * <li>the level to publish in MeasureItem with <code>level</code> entry
45   * </li>
46   * </ul>
47   *
48   * This class was ported from Leaf 2.
49   * Original authors: WHO, DBA.
50   * Leaf2 package name: ch.elca.leaf.services.measuring
51   *
52   *
53   * @svnLink $Revision: 3880 $;$Date: 2009-08-04 15:17:52 +0200 (Di, 04. Aug 2009) $;$Author: swismer $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/detailed_statistics/common/src/main/java/ch/elca/el4j/services/statistics/detailed/contextpassing/MeasureInterceptor.java $
54   *
55   * @author Rashid Waraich (RWA)
56   * @author Philipp Oser (POS)
57   */
58  public class MeasureInterceptor  implements MethodInterceptor {
59  	
60  	/**
61  	 * The host name.
62  	 */
63  	protected static String s_hostName = "<unknown>";
64  	{
65  		try {
66  			s_hostName = InetAddress.getLocalHost().getHostName();
67  			// Checkstyle: EmptyBlock off
68  		} catch (UnknownHostException e) {
69  		}
70  			// Checkstyle: EmptyBlock on
71  	}
72  	
73  	/**
74  	 * Private logger.
75  	 */
76  	private static Logger s_logger = LoggerFactory.getLogger(MeasureInterceptor.class);
77  		
78  	/**
79  	 * ThreadLocal variables to store the measureId locally in the thread.
80  	 */
81  	private MeasureCollectorService m_collectorService = null;
82  
83  	/** Level for MeasureItem. */
84  	private String m_level = null;
85  	
86  	/**	Mapping of proxied class names and their substitutes. */
87  	private Map<String, String> m_proxyMap;
88  
89  	/**
90  	 * The constructor.
91  	 *
92  	 * @param collectorService
93  	 *            The collectorService, where the measured data should be stored
94  	 *            to.
95  	 * @param isServer
96  	 *            Is this a server (client=false).
97  	 * @deprecated use the constructor with (MeasureCollectorService,String)
98  	 *             instead
99  	 */
100 	public MeasureInterceptor(MeasureCollectorService collectorService,
101 		boolean isServer) {
102 		this.m_collectorService = collectorService;
103 		if (isServer) {
104 			this.m_level = "EJB_CONTAINER";
105 		} else {
106 			this.m_level = "CLIENT";
107 		}
108 	}
109 
110 	/**
111 	 * The constructor.
112 	 *
113 	 * @param collectorService
114 	 *            The collectorService, where the measured data should be stored
115 	 *            to.
116 	 * @param vmName
117 	 *            Name of the virual machine
118 	 */
119 	public MeasureInterceptor(MeasureCollectorService collectorService,
120 		String vmName) {
121 		m_collectorService = collectorService;
122 		m_level = vmName;
123 	}
124 	
125 	/**
126 	 * {@inheritDoc}
127 	 */
128 	public Object invoke(MethodInvocation methodInvocation) throws Throwable {
129 		
130 		// we will work on the passed object directly (it is stored in a
131 		//  threadlocal in the DetailedStatisticsSharedContextHolder)
132 		DetailedStatisticsContext context
133 			= DetailedStatisticsSharedContextHolder.getContext();
134 
135 		int seqNumber = 0;
136 		int[] hierarchy = new int[0];
137 		long startTime = 0;
138 		boolean ignoreThisMeasure = false;
139 		
140 		// Indicates that the ID was newly created in this currently running
141 		// invoke method. At the end of this currently running invoke method,
142 		// we should drop it again.
143 		boolean dropIdAtEnd = false;
144 		
145 		// Decide whether to leave proxied class name, substitute it or leave
146 		// proxy description completely away.
147 		String className = methodInvocation.getThis().getClass().getName();
148 		Object target = methodInvocation.getThis();
149 		if (target.getClass().getSuperclass().equals(Proxy.class)
150 			&& m_proxyMap != null) {
151 			
152 			InvocationHandler handler = Proxy.getInvocationHandler(target);
153 			String proxyClassName = handler.getClass().getName();
154 			
155 			if (m_proxyMap.containsKey(proxyClassName)) {
156 				if (m_proxyMap.get(proxyClassName).equals("")) {
157 					ignoreThisMeasure = true;
158 				} else {
159 					className = (String) m_proxyMap.get(proxyClassName);
160 				}
161 			}
162 		}
163 		
164 		// Proxied class was not ignored.
165 		if (!ignoreThisMeasure) {
166 		
167 			// depth++
168 			context.setDepth(context.getDepth() + 1);
169 			seqNumber = context.getSequenceNumber();
170 			seqNumber++;
171 	
172 			s_logger.info(methodInvocation.toString());
173 			s_logger.info("depth:" + context.getDepth());
174 				
175 			/* ----------------------------------------------------------------
176 			 * If no mesure id is available this is the first measure and the
177 			 * interceptor has to generate a new MeasureId
178 			 * ---------------------------------------------------------------*/
179 			if (context.getMeasureId() == null) {
180 	
181 				// The id does not exist : client side, or first call
182 				// of the invoker from service side
183 				context.setMeasureId(MeasureId.createID());
184 				hierarchy = new int[]{1};
185 				
186 				dropIdAtEnd = true;
187 	
188 			} else {
189 			/* ----------------------------------------------------------------
190 			 * A MeasureId was found in the shared context
191 			 * This means an additional call in the existing measuring chain.
192 			 * ---------------------------------------------------------------*/
193 	
194 				hierarchy = context.getHierarchy();
195 	
196 				// after previous depth incremental!
197 				assert (context.getDepth() <= hierarchy.length);
198 	
199 				s_logger.info("*hier:" + hierarchy.length
200 					+ " " + context.getDepth());
201 				
202 				// extend hierarchy-array if needed
203 				if ((hierarchy.length + 1) == context.getDepth()) {
204 					int[] extendedHierarchy = new int[hierarchy.length + 1];
205 					for (int i = 0; i < hierarchy.length; i++) {
206 						extendedHierarchy[i] = hierarchy[i];
207 					}
208 					extendedHierarchy[hierarchy.length] = 0;
209 	
210 					hierarchy = extendedHierarchy;
211 				}
212 					
213 				s_logger.info("hier:" + hierarchy.length + " "
214 					+ context.getDepth());
215 				hierarchy[context.getDepth() - 1]
216 					= hierarchy[context.getDepth() - 1] + 1;
217 			}
218 	 
219 			// Perform invocation and measure time
220 			startTime = System.currentTimeMillis();
221 	
222 			long lastStartTime = context.getStartTime();
223 	
224 			// Correction of unsynchronized clocks.
225 			if (startTime < lastStartTime) {
226 				startTime = lastStartTime;
227 			}
228 	
229 			// Put the values in shared context
230 			context.setStartTime(startTime);
231 			context.setSequenceNumber(seqNumber);
232 			context.setHierarchy(hierarchy);
233 		}
234 		
235 		Object retVal = null;
236 		try {
237 			// the invocation
238 			retVal = methodInvocation.proceed();
239 		
240 		} catch (Throwable t) {
241 			throw t;
242 		} finally {
243 		
244 			// Proxied class was not ignored.
245 			if (!ignoreThisMeasure) {
246 				// Calculate duration after invocation
247 				long duration = System.currentTimeMillis() - startTime;
248 	
249 			/*
250 			 * -----------------------------------------------------------------
251 			 * Create MeasureItem and pass it to the CollectorService that
252 			 * stores is
253 			 * -----------------------------------------------------------------
254 			 */
255 				if (m_collectorService != null) {
256 					MeasureItem tempMeasure = new MeasureItem(
257 						context.getMeasureId(), seqNumber, s_hostName, m_level,
258 						className, methodInvocation.getMethod().getName(),
259 						startTime, duration,
260 						arrayToString(hierarchy, context.getDepth()));
261 					m_collectorService.add(tempMeasure);
262 	
263 				} else {
264 					s_logger.info("invoke, Unable to write measure because "
265 						+ "MeasureCollectorService is not available. "
266 						+ "The measure is ignored");
267 				}
268 	
269 				// depth--
270 				context.setDepth(context.getDepth() - 1);
271 	
272 				if (dropIdAtEnd) {
273 					// start with a fresh context
274 					DetailedStatisticsSharedContextHolder
275 						.setContext(new DetailedStatisticsContext());
276 				}
277 				
278 			}
279 		}
280 		return retVal;
281 	}
282 
283 	/**
284 	 * Sets the mapping between proxied class names and their subsitutes.
285 	 * 
286 	 * @param proxyMap Fully qualified class names and respective short name of
287 	 * replacement. Leave value empty if proxied class should be ignored.
288 	 */
289 	public void setProxyMap(Map<String, String> proxyMap) {
290 		
291 		this.m_proxyMap = proxyMap;
292 	}
293 	
294 	/**
295 	 * Returns the mapping between proxied class names and their substitutes.
296 	 * 
297 	 * @return Fully qualified class names and respective short name of
298 	 * replacement.
299 	 */
300 	public Map<String, String> getProxyMap() {
301 		
302 		return this.m_proxyMap;
303 	}
304 	
305 	/**
306 	 * Print the hierarchy array as String with "-" between every array entry.
307 	 *
308 	 * @param array
309 	 *            Every array entry must provide a toString() method
310 	 * @param depth Depth of array
311 	 * @return A string representation of the array. (E.g. "1-2-1")
312 	 */
313 	private String arrayToString(int[] array, int depth) {
314 		if (array == null) {
315 			return "null";
316 		}
317 
318 		StringBuffer str = new StringBuffer();
319 		for (int j = 0; j < depth; j++) {
320 			str.append(array[j]);
321 
322 			if ((j + 1) != depth) {
323 				str.append("-");
324 			}
325 		}
326 
327 		return str.toString();
328 	}
329 }