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
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
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
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
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
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
147
148
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
187 Transformer serializer = TransformerFactory.newInstance()
188 .newTransformer();
189 serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
190
191
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
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 }