View Javadoc

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 }