View Javadoc

1   ////////////////////////////////////////////////////////////////////////////////
2   //
3   //                  ObjectLab is sponsoring QALab
4   // 
5   // Based in London, we are world leaders in the design and development 
6   // of bespoke applications for the Securities Financing markets.
7   // 
8   // <a href="http://www.objectlab.co.uk/open">Click here to learn more</a>
9   //           ___  _     _           _   _          _
10  //          / _ \| |__ (_) ___  ___| |_| |    __ _| |__
11  //         | | | | '_ \| |/ _ \/ __| __| |   / _` | '_ \
12  //         | |_| | |_) | |  __/ (__| |_| |__| (_| | |_) |
13  //          \___/|_.__// |\___|\___|\__|_____\__,_|_.__/
14  //                   |__/
15  //
16  //                   http://www.ObjectLab.co.uk
17  // ---------------------------------------------------------------------------
18  //
19  //QALab is released under the GNU General Public License.
20  //
21  //QALab: Collects QA Statistics from your build over time.
22  //2005+, ObjectLab Ltd
23  //
24  //This library is free software; you can redistribute it and/or
25  //modify it under the terms of the GNU General Public
26  //License as published by the Free Software Foundation; either
27  //version 2.1 of the License, or (at your option) any later version.
28  //
29  //This library is distributed in the hope that it will be useful,
30  //but WITHOUT ANY WARRANTY; without even the implied warranty of
31  //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
32  //General Public License for more details.
33  //
34  //You should have received a copy of the GNU General Public
35  //License along with this library; if not, write to the Free Software
36  //Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
37  //
38  ////////////////////////////////////////////////////////////////////////////////
39  package net.objectlab.qalab.parser;
40  
41  import net.objectlab.qalab.util.QALabTags;
42  import net.objectlab.qalab.util.TaskLogger;
43  import net.objectlab.qalab.util.Util;
44  
45  import org.xml.sax.Attributes;
46  import org.xml.sax.InputSource;
47  import org.xml.sax.SAXException;
48  import org.xml.sax.helpers.DefaultHandler;
49  
50  import javax.xml.parsers.ParserConfigurationException;
51  import javax.xml.parsers.SAXParser;
52  import javax.xml.parsers.SAXParserFactory;
53  
54  import java.io.IOException;
55  import java.io.Writer;
56  
57  import java.text.ParseException;
58  
59  import java.util.Calendar;
60  import java.util.Date;
61  import java.util.HashMap;
62  import java.util.Iterator;
63  import java.util.LinkedList;
64  import java.util.List;
65  import java.util.Map;
66  import java.util.Stack;
67  
68  /**
69   * A handler class which uses SAX method to parse the XML file from QALab and
70   * extract the files that have seen a change in statistics within the time
71   * window.
72   * 
73   * @author Paramjit Rehinsi
74   * @version $Revision: 199 $
75   * @todo add a StartDate for the window
76   * @todo add an EndDate for the Window
77   * @todo simplify the constructor to remove File dependencies.
78   */
79  public class BuildStatMoverHandler extends DefaultHandler {
80      private static final int ONE_DAY = 24;
81  
82      /**
83       * default 48h to deduct is weekendAdjustment.
84       */
85      private static final int WEEKEND_SKIP = 48;
86  
87      // ~ Instance fields
88      // ------------------------------------------------------------------------
89  
90      /**
91       * The types to be handled checkstyle|findbugs|pmd|simian.
92       */
93      private List listOfTypes = new LinkedList();
94  
95      /**
96       * The logger to use for exceptions and debug information.
97       */
98      private TaskLogger tasklogger;
99  
100     /**
101      * The QALab input source.
102      */
103     private InputSource qalabSource;
104 
105     /**
106      * the XML writer with the "movers".
107      */
108     private Writer outputWriter;
109 
110     /**
111      * A map containing a Stack of items per type.
112      */
113     private Map mapOfTypes = new HashMap();
114 
115     /**
116      * list of file results.
117      */
118     private List filebeans = new LinkedList();
119 
120     /**
121      * the current file result.
122      */
123     private FileStats fileResult;
124 
125     /**
126      * the current file name.
127      */
128     private String filename;
129 
130     /**
131      * the current path to file.
132      */
133     private String path;
134 
135     /**
136      * cutoff earliest date to consider in the stats.
137      */
138     private Date startDateCutoff;
139 
140     /**
141      * cutoff latest date to consider in the stats.
142      */
143     private Date endDateCutoff;
144 
145     /**
146      * StartTimeWindow.
147      */
148     private Date startTimeWindow = null;
149 
150     /**
151      * End Time Window.
152      */
153     private Date endTimeWindow = null;
154 
155     /**
156      * start time offset (from now or endTimeWindow).
157      */
158     private String offsetHours;
159 
160     /**
161      * weekendAdjustment, true and an extra 48 will be subtracted to any
162      * datecutoff that fell on a Saturday or Sunday.
163      */
164     private boolean wkendadj = false;
165 
166     /**
167      * quiet no debug if true (default).
168      */
169     private boolean quiet = true;
170     
171     /** offset in hours to determine if the stat has fallen to 0 over the last n hours
172      * from the endDateCutoff.
173      */
174     private int hoursOffsetForLastRun = ONE_DAY;
175 
176     // ~ Constructors
177     // ------------------------------------------------------------------------
178 
179     /**
180      * Constructor used by ant Task/Maven.
181      * 
182      * @param qaLabSource
183      *            the original QALab file.
184      * @param types
185      *            the types to extract.
186      * @param outputFile
187      *            the output file.
188      * @param weekendAdjustment
189      *            true if it should adjust for weekends.
190      * @param log
191      *            the logger to use.
192      * @param noDebug
193      *            if true, quiet output (no debug info).
194      */
195     public BuildStatMoverHandler(final InputSource qaLabSource,
196             final String types, final Writer outputFile,
197             final boolean weekendAdjustment, final TaskLogger log,
198             final boolean noDebug) {
199         setLogger(log);
200         quiet = noDebug;
201         wkendadj = weekendAdjustment;
202         listOfTypes = Util.listify(types, ",");
203         processTypes(listOfTypes);
204         this.outputWriter = outputFile;
205 
206         qalabSource = qaLabSource;
207     }
208 
209     // ~ Methods
210     // ------------------------------------------------------------------------
211 
212     /**
213      * Create an instance of the SAX Parser and process the qalab.xml file.
214      * 
215      * @throws ParserConfigurationException
216      *             cannot create an instance of parser.
217      * @throws SAXException
218      *             any SAX issue.
219      * @throws java.io.IOException
220      *             any IO/access to file issue.
221      */
222     public final void process() throws ParserConfigurationException,
223             SAXException, IOException {
224         calculateCutoffDateTime();
225 
226         SAXParserFactory factory = SAXParserFactory.newInstance();
227         SAXParser saxParser = factory.newSAXParser();
228 
229         saxParser.parse(getQalabSource(), this);
230     }
231 
232     /**
233      * Process the types, assign a new stack to each type.
234      * 
235      * @param listoftypes
236      *            list of types to support.
237      */
238     private void processTypes(final List listoftypes) {
239         Iterator i = listoftypes.iterator();
240 
241         while (i.hasNext()) {
242             mapOfTypes.put(i.next(), new Stack());
243         }
244     }
245 
246     /**
247      * Gets the filename from the id attribute.
248      * 
249      * @param attrs
250      *            XML attributes.
251      */
252     public final void getFileName(final Attributes attrs) {
253         if (attrs != null) {
254             for (int i = 0; i < attrs.getLength(); i++) {
255                 String name = attrs.getQName(i); // Attr name
256                 String value = attrs.getValue(i);
257 
258                 if (QALabTags.ID_ATTRIBUTE.equals(name)) {
259                     filename = value;
260 
261                     if (!quiet) {
262                         tasklogger.log("File:" + filename);
263                     }
264                 } else if (QALabTags.PATH_ATTRIBUTE.equals(name)) {
265                     path = value;
266                 }
267             }
268         }
269     }
270 
271     /**
272      * Process the attributes of each result and return a SingleStat object
273      * built from the attributes date, type and statvalue.
274      * 
275      * @param attrs
276      *            XML attributes
277      * @return new SingleStat object built from the attributes.
278      */
279     private SingleStat buildResultBean(final Attributes attrs) {
280         SingleStat singleStat = new SingleStat();
281 
282         if (attrs != null) {
283             for (int i = 0; i < attrs.getLength(); i++) {
284                 String name = attrs.getQName(i); // Attr name
285                 String value = attrs.getValue(i);
286 
287                 if (QALabTags.TYPE_ATTRIBUTE.equals(name)) {
288                     singleStat.setType(value);
289                 } else if (QALabTags.STATVALUE_ATTRIBUTE.equals(name)) {
290                     singleStat.setStatValue(Integer.parseInt(value));
291                 } else if (QALabTags.DATE_ATTRIBUTE.equals(name)) {
292                     try {
293                         if (value != null
294                                 && value.length() > QALabTags.DATE_ONLY_SIZE) {
295                             singleStat
296                                     .setDate(QALabTags.DEFAULT_DATETIME_FORMAT
297                                             .parse(value));
298                         } else if (value != null
299                                 && value.length() == QALabTags.DATE_ONLY_SIZE) {
300                             singleStat.setDate(QALabTags.DEFAULT_DATE_FORMAT
301                                     .parse(value));
302                         }
303                     } catch (ParseException e) {
304                         tasklogger.log(e.toString());
305                     }
306                 }
307             }
308         }
309 
310         return singleStat;
311     }
312 
313     // ===========================================================
314     // SAX DocumentHandler methods
315     // ===========================================================
316 
317     /**
318      * At the start of a new element, capture the filename and if the element is
319      * a result one, create a SingleStat to store for the given type.
320      * 
321      * @param ignoreNamespaceURI
322      *            ignore (present for interface implementation).
323      * @param localname
324      *            name of the current element.
325      * @param qualifiedname
326      *            element name.
327      * @param attrs
328      *            the XML attribute of the current element.
329      * @throws SAXException
330      *             any SAX issue
331      */
332     public final void startElement(final String ignoreNamespaceURI, // NOPMD
333             final String localname, final String qualifiedname,
334             final Attributes attrs) throws SAXException {
335         String local = localname;
336         if ("".equals(local)) {
337             local = qualifiedname;
338         }
339 
340         // if (!quiet) {
341         // tasklogger.log("Start :" + qualifiedname);
342         // }
343 
344         if (QALabTags.FILE_TAG.equals(local)) {
345             getFileName(attrs);
346         }
347 
348         if (QALabTags.RESULT_TAG.equals(local)) {
349             SingleStat resultbean = buildResultBean(attrs);
350             Stack s = (Stack) mapOfTypes.get(resultbean.getType());
351 
352             // only push items that are within the time window.
353             if (s != null) {
354                 s.push(resultbean);
355             }
356         }
357     }
358 
359     /**
360      * At the end of an element, check if it is a file one and add the results
361      * found.
362      * 
363      * @param ignoreNamespaceURI
364      *            ignore (present for interface implementation).
365      * @param ignoreSimplename
366      *            ignore (present for interface implementation).
367      * @param qualifiedname
368      *            the name of the element.
369      * @throws SAXException
370      *             any SAX issue
371      */
372     public final void endElement(final String ignoreNamespaceURI, // NOPMD
373             final String ignoreSimplename, final String qualifiedname) // NOPMD
374             throws SAXException {
375         if (QALabTags.FILE_TAG.equals(qualifiedname)) {
376             fileResult = new FileStats(mapOfTypes, filename, path,
377                     startDateCutoff);
378             fileResult.setEndDateCutoff(endDateCutoff);
379             fileResult.setOffsetForLastRun(hoursOffsetForLastRun);
380             filebeans.add(fileResult);
381             mapOfTypes = new HashMap();
382             processTypes(listOfTypes);
383         }
384     }
385 
386     /**
387      * Return the input source to the base stats.
388      * 
389      * @return InputSource typically the source to qalab.xml
390      */
391     public final InputSource getQalabSource() {
392         return qalabSource;
393     }
394 
395     /**
396      * At the end of parsing the qalab.xml document, generate the up/down
397      * report.
398      * 
399      * @throws SAXException
400      *             any SAX issue.
401      */
402     public final void endDocument() throws SAXException {
403         try {
404             // now write the results
405             outputWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
406             outputWriter.write(QALabTags.LINE_END);
407             outputWriter.write("<!DOCTYPE moversreport [");
408             outputWriter.write(QALabTags.LINE_END);
409             outputWriter.write("<!ELEMENT moversreport (types,datethreshold,"
410                     + "enddatethreshold,daterun,up?,down?)>");
411             outputWriter.write(QALabTags.LINE_END);
412             outputWriter.write("<!ELEMENT types (#PCDATA)>");
413             outputWriter.write(QALabTags.LINE_END);
414             outputWriter.write("<!ELEMENT datethreshold (#PCDATA)>");
415             outputWriter.write(QALabTags.LINE_END);
416             outputWriter.write("<!ELEMENT enddatethreshold (#PCDATA)>");
417             outputWriter.write(QALabTags.LINE_END);
418             outputWriter.write("<!ELEMENT daterun (#PCDATA)>");
419             outputWriter.write(QALabTags.LINE_END);
420             outputWriter.write("<!ELEMENT up (file+)>");
421             outputWriter.write(QALabTags.LINE_END);
422             outputWriter.write("<!ELEMENT down (file+)>");
423             outputWriter.write(QALabTags.LINE_END);
424             outputWriter.write("<!ELEMENT file (diff+)>");
425             outputWriter.write(QALabTags.LINE_END);
426             outputWriter.write("<!ATTLIST file name CDATA #REQUIRED>");
427             outputWriter.write(QALabTags.LINE_END);
428             outputWriter.write("<!ATTLIST file path CDATA #REQUIRED>");
429             outputWriter.write(QALabTags.LINE_END);
430             outputWriter.write("<!ELEMENT diff EMPTY>");
431             outputWriter.write(QALabTags.LINE_END);
432             outputWriter.write("<!ATTLIST diff previousrun CDATA #REQUIRED>");
433             outputWriter.write(QALabTags.LINE_END);
434             outputWriter.write("<!ATTLIST diff currentrun CDATA #REQUIRED>");
435             outputWriter.write(QALabTags.LINE_END);
436             outputWriter.write("<!ATTLIST diff type CDATA #REQUIRED>");
437             outputWriter.write(QALabTags.LINE_END);
438             outputWriter.write("<!ATTLIST diff previouserrors "
439                     + "CDATA #REQUIRED>");
440             outputWriter.write(QALabTags.LINE_END);
441             outputWriter.write("<!ATTLIST diff currenterrors CDATA "
442                     + "#REQUIRED>");
443             outputWriter.write(QALabTags.LINE_END);
444             outputWriter.write("<!ATTLIST diff diff CDATA #REQUIRED>");
445             outputWriter.write(QALabTags.LINE_END);
446             outputWriter.write("]>");
447             outputWriter.write(QALabTags.LINE_END);
448             outputWriter.write("<moversreport>");
449             outputWriter.write(QALabTags.LINE_END);
450             outputWriter.write(" <types>");
451             outputWriter.write(Util.listToCSVString(listOfTypes));
452             outputWriter.write("</types>");
453             outputWriter.write(QALabTags.LINE_END);
454             outputWriter.write(" <datethreshold>");
455             outputWriter.write(QALabTags.DEFAULT_DATETIME_FORMAT
456                     .format(startDateCutoff));
457             outputWriter.write("</datethreshold>");
458             outputWriter.write(QALabTags.LINE_END);
459             outputWriter.write(" <enddatethreshold>");
460             outputWriter.write(QALabTags.DEFAULT_DATETIME_FORMAT
461                     .format(endDateCutoff));
462             outputWriter.write("</enddatethreshold>");
463             outputWriter.write(QALabTags.LINE_END);
464             outputWriter.write(" <daterun>");
465             outputWriter.write(QALabTags.DEFAULT_DATETIME_FORMAT
466                     .format(new Date()));
467             outputWriter.write("</daterun>");
468             outputWriter.write(QALabTags.LINE_END);
469             outputWriter.write(" <up>");
470             outputWriter.write(QALabTags.LINE_END);
471 
472             for (Iterator it = filebeans.iterator(); it.hasNext();) {
473                 FileStats fb = (FileStats) it.next();
474 
475                 fb.constructXML();
476                 outputWriter.write(fb.getUpXmlResult());
477             }
478 
479             outputWriter.write(" </up>");
480             outputWriter.write(QALabTags.LINE_END);
481             outputWriter.write(" <down>");
482             outputWriter.write(QALabTags.LINE_END);
483 
484             for (Iterator it = filebeans.iterator(); it.hasNext();) {
485                 FileStats fb = (FileStats) it.next();
486 
487                 outputWriter.write(fb.getDownXmlResult());
488             }
489 
490             outputWriter.write(" </down>");
491             outputWriter.write(QALabTags.LINE_END);
492             outputWriter.write("</moversreport>");
493 
494             outputWriter.flush();
495         } catch (IOException e) {
496             tasklogger.log(e.toString());
497         } finally {
498             if (outputWriter != null) {
499                 try {
500                     outputWriter.close();
501                 } catch (IOException e) {
502                     tasklogger.log(e.toString());
503                 }
504             }
505         }
506     }
507 
508     /**
509      * calculates the datecutoff by adding the number of offset hours.
510      * 
511      * @param offsetInHours
512      *            number of hours from now.
513      */
514     public final void setDatecutoff(final int offsetInHours) {
515         Calendar cal = Calendar.getInstance();
516 
517         cal.add(Calendar.HOUR, offsetInHours);
518         startDateCutoff = cal.getTime();
519     }
520 
521     /**
522      * Calculate the cutoff date, ie the earliest date for the stat to be taken
523      * into account. extra 48h if original cutoff date falls on a Saturday or
524      * Sunday.
525      */
526     public final void calculateCutoffDateTime() {
527         if (startTimeWindow != null) {
528             startDateCutoff = startTimeWindow;
529         } else {
530             // always do the cut off
531             int offset = -Integer.parseInt(offsetHours);
532 
533             setDatecutoff(offset);
534         }
535 
536         if (wkendadj) {
537             // check if weekend:
538             Calendar cal = Calendar.getInstance();
539 
540             cal.setTime(startDateCutoff);
541 
542             final int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
543 
544             if ((dayOfWeek == Calendar.SUNDAY)
545                     || (dayOfWeek == Calendar.SATURDAY)) {
546                 cal.add(Calendar.HOUR, -WEEKEND_SKIP);
547                 startDateCutoff = cal.getTime();
548             }
549         }
550 
551         if (!quiet) {
552             tasklogger.log("Cutoff = " + startDateCutoff);
553         }
554 
555         if (endTimeWindow != null) {
556             endDateCutoff = endTimeWindow;
557         } else {
558             endDateCutoff = new Date();
559         }
560     }
561 
562     /**
563      * @param log
564      *            the logger to use.
565      */
566     private void setLogger(final TaskLogger log) {
567         this.tasklogger = log;
568     }
569 
570     /**
571      * @param endTime
572      *            The endTimeWindow to set.
573      */
574     public final void setEndTimeWindow(final Date endTime) {
575         this.endTimeWindow = new Date(endTime.getTime());
576     }
577 
578     /**
579      * @param startTime
580      *            The startTimeWindow to set.
581      */
582     public final void setStartTimeWindow(final Date startTime) {
583         this.startTimeWindow = new Date(startTime.getTime());
584     }
585 
586     /**
587      * @param offset
588      *            The offsetHours to set.
589      */
590     public final void setOffsetHours(final String offset) {
591         this.offsetHours = offset;
592     }
593 
594     /**
595      * @return Returns the offsetHours.
596      */
597     public final String getOffsetHours() {
598         return offsetHours;
599     }
600 
601     public int getHoursOffsetForLastRun() {
602         return hoursOffsetForLastRun;
603     }
604 
605     public void setHoursOffsetForLastRun(int hoursOffsetForLastRun) {
606         this.hoursOffsetForLastRun = hoursOffsetForLastRun;
607     }
608 }
609 /*
610  *                   ObjectLab is sponsoring QALab
611  * 
612  * Based in London, we are world leaders in the design and development 
613  * of bespoke applications for the securities financing markets.
614  * 
615  * <a href="http://www.objectlab.co.uk/open">Click here to learn more about us</a>
616  *           ___  _     _           _   _          _
617  *          / _ \| |__ (_) ___  ___| |_| |    __ _| |__
618  *         | | | | '_ \| |/ _ \/ __| __| |   / _` | '_ \
619  *         | |_| | |_) | |  __/ (__| |_| |__| (_| | |_) |
620  *          \___/|_.__// |\___|\___|\__|_____\__,_|_.__/
621  *                   |__/
622  *
623  *                     www.ObjectLab.co.uk
624  */