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 }