View Javadoc

1   package uk.co.concise.maven;
2   
3   import java.io.File;
4   import java.io.FileInputStream;
5   import java.io.FileNotFoundException;
6   import java.io.IOException;
7   import java.util.Date;
8   
9   import javax.xml.parsers.DocumentBuilderFactory;
10  import javax.xml.parsers.FactoryConfigurationError;
11  import javax.xml.parsers.ParserConfigurationException;
12  import javax.xml.transform.OutputKeys;
13  import javax.xml.transform.Transformer;
14  import javax.xml.transform.TransformerConfigurationException;
15  import javax.xml.transform.TransformerException;
16  import javax.xml.transform.TransformerFactory;
17  import javax.xml.transform.TransformerFactoryConfigurationError;
18  
19  import org.apache.commons.logging.Log;
20  import org.apache.commons.logging.LogFactory;
21  import org.apache.xpath.XPathAPI;
22  import org.w3c.dom.Document;
23  import org.w3c.dom.Node;
24  import org.w3c.dom.traversal.NodeIterator;
25  import org.xml.sax.InputSource;
26  import org.xml.sax.SAXException;
27  
28  import uk.co.concise.maven.hdc.HibernatePlugin;
29  import uk.co.concise.maven.hdc.dao.ChartDao;
30  import uk.co.concise.maven.hdc.dao.LabelDao;
31  import uk.co.concise.maven.hdc.dao.PointDao;
32  import uk.co.concise.maven.hdc.model.Chart;
33  import uk.co.concise.maven.hdc.model.Label;
34  import uk.co.concise.maven.hdc.model.Point;
35  
36  /***
37   * Collects historical data from build result files.
38   * @author martenssonb
39   */
40  public class HistoricalDataCollector extends HibernatePlugin {
41  
42      private static final Log LOG = LogFactory
43              .getLog(HistoricalDataCollector.class);
44  
45      private String buildResultFile;
46  
47      private String valuesToTrackXPath;
48  
49      private String labelsToTrackXPath;
50  
51      private String heading;
52  
53      /***
54       * Called from Maven.
55       * @throws Exception
56       *             for Maven to handle.
57       */
58      public void run() throws Exception {
59          super.configure();
60  
61          // Check if the chart already exists in the DB.
62          ChartDao chartDao = new ChartDao();
63          Chart chart = chartDao.findByHeading(getHeading());
64          if (chart == null) {
65              chart = new Chart();
66              chart.setHeading(getHeading());
67              chartDao.saveOrUpdateChart(chart);
68          }
69  
70          NodeIterator valueNodes = findNodes(getValuesToTrackXPath());
71          NodeIterator labelNodes = findNodes(getLabelsToTrackXPath());
72          // Go through the found nodes.
73          Node oneValueNode;
74          LabelDao labelDao = new LabelDao();
75          PointDao pointDao = new PointDao();
76          Date now = new Date();
77          while ((oneValueNode = valueNodes.nextNode()) != null) {
78              Node oneLabelNode = labelNodes.nextNode();
79              if (oneLabelNode == null) {
80                  throw new RuntimeException(
81                          "There were fewer label nodes than value nodes.");
82              }
83  
84              String labelText = extractText(oneLabelNode);
85  
86              // Check if the Label already existed.
87              Label label = labelDao.findByChartAndText(chart, labelText);
88              if (label == null) {
89                  label = new Label();
90                  label.setChart(chart);
91                  chart.getLabels().add(label);
92                  label.setText(labelText);
93                  labelDao.saveOrUpdateLabel(label);
94                  chartDao.saveOrUpdateChart(chart);
95              }
96  
97              String valueText = extractText(oneValueNode);
98  
99              // Create a new point.
100             Point point = new Point();
101             point.setLabel(label);
102             label.getPoints().add(point);
103             point.setTime(now);
104             try {
105                 point.setValue(parseDouble(valueText));
106 
107                 pointDao.saveOrUpdatePoint(point);
108                 labelDao.saveOrUpdateLabel(label);
109                 chartDao.saveOrUpdateChart(chart);
110             } catch (NumberFormatException e) {
111                 System.out.println("Label: " + labelText
112                         + " didn't have a valid value (" + valueText + ")");
113             }
114 
115         }
116 
117     }
118 
119     /***
120      * Parses a value text and returns its double value. If the value ends
121      * with a % sign, then the value is divided by 100.
122      * @param valueText the text containing a double.
123      * @return the double value of the valueText.
124      * @throws NumberFormatException if the text couldn't be parsed 
125      * into a double.
126      */
127     private double parseDouble(String valueText) {
128         // remove % sign
129         if (valueText.endsWith("%")) {
130             valueText = valueText.substring(0, valueText.length() - 1);
131             final double centsPerWhole = 100;
132             return Double.parseDouble(valueText) / centsPerWhole;
133         }
134         return Double.parseDouble(valueText);
135     }
136 
137     /***
138      * Extracts the text from a text node.
139      * @param n
140      *            the node to extract the text from.
141      * @return the extracted text.
142      */
143     private String extractText(Node n) {
144         String nodeText = null;
145         if (isTextNode(n)) {
146             // DOM may have more than one node corresponding to a
147             // single XPath text node. Coalesce all contiguous text nodes
148             // at this level
149             StringBuffer sb = new StringBuffer(n.getNodeValue());
150             for (Node nn = n.getNextSibling(); isTextNode(nn); nn = nn
151                     .getNextSibling()) {
152                 sb.append(nn.getNodeValue());
153             }
154             nodeText = sb.toString();
155         } else {
156             throw new RuntimeException(
157                     "One of the nodes is not a text node. (type="
158                             + n.getNodeType() + ")");
159         }
160         return nodeText;
161     }
162 
163     /***
164      * Sets up for XPath etc. and locates the nodes containing values.
165      * @param xpath
166      *            the path to find nodes for.
167      * @return
168      * @throws FileNotFoundException
169      * @throws FactoryConfigurationError
170      * @throws SAXException
171      * @throws IOException
172      * @throws ParserConfigurationException
173      * @throws TransformerConfigurationException
174      * @throws TransformerFactoryConfigurationError
175      * @throws TransformerException
176      */
177     private NodeIterator findNodes(String xpath) throws FileNotFoundException,
178             FactoryConfigurationError, SAXException, IOException,
179             ParserConfigurationException, TransformerConfigurationException,
180             TransformerFactoryConfigurationError, TransformerException {
181         InputSource in = getInputSource();
182         DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
183         dfactory.setNamespaceAware(true);
184         Document doc = dfactory.newDocumentBuilder().parse(in);
185 
186         // Set up an identity transformer to use as serializer.
187         Transformer serializer = TransformerFactory.newInstance()
188                 .newTransformer();
189         serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
190 
191         // Use the simple XPath API to select a nodeIterator.
192         System.out.println("Querying DOM using " + xpath);
193         NodeIterator nl = XPathAPI.selectNodeIterator(doc, xpath);
194         return nl;
195     }
196 
197     /***
198      * Creates an InputSource.
199      * @return an InputSource.
200      * @throws FileNotFoundException
201      */
202     private InputSource getInputSource() throws FileNotFoundException {
203         LOG.info("Collecting data from file \"" + getBuildResultFile() + "\".");
204         // Set up a DOM tree to query.
205         File resultsFile = new File(getBuildResultFile());
206         if (!resultsFile.exists()) {
207             throw new RuntimeException("The file \"" + getBuildResultFile()
208                     + "\" doesn't exist.");
209         }
210         InputSource in = new InputSource(new FileInputStream(resultsFile));
211         return in;
212     }
213 
214     /***
215      * Returns the file that contains build results to be collected.
216      * @return Returns the buildResultFile.
217      */
218     public String getBuildResultFile() {
219         return buildResultFile;
220     }
221 
222     /***
223      * Sets the file that contains build results to be collected.
224      * @param buildResultFile
225      *            The buildResultFile to set.
226      */
227     public void setBuildResultFile(String buildResultFile) {
228         this.buildResultFile = buildResultFile;
229     }
230 
231     /***
232      * Decides if the node is text, and so must be handled specially.
233      */
234     private static boolean isTextNode(Node n) {
235         if (n == null) {
236             return false;
237         }
238         short nodeType = n.getNodeType();
239         return nodeType == Node.CDATA_SECTION_NODE
240                 || nodeType == Node.TEXT_NODE
241                 || nodeType == Node.ATTRIBUTE_NODE;
242     }
243 
244     /***
245      * Returns the XPath to the value that we are tracking over time.
246      * 
247      * This should be an XPath to one or more text nodes containing the value
248      * that should be tracked over time.
249      * 
250      * @return Returns the valueToTrackXPath.
251      */
252     public String getValuesToTrackXPath() {
253         return valuesToTrackXPath;
254     }
255 
256     /***
257      * Sets the XPath to the value that we are tracking over time.
258      * 
259      * This should be an XPath to one or more text nodes containing the value
260      * that should be tracked over time.
261      * 
262      * @param valueToTrackXPath
263      *            The valueToTrackXPath to set.
264      */
265     public void setValuesToTrackXPath(String valueToTrackXPath) {
266         this.valuesToTrackXPath = valueToTrackXPath;
267     }
268 
269     /***
270      * The heading for the chart.
271      * @return Returns the heading.
272      */
273     public String getHeading() {
274         return heading;
275     }
276 
277     /***
278      * The heading for the chart.
279      * @param heading
280      *            The heading to set.
281      */
282     public void setHeading(String heading) {
283         this.heading = heading;
284     }
285 
286     /***
287      * The XPath to the labels of the values that we track over time.
288      * @return Returns the labelsToTrackXPath.
289      */
290     public String getLabelsToTrackXPath() {
291         return labelsToTrackXPath;
292     }
293 
294     /***
295      * The XPath to the labels of the values that we track over time.
296      * @param labelsToTrackXPath
297      *            The labelsToTrackXPath to set.
298      */
299     public void setLabelsToTrackXPath(String labelsToTrackXPath) {
300         this.labelsToTrackXPath = labelsToTrackXPath;
301     }
302 }