1 package org.lsst.ccs.utilities.tracers;
2
3 import org.lsst.ccs.bootstrap.resources.BootstrapResourceUtils;
4 import org.lsst.ccs.utilities.jars.MavenResources;
5
6 import java.net.URL;
7 import java.util.Arrays;
8 import java.util.Properties;
9 import java.util.ServiceLoader;
10
11 import static org.lsst.ccs.utilities.jars.CommonResources.*;
12
13 /**
14 * Utility to add traces to a code.
15 * <p/>
16 * Though it is a hack on assertions this can be used that way
17 * <PRE>
18 * import static org.lsst.ccs.utilities.tracers.Tracer.* ;
19 * ....
20 * public void myMethod(int arg) {
21 * assert trace("myMethod called with" +arg) ;
22 * // code
23 * }
24 * <p/>
25 * public void anotherMethod(String arg) {
26 * assert stackTrace() ;
27 * }
28 * </PRE>
29 * A developper can also use home-grown filters
30 * <p/>
31 * <PRE>
32 * import static org.lsst.ccs.utilities.tracers.Tracer.* ;
33 * import org.lsst.ccs.mypackage.MyEnum ;
34 * ....
35 * public void myMethod(int arg) {
36 * assert trace(MyEnum.DOING_THIS, "myMethod called with" +arg) ;
37 * // code
38 * }
39 * <p/>
40 * public void anotherMethod(String arg) {
41 * assert stackTrace(MyEnum.DOING_THAT) ;
42 * }
43 * </PRE>
44 * then if System property <TT>org.lsst.ccs.traces</TT> is set
45 * the traces can be produced or not. Example of option
46 * <TT>-Dorg.lsst.ccs.traces=DOING_THIS,DOING_THAT</TT>
47 * <p/>
48 * By default the strings are printed to System.out but this can be changed.
49 * If you want to delegate the reporting to another code (such as a Logger)
50 * write a code implementing <TT>Tracer.Strategy</TT>
51 * and deploy it using the <TT>ServiceLoader</TT> (or put the canonical
52 * className as value for property <TT>org.lsst.ccs.traceReporter</TT>).
53 *
54 * Other methods of this class are mostly to be used while debugging and do not
55 * use keys for filtering.
56 *
57 * @author bamade
58 */
59 // Date: 23/04/13
60
61 public class Tracer {
62 /**
63 * defines the behaviour of a tracer.
64 * Implementations can forward traces to a Logger for instance.
65 */
66 public interface Strategy {
67 /**
68 * starts a trace and reports a String.
69 * Each "report" (mostly one line reports) is terminated by a call
70 * to <TT>endReport</TT>
71 *
72 * @param report
73 */
74 void report(String report);
75
76 /**
77 * long reports that span many lines (such as stackTraces) may have intermediate
78 * calls to <TT>addReportLine</TT> before a call to <TT>endReport</TT>
79 *
80 * @param line
81 */
82 void addReportLine(String line);
83
84 /**
85 * marks the end of a trace (this can be used with implementations that use
86 * parenthesized expressions -such as XML-).
87 * it is guaranteed that each call to <TT>report</TT> is closed by a call
88 * to <TT>endReport</TT> (in the meantime many invocations to <TT>addReportLine</TT>
89 * may have been fired).
90 */
91 void endReport();
92 }
93
94 /**
95 * Convenience class to create a code that reports to many report "handlers".
96 * (for instance to a Logger AND System.out).
97 */
98 public static class MultiReporter implements Strategy {
99 Strategy[] strategies;
100
101 public MultiReporter(Strategy... strategies) {
102 this.strategies = strategies;
103 }
104
105 @Override
106 public void report(String report) {
107 for (Strategy strategy : strategies) {
108 strategy.report(report);
109 }
110 }
111
112 @Override
113 public void addReportLine(String line) {
114 for (Strategy strategy : strategies) {
115 strategy.addReportLine(line);
116 }
117 }
118
119 @Override
120 public void endReport() {
121 for (Strategy strategy : strategies) {
122 strategy.endReport();
123 }
124 }
125 }
126
127 static Strategy reporter;
128
129 static {
130 ServiceLoader<Tracer.Strategy> loader = ServiceLoader.load(Tracer.Strategy.class);
131 boolean found = false;
132 try {
133 for (Tracer.Strategy rep : loader) {
134 reporter = rep;
135 found = true;
136 break;
137 }
138
139 } catch (Exception exc) { /*IGNORE*/ }
140 if (!found) {
141 String className = BootstrapResourceUtils.getBootstrapSystemProperties().getProperty("org.lsst.ccs.traceReporter");
142 // String className = System.getProperty("org.lsst.ccs.traceReporter");
143 if (className == null) {
144 reporter = new Strategy() {
145 @Override
146 public void report(String report) {
147 System.out.println(report);
148 }
149
150 @Override
151 public void addReportLine(String line) {
152 System.out.println('\t' + line);
153 }
154
155 @Override
156 public void endReport() {
157 }
158 };
159 } else {
160 try {
161 Class clazz = Class.forName(className);
162 reporter = (Strategy) clazz.newInstance();
163 } catch (Exception exc) {
164 throw new Error("incorrect class for trace configuration", exc);
165 }
166 }
167
168 }
169 }
170
171 public static void setReporter(Strategy reporter) {
172 Tracer.reporter = reporter;
173 }
174
175 public static Strategy getReporter() {
176 return reporter;
177 }
178
179 static final String[] keys;
180
181 public static String[] getKeys() {
182 return Arrays.copyOf(keys, keys.length);
183 }
184
185 static {
186 // String properties = System.getProperty("org.lsst.ccs.traces");
187 String properties = BootstrapResourceUtils.getBootstrapSystemProperties().getProperty("org.lsst.ccs.traces");
188 if (properties != null) {
189 keys = properties.split(",");
190 } else {
191 keys = new String[0];
192 }
193 }
194
195 /**
196 * utility method to fire a trace.
197 * Though it is not mandatory this method could be used as a side-effect of an <TT>assert</TT>
198 * (this is not a politically correct use of the assert feature).
199 *
200 * @param traceString will always be transmitted to code for reporting the trace
201 * @return
202 */
203 public static boolean trace(String traceString) {
204 reporter.report(traceString);
205 reporter.endReport();
206 return true;
207 }
208
209 /**
210 * reports a trace only if the <TT>filter</TT> string is part of the <TT>org.lsst.ccs.traces</TT>
211 * property.
212 * It is highly recommended to use enums instead of Strings to operate this filtering.
213 *
214 * @param filter
215 * @param traceString
216 * @return
217 */
218 public static boolean trace(String filter, String traceString) {
219 for (String key : keys) {
220 if (key.equalsIgnoreCase(filter)) {
221 reporter.report(traceString);
222 reporter.endReport();
223 return true;
224 }
225 }
226 return true;
227 }
228
229 /**
230 * uses an enum constant to filter the message passed to the tracer.
231 *
232 * @param enumElement
233 * @param traceString
234 * @param <T>
235 * @return
236 */
237 public static <T extends Enum> boolean trace(T enumElement, String traceString) {
238 return trace(String.valueOf(enumElement), traceString);
239 }
240
241 /**
242 * reports a complete stack trace of the current code execution.
243 * It is highly recommended to use with an assert and preferably with a filter.
244 *
245 * @return
246 */
247 public static boolean stackTrace() {
248 stackTrace(3);
249 return true;
250 }
251
252 /**
253 * reports a complete stack trace of the current code execution
254 * only if the <TT>filter</TT> string is part of the <TT>org.lsst.ccs.traces</TT>
255 * property.
256 *
257 * @param filter
258 * @return
259 */
260 public static boolean stackTrace(String filter) {
261 for (String key : keys) {
262 if (key.equalsIgnoreCase(filter)) {
263 stackTrace(3);
264 return true;
265 }
266 }
267 return true;
268 }
269
270 /**
271 * reports a complete stack trace of the current code execution
272 * only if the <TT>filter</TT> enum constant is cited in the <TT>org.lsst.ccs.traces</TT>
273 * property.
274 *
275 * @param enumElement
276 * @param <T>
277 * @return
278 */
279 public static <T extends Enum> boolean stackTrace(T enumElement) {
280 String filter = String.valueOf(enumElement);
281 for (String key : keys) {
282 if (key.equalsIgnoreCase(filter)) {
283 stackTrace(3); //TODO check this value
284 return true;
285 }
286 }
287 return true;
288 }
289
290 /**
291 * reports part a stacktrace starting at index <TT>levelOut</TT>
292 *
293 * @param levelOut
294 */
295 protected static void stackTrace(int levelOut) {
296 StackTraceElement[] stack = Thread.currentThread().getStackTrace();
297 reporter.report("-----------------");
298 for (int ix = levelOut; ix < stack.length; ix++) {
299 StackTraceElement element = stack[ix];
300 reporter.addReportLine(element.toString());
301 }
302 reporter.endReport();
303 }
304
305
306 /**
307 * Trace properties linked to an object.
308 * This method always traces and does not use filtering "keys".
309 * @param obj
310 * @param propFile name of the property file (without leading slash
311 * @return always true (for use in assertions)
312 */
313 public static boolean traceProperties(Object obj, String propFile) {
314 if (obj != null) {
315 Class clazz ;
316 if(obj instanceof Class) {
317 clazz = (Class) obj ;
318 } else {
319 clazz = obj.getClass();
320 }
321 reporter.report("properties for :" +clazz.getSimpleName() + " [" + clazz.getPackage() + "]");
322 URL[] resURLs = getURLsFor(obj, propFile);
323 for (URL url : resURLs) {
324 Properties props = getPropertiesFrom(url);
325 reporter.addReportLine( " -> " + (props.size() != 0 ? props : " no project property"));
326 reporter.addReportLine("\t (in: " + getResourceContainer(url, propFile) + " ) ");
327
328 }
329 reporter.endReport();
330 }
331 return true;
332 }
333
334 /**
335 * traces the package infos of a Class (including version).
336 * This method always traces and does not use filtering "keys".
337 * @param obj can be an Object or a Class
338 * @return always true
339 */
340 public static boolean traceInfos(Object obj) {
341 if (obj != null) {
342 Class clazz ;
343 if(obj instanceof Class) {
344 clazz = (Class) obj ;
345 } else {
346 clazz = obj.getClass();
347 }
348 reporter.report("infos for :" + clazz.getSimpleName() + " [" + clazz.getPackage() + "]");
349 reporter.endReport();
350 }
351 return true ;
352 }
353
354 /**
355 * traces where a class comes from.
356 * This method always traces and does not use filtering "keys".
357 * @param obj can be an Object or a Class
358 * @return always true
359 */
360 public static boolean traceWhere(Object obj) {
361 String[] urlNames = getClassURLNamesFor(obj) ;
362 reporter.report("where is :" + (obj instanceof Class ? obj: obj.getClass())) ;
363 for(String name : urlNames) {
364 reporter.addReportLine(" -> " + name);
365 }
366 reporter.endReport();
367 return true ;
368 }
369
370 /**
371 * traces standard pom properties as deployed by maven.
372 * This method always traces and does not use filtering "keys".
373 * @param orgName name of organisation ("org.lsst")
374 * @param projectName name of project ("org-lsst-ccs-core")
375 * @return
376 */
377 public static boolean tracePomProperties(String orgName, String projectName) {
378 URL[] urlsPom = MavenResources.getPomURLs(orgName,projectName) ;
379 reporter.report("poms for :" +orgName + " " +projectName);
380 for(URL url: urlsPom) {
381 Properties props = getPropertiesFrom(url);
382 reporter.addReportLine( " -> " + (props.size() != 0 ? props : " no project property"));
383 }
384 reporter.endReport();
385 return true ;
386 }
387
388 /**
389 * traces non-standard <TT>$Project.maven.properties</TT>.
390 * This method always traces and does not use filtering "keys".
391 * @param projectName name of project ("org-lsst-ccs-core")
392 * @return always true
393 */
394 public static boolean traceOurMavenProperties(String projectName) {
395 URL[] urlsPom = MavenResources.getMavenProjectURLs(projectName) ;
396 reporter.report("project maven properties for :" +projectName);
397 for(URL url: urlsPom) {
398 Properties props = getPropertiesFrom(url);
399 reporter.addReportLine( " -> " + (props.size() != 0 ? props : " no project property"));
400 }
401 reporter.endReport();
402 return true ;
403 }
404
405 /**
406 *
407 * @param vcsString the caller should have put the result of a subversion evaluation (variables Rev, Author between '$' chars)
408 * @param clazz
409 * @param orgName such as org.lsst
410 * @param projectName
411 * @return
412 */
413 //TODO: now that maven generates a correct Manifest get infos from Manifest
414 public static boolean version(String vcsString, Class clazz, String orgName ,String projectName) {
415 for (String key : keys) {
416 if (key.equalsIgnoreCase("VERSION")) {
417 Properties props = MavenResources.getPomProperties(orgName,projectName) ;
418 String version = props.getProperty("version", "unknown version") ;
419 String message = String.format("VERSION: %s %s (with loaders %s,%s,%s)", clazz.getCanonicalName(), vcsString, clazz.getClassLoader(), Thread.currentThread().getContextClassLoader(), ClassLoader.getSystemClassLoader() ) ;
420 reporter.report( message);
421 message = String.format("-------: %s %s ", projectName, version) ;
422 reporter.report( message);
423 reporter.endReport();
424 return true;
425 }
426 }
427 return true ;
428 }
429 public static boolean version(String vcsString, Class clazz, String projectName) {
430 return version(vcsString,clazz,"org.lsst",projectName) ;
431 }
432
433
434 /**
435 * normally assertion should not have side-effects. This method circumvents this rule:
436 * if a system property is set the <TT>Runnable</TT> will be executed and might have side-effects.
437 * This can be used to "inject" specific testing behaviours in a class.
438 * @param enablingSystemProperty the name of a boolean system property that allows this code to be run
439 * @param sideEffectCode the code that will be executed in the current Thread (beware: exceptions not caught!)
440 * @return always true
441 */
442 public static boolean injectSideEffect(String enablingSystemProperty, Runnable sideEffectCode) {
443 boolean activation = Boolean.getBoolean(enablingSystemProperty) ;
444 if(activation) {
445 sideEffectCode.run();
446 }
447 return true ;
448 }
449 }