View Javadoc

1   /* ============================================================
2    * JRobin : Pure java implementation of RRDTool's functionality
3    * ============================================================
4    *
5    * Project Info:  http://www.jrobin.org
6    * Project Lead:  Sasa Markovic (saxon@jrobin.org);
7    *
8    * (C) Copyright 2003-2005, by Sasa Markovic.
9    *
10   * Developers:    Sasa Markovic (saxon@jrobin.org)
11   *
12   *
13   * This library is free software; you can redistribute it and/or modify it under the terms
14   * of the GNU Lesser General Public License as published by the Free Software Foundation;
15   * either version 2.1 of the License, or (at your option) any later version.
16   *
17   * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
18   * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19   * See the GNU Lesser General Public License for more details.
20   *
21   * You should have received a copy of the GNU Lesser General Public License along with this
22   * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
23   * Boston, MA 02111-1307, USA.
24   */
25  package org.jrobin.graph;
26  
27  import org.jrobin.core.RrdException;
28  import org.jrobin.core.Util;
29  import org.jrobin.core.XmlTemplate;
30  import org.w3c.dom.Node;
31  import org.xml.sax.InputSource;
32  
33  import java.awt.*;
34  import java.io.File;
35  import java.io.IOException;
36  
37  /**
38   * Class used to create an arbitrary number of RrdGraphDef (graph definition) objects
39   * from a single XML template. XML template can be supplied as an XML InputSource,
40   * XML file or XML formatted string.<p>
41   * <p/>
42   * Here is an example of a properly formatted XML template with all available options in it
43   * (unwanted options can be removed/ignored):<p>
44   * <pre>
45   * &lt;rrd_graph_def&gt;
46   *     &lt;!-- use '-' to represent in-memory graph --&gt;
47   *     &lt;filename&gt;test.png&lt;/filename&gt;
48   *     &lt;!--
49   *         starting and ending timestamps can be specified by
50   *         using at-style time specification, or by specifying
51   *         exact timestamps since epoch (without milliseconds)
52   *     --&gt;
53   *     &lt;span&gt;
54   *         &lt;start&gt;now - 1d&lt;/start&gt;
55   *         &lt;end&gt;now&lt;/end&gt;
56   *     &lt;/span&gt;
57   *     &lt;options&gt;
58   *         &lt;!--
59   *             specify 'true' if you want to use RrdDbPool while
60   *             creating graph
61   *         --&gt;
62   *         &lt;use_pool&gt;false&lt;/use_pool&gt;
63   *         &lt;anti_aliasing&gt;true&lt;/anti_aliasing&gt;
64   *         &lt;time_grid&gt;
65   *             &lt;show_grid&gt;true&lt;/show_grid&gt;
66   *             &lt;!-- allowed units: second, minute, hour, day, week, month, year --&gt;
67   *             &lt;minor_grid_unit&gt;minute&lt;/minor_grid_unit&gt;
68   *             &lt;minor_grid_unit_count&gt;60&lt;/minor_grid_unit_count&gt;
69   *             &lt;major_grid_unit&gt;hour&lt;/major_grid_unit&gt;
70   *             &lt;major_grid_unit_count&gt;2&lt;/major_grid_unit_count&gt;
71   *             &lt;label_unit&gt;hour&lt;/label_unit&gt;
72   *             &lt;label_unit_count&gt;2&lt;/label_unit_count&gt;
73   *             &lt;label_span&gt;1200&lt;/label_span&gt;
74   *             &lt;!-- use SimpleDateFormat or strftime-like format to format labels --&gt;
75   *             &lt;label_format&gt;dd-MMM-yy&lt;/label_format&gt;
76   *         &lt;/time_grid&gt;
77   *         &lt;value_grid&gt;
78   *             &lt;show_grid&gt;true&lt;/show_grid&gt;
79   *             &lt;grid_step&gt;100.0&lt;/grid_step&gt;
80   *             &lt;label_factor&gt;5&lt;/label_factor&gt;
81   *         &lt;/value_grid&gt;
82   *         &lt;no_minor_grid&gt;true&lt;/no_minor_grid&gt;
83   *         &lt;alt_y_grid&gt;true&lt;/alt_y_grid&gt;
84   *         &lt;alt_y_mrtg&gt;true&lt;/alt_y_mrtg&gt;
85   *         &lt;alt_autoscale&gt;true&lt;/alt_autoscale&gt;
86   *         &lt;alt_autoscale_max&gt;true&lt;/alt_autoscale_max&gt;
87   *         &lt;units_exponent&gt;3&lt;/units_exponent&gt;
88   *         &lt;units_length&gt;13&lt;/units_length&gt;
89   *         &lt;vertical_label&gt;Speed (kbits/sec)&lt;/vertical_label&gt;
90   *         &lt;width&gt;444&lt;/width&gt;
91   *         &lt;height&gt;222&lt;/height&gt;
92   *         &lt;interlaced&gt;true&lt;/interlaced&gt;
93   *         &lt;image_info&gt;filename = %s, width=%d, height=%d&lt;/image_info&gt;
94   *         &lt;image_format&gt;png&lt;/image_format&gt;
95   *         &lt;image_quality&gt;0.8&lt;/image_quality&gt;
96   *         &lt;background_image&gt;luka.png&lt;/background_image&gt;
97   *         &lt;overlay_image&gt;luka.png&lt;/overlay_image&gt;
98   *         &lt;unit&gt;kilos&lt;/unit&gt;
99   *         &lt;lazy&gt;false&lt;/lazy&gt;
100  *         &lt;min_value&gt;0&lt;/min_value&gt;
101  *         &lt;max_value&gt;5000&lt;/max_value&gt;
102  *         &lt;rigid&gt;true&lt;/rigid&gt;
103  *         &lt;base&gt;1000&lt;/base&gt;
104  *         &lt;logarithmic&gt;false&lt;/logarithmic&gt;
105  *         &lt;colors&gt;
106  *             &lt;canvas&gt;#FFFFFF&lt;/canvas&gt;
107  *             &lt;back&gt;#FFFFFF&lt;/back&gt;
108  *             &lt;shadea&gt;#AABBCC&lt;/shadea&gt;
109  *             &lt;shadeb&gt;#DDDDDD&lt;/shadeb&gt;
110  *             &lt;grid&gt;#FF0000&lt;/grid&gt;
111  *             &lt;mgrid&gt;#00FF00&lt;/mgrid&gt;
112  *             &lt;font&gt;#FFFFFF&lt;/font&gt;
113  *             &lt;frame&gt;#EE00FF&lt;/frame&gt;
114  *             &lt;arrow&gt;#FF0000&lt;/arrow&gt;
115  *         &lt;/colors&gt;
116  *         &lt;no_legend&gt;false&lt;/no_legend&gt;
117  *         &lt;only_graph&gt;false&lt;/only_graph&gt;
118  *         &lt;force_rules_legend&gt;false&lt;/force_rules_legend&gt;
119  *         &lt;title&gt;This is a title&lt;/title&gt;
120  *         &lt;step&gt;300&lt;/step&gt;
121  *         &lt;fonts&gt;
122  *             &lt;small_font&gt;
123  *                 &lt;name&gt;Courier&lt;/name&gt;
124  *                 &lt;style&gt;bold italic&lt;/style&gt;
125  *                 &lt;size&gt;12&lt;/size&gt;
126  *             &lt;/small_font&gt;
127  *             &lt;large_font&gt;
128  *                 &lt;name&gt;Courier&lt;/name&gt;
129  *                 &lt;style&gt;plain&lt;/style&gt;
130  *                 &lt;size&gt;11&lt;/size&gt;
131  *             &lt;/large_font&gt;
132  *         &lt;/fonts&gt;
133  *         &lt;first_day_of_week&gt;SUNDAY&lt;/first_day_of_week&gt;
134  *     &lt;/options&gt;
135  *     &lt;datasources&gt;
136  *         &lt;def&gt;
137  *             &lt;name&gt;x&lt;/name&gt;
138  *             &lt;rrd&gt;test.rrd&lt;/rrd&gt;
139  *             &lt;source&gt;sun&lt;/source&gt;
140  *             &lt;cf&gt;AVERAGE&lt;/cf&gt;
141  *             &lt;backend&gt;FILE&lt;/backend&gt;
142  *         &lt;/def&gt;
143  *         &lt;def&gt;
144  *             &lt;name&gt;y&lt;/name&gt;
145  *             &lt;rrd&gt;test.rrd&lt;/rrd&gt;
146  *             &lt;source&gt;shade&lt;/source&gt;
147  *             &lt;cf&gt;AVERAGE&lt;/cf&gt;
148  *         &lt;/def&gt;
149  *         &lt;cdef&gt;
150  *             &lt;name&gt;x_plus_y&lt;/name&gt;
151  *             &lt;rpn&gt;x,y,+&lt;/rpn&gt;
152  *         &lt;/cdef&gt;
153  *         &lt;cdef&gt;
154  *             &lt;name&gt;x_minus_y&lt;/name&gt;
155  *             &lt;rpn&gt;x,y,-&lt;/rpn&gt;
156  *         &lt;/cdef&gt;
157  *         &lt;sdef&gt;
158  *             &lt;name&gt;x_avg&lt;/name&gt;
159  *             &lt;source&gt;x&lt;/source&gt;
160  *             &lt;cf&gt;AVERAGE&lt;/cf&gt;
161  *         &lt;/sdef&gt;
162  *         &lt;sdef&gt;
163  *             &lt;name&gt;y_max&lt;/name&gt;
164  *             &lt;source&gt;y&lt;/source&gt;
165  *             &lt;cf&gt;MAX&lt;/cf&gt;
166  *         &lt;/sdef&gt;
167  *     &lt;/datasources&gt;
168  *     &lt;graph&gt;
169  *         &lt;area&gt;
170  *             &lt;datasource&gt;x&lt;/datasource&gt;
171  *             &lt;color&gt;#FF0000&lt;/color&gt;
172  *             &lt;legend&gt;X value\r&lt;/legend&gt;
173  *         &lt;/area&gt;
174  *         &lt;stack&gt;
175  *             &lt;datasource&gt;y&lt;/datasource&gt;
176  *             &lt;color&gt;#00FF00&lt;/color&gt;
177  *             &lt;legend&gt;Y value\r&lt;/legend&gt;
178  *         &lt;/stack&gt;
179  *         &lt;line&gt;
180  *             &lt;datasource&gt;x&lt;/datasource&gt;
181  *             &lt;color&gt;#FF0000&lt;/color&gt;
182  *             &lt;legend&gt;X value\r&lt;/legend&gt;
183  *             &lt;width&gt;2&lt;/width&gt;
184  *         &lt;/line&gt;
185  *         &lt;print&gt;
186  *             &lt;datasource&gt;x&lt;/datasource&gt;
187  *             &lt;cf&gt;AVERAGE&lt;/cf&gt;
188  *             &lt;format&gt;Average is %7.3f\c&lt;/format&gt;
189  *         &lt;/print&gt;
190  *         &lt;gprint&gt;
191  *             &lt;datasource&gt;y&lt;/datasource&gt;
192  *             &lt;cf&gt;MAX&lt;/cf&gt;
193  *             &lt;format&gt;Max is %7.3f\c&lt;/format&gt;
194  *         &lt;/gprint&gt;
195  *         &lt;hrule&gt;
196  *             &lt;value&gt;1250&lt;/value&gt;
197  *             &lt;color&gt;#0000FF&lt;/color&gt;
198  *             &lt;legend&gt;This is a horizontal rule&lt;/legend&gt;
199  *         &lt;/hrule&gt;
200  *         &lt;vrule&gt;
201  *             &lt;time&gt;now-6h&lt;/time&gt;
202  *             &lt;color&gt;#0000FF&lt;/color&gt;
203  *             &lt;legend&gt;This is a vertical rule&lt;/legend&gt;
204  *         &lt;/vrule&gt;
205  *         &lt;comment&gt;Simple comment&lt;/comment&gt;
206  *         &lt;comment&gt;One more comment\c&lt;/comment&gt;
207  *     &lt;/graph&gt;
208  * &lt;/rrd_graph_def&gt;
209  * </pre>
210  * Notes on the template syntax:<p>
211  * <ul>
212  * <li>There is a strong relation between the XML template syntax and the syntax of
213  * {@link RrdGraphDef} class methods. If you are not sure what some XML tag means, check javadoc
214  * for the corresponding class method.
215  * <li>hard-coded timestamps in templates should be long integeres
216  * (like: 1000243567) or at-style formatted strings
217  * <li>whitespaces are not harmful
218  * <li>use <code>true</code>, <code>on</code>, <code>yes</code>, <code>y</code>,
219  * or <code>1</code> to specify boolean <code>true</code> value (anything else will
220  * be treated as <code>false</code>).
221  * <li>floating point values: anything that cannot be parsed will be treated as Double.NaN
222  * (like: U, unknown, 12r.23)
223  * <li>use #RRGGBB or #RRGGBBAA format to specify colors.
224  * <li>valid font styles are: PLAIN, ITALIC, BOLD, BOLDITALIC
225  * <li>comments are allowed.
226  * </ul>
227  * Any template value (text between <code>&lt;some_tag&gt;</code> and
228  * <code>&lt;/some_tag&gt;</code>) can be replaced with
229  * a variable of the following form: <code>${variable_name}</code>. Use
230  * {@link XmlTemplate#setVariable(String, String) setVariable()}
231  * methods from the base class to replace
232  * template variables with real values at runtime.<p>
233  * <p/>
234  * Typical usage scenario:<p>
235  * <ul>
236  * <li>Create your XML template and save it to a file (template.xml, for example)
237  * <li>Replace template values with variables if you want to change them during runtime.
238  * For example, time span should not be hard-coded in the template - you probably want to create
239  * many different graphs with different time spans from the same XML template.
240  * For example, your XML template could start with:
241  * <pre>
242  * &lt;rrd_graph_def&gt;
243  *     ...
244  *     &lt;span&gt;
245  *         &lt;start&gt;${start}&lt;/start&gt;
246  *         &lt;end&gt;${end}&lt;/end&gt;
247  *     &lt;/span&gt;
248  *     ...
249  * </pre>
250  * <li>In your Java code, create RrdGraphDefTemplate object using your XML template file:
251  * <pre>
252  * RrdGraphDefTemplate t = new RrdGraphDefTemplate(new File(template.xml));
253  * </pre>
254  * <li>Then, specify real values for template variables:
255  * <pre>
256  * t.setVariable("start", new GregorianCalendar(2004, 2, 25));
257  * t.setVariable("end", new GregorianCalendar(2004, 2, 26));
258  * </pre>
259  * <li>Once all template variables are set, just use the template object to create RrdGraphDef
260  * object. This object is actually used to create JRobin grahps:
261  * <pre>
262  * RrdGraphDef gdef = t.getRrdGraphDef();
263  * RrdGraph g = new RrdGraph(gdef);
264  * </pre>
265  * </ul>
266  * You should create new RrdGraphDefTemplate object only once for each XML template. Single template
267  * object can be reused to create as many RrdGraphDef objects as needed, with different values
268  * specified for template variables. XML synatax check is performed only once - the first graph
269  * definition object gets created relatively slowly, but it will be created much faster next time.
270  */
271 public class RrdGraphDefTemplate extends XmlTemplate implements RrdGraphConstants {
272 	static final Color BLIND_COLOR = new Color(0, 0, 0, 0);
273 
274 	private RrdGraphDef rrdGraphDef;
275 
276 	/**
277 	 * Creates template object from any parsable XML source
278 	 *
279 	 * @param inputSource XML source
280 	 * @throws IOException  thrown in case of I/O error
281 	 * @throws RrdException usually thrown in case of XML related error
282 	 */
283 	public RrdGraphDefTemplate(InputSource inputSource) throws IOException, RrdException {
284 		super(inputSource);
285 	}
286 
287 	/**
288 	 * Creates template object from the file containing XML template code
289 	 *
290 	 * @param xmlFile file containing XML template
291 	 * @throws IOException  thrown in case of I/O error
292 	 * @throws RrdException usually thrown in case of XML related error
293 	 */
294 	public RrdGraphDefTemplate(File xmlFile) throws IOException, RrdException {
295 		super(xmlFile);
296 	}
297 
298 	/**
299 	 * Creates template object from the string containing XML template code
300 	 *
301 	 * @param xmlString string containing XML template
302 	 * @throws IOException  thrown in case of I/O error
303 	 * @throws RrdException usually thrown in case of XML related error
304 	 */
305 	public RrdGraphDefTemplate(String xmlString) throws IOException, RrdException {
306 		super(xmlString);
307 	}
308 
309 	/**
310 	 * Creates RrdGraphDef object which can be used to create RrdGraph
311 	 * object (actual JRobin graphs). Before this method is called, all template variables (if any)
312 	 * must be resolved (replaced with real values).
313 	 * See {@link XmlTemplate#setVariable(String, String) setVariable()} method information to
314 	 * understand how to supply values for template variables.
315 	 *
316 	 * @return Graph definition which can be used to create RrdGraph object (actual JRobin graphs)
317 	 * @throws RrdException Thrown if parsed XML template contains invalid (unrecognized) tags
318 	 */
319 	public RrdGraphDef getRrdGraphDef() throws RrdException {
320 		// basic check
321 		if (!root.getTagName().equals("rrd_graph_def")) {
322 			throw new RrdException("XML definition must start with <rrd_graph_def>");
323 		}
324 		validateTagsOnlyOnce(root, new String[] {"filename", "span", "options", "datasources", "graph"});
325 		rrdGraphDef = new RrdGraphDef();
326 		// traverse all nodes
327 		Node[] childNodes = getChildNodes(root);
328 		for (Node childNode : childNodes) {
329 			String nodeName = childNode.getNodeName();
330 			if (nodeName.equals("filename")) {
331 				resolveFilename(childNode);
332 			}
333 			// SPAN
334 			else if (nodeName.equals("span")) {
335 				resolveSpan(childNode);
336 			}
337 			// OPTIONS
338 			else if (nodeName.equals("options")) {
339 				resolveOptions(childNode);
340 			}
341 			// DATASOURCES
342 			else if (nodeName.equals("datasources")) {
343 				resolveDatasources(childNode);
344 			}
345 			// GRAPH ELEMENTS
346 			else if (nodeName.equals("graph")) {
347 				resolveGraphElements(childNode);
348 			}
349 		}
350 		return rrdGraphDef;
351 	}
352 
353 	private void resolveGraphElements(Node graphNode) throws RrdException {
354 		validateTagsOnlyOnce(graphNode, new String[] {"area*", "line*", "stack*",
355 				"print*", "gprint*", "hrule*", "vrule*", "comment*"});
356 		Node[] childNodes = getChildNodes(graphNode);
357 		for (Node childNode : childNodes) {
358 			String nodeName = childNode.getNodeName();
359 			if (nodeName.equals("area")) {
360 				resolveArea(childNode);
361 			}
362 			else if (nodeName.equals("line")) {
363 				resolveLine(childNode);
364 			}
365 			else if (nodeName.equals("stack")) {
366 				resolveStack(childNode);
367 			}
368 			else if (nodeName.equals("print")) {
369 				resolvePrint(childNode, false);
370 			}
371 			else if (nodeName.equals("gprint")) {
372 				resolvePrint(childNode, true);
373 			}
374 			else if (nodeName.equals("hrule")) {
375 				resolveHRule(childNode);
376 			}
377 			else if (nodeName.equals("vrule")) {
378 				resolveVRule(childNode);
379 			}
380 			else if (nodeName.equals("comment")) {
381 				rrdGraphDef.comment(getValue(childNode));
382 			}
383 		}
384 	}
385 
386 	private void resolveVRule(Node parentNode) throws RrdException {
387 		validateTagsOnlyOnce(parentNode, new String[] {"time", "color", "legend"});
388 		long timestamp = Long.MIN_VALUE;
389 		Paint color = null;
390 		String legend = null;
391 		Node[] childNodes = getChildNodes(parentNode);
392 		for (Node childNode : childNodes) {
393 			String nodeName = childNode.getNodeName();
394 			if (nodeName.equals("time")) {
395 				timestamp = Util.getTimestamp(getValue(childNode));
396 			}
397 			else if (nodeName.equals("color")) {
398 				color = getValueAsColor(childNode);
399 			}
400 			else if (nodeName.equals("legend")) {
401 				legend = getValue(childNode);
402 			}
403 		}
404 		if (timestamp != Long.MIN_VALUE && color != null) {
405 			rrdGraphDef.vrule(timestamp, color, legend);
406 		}
407 		else {
408 			throw new RrdException("Incomplete VRULE settings");
409 		}
410 	}
411 
412 	private void resolveHRule(Node parentNode) throws RrdException {
413 		validateTagsOnlyOnce(parentNode, new String[] {"value", "color", "legend"});
414 		double value = Double.NaN;
415 		Paint color = null;
416 		String legend = null;
417 		Node[] childNodes = getChildNodes(parentNode);
418 		for (Node childNode : childNodes) {
419 			String nodeName = childNode.getNodeName();
420 			if (nodeName.equals("value")) {
421 				value = getValueAsDouble(childNode);
422 			}
423 			else if (nodeName.equals("color")) {
424 				color = getValueAsColor(childNode);
425 			}
426 			else if (nodeName.equals("legend")) {
427 				legend = getValue(childNode);
428 			}
429 		}
430 		if (!Double.isNaN(value) && color != null) {
431 			rrdGraphDef.hrule(value, color, legend);
432 		}
433 		else {
434 			throw new RrdException("Incomplete HRULE settings");
435 		}
436 	}
437 
438 	private void resolvePrint(Node parentNode, boolean isInGraph) throws RrdException {
439 		validateTagsOnlyOnce(parentNode, new String[] {"datasource", "cf", "format"});
440 		String datasource = null, cf = null, format = null;
441 		Node[] childNodes = getChildNodes(parentNode);
442 		for (Node childNode : childNodes) {
443 			String nodeName = childNode.getNodeName();
444 			if (nodeName.equals("datasource")) {
445 				datasource = getValue(childNode);
446 			}
447 			else if (nodeName.equals("cf")) {
448 				cf = getValue(childNode);
449 			}
450 			else if (nodeName.equals("format")) {
451 				format = getValue(childNode);
452 			}
453 		}
454 		if (datasource != null && cf != null && format != null) {
455 			if (isInGraph) {
456 				rrdGraphDef.gprint(datasource, cf, format);
457 			}
458 			else {
459 				rrdGraphDef.print(datasource, cf, format);
460 			}
461 		}
462 		else {
463 			throw new RrdException("Incomplete " + (isInGraph ? "GRPINT" : "PRINT") + " settings");
464 		}
465 	}
466 
467 	private void resolveStack(Node parentNode) throws RrdException {
468 		validateTagsOnlyOnce(parentNode, new String[] {"datasource", "color", "legend"});
469 		String datasource = null, legend = null;
470 		Paint color = null;
471 		Node[] childNodes = getChildNodes(parentNode);
472 		for (Node childNode : childNodes) {
473 			String nodeName = childNode.getNodeName();
474 			if (nodeName.equals("datasource")) {
475 				datasource = getValue(childNode);
476 			}
477 			else if (nodeName.equals("color")) {
478 				color = getValueAsColor(childNode);
479 			}
480 			else if (nodeName.equals("legend")) {
481 				legend = getValue(childNode);
482 			}
483 		}
484 		if (datasource != null) {
485 			if (color != null) {
486 				rrdGraphDef.stack(datasource, color, legend);
487 			}
488 			else {
489 				rrdGraphDef.stack(datasource, BLIND_COLOR, legend);
490 			}
491 		}
492 		else {
493 			throw new RrdException("Incomplete STACK settings");
494 		}
495 	}
496 
497 	private void resolveLine(Node parentNode) throws RrdException {
498 		validateTagsOnlyOnce(parentNode, new String[] {"datasource", "color", "legend", "width"});
499 		String datasource = null, legend = null;
500 		Paint color = null;
501 		float width = 1.0F;
502 		Node[] childNodes = getChildNodes(parentNode);
503 		for (Node childNode : childNodes) {
504 			String nodeName = childNode.getNodeName();
505 			if (nodeName.equals("datasource")) {
506 				datasource = getValue(childNode);
507 			}
508 			else if (nodeName.equals("color")) {
509 				color = getValueAsColor(childNode);
510 			}
511 			else if (nodeName.equals("legend")) {
512 				legend = getValue(childNode);
513 			}
514 			else if (nodeName.equals("width")) {
515 				width = (float) getValueAsDouble(childNode);
516 			}
517 		}
518 		if (datasource != null) {
519 			if (color != null) {
520 				rrdGraphDef.line(datasource, color, legend, width);
521 			}
522 			else {
523 				rrdGraphDef.line(datasource, BLIND_COLOR, legend, width);
524 			}
525 		}
526 		else {
527 			throw new RrdException("Incomplete LINE settings");
528 		}
529 	}
530 
531 	private void resolveArea(Node parentNode) throws RrdException {
532 		validateTagsOnlyOnce(parentNode, new String[] {"datasource", "color", "legend"});
533 		String datasource = null, legend = null;
534 		Paint color = null;
535 		Node[] childNodes = getChildNodes(parentNode);
536 		for (Node childNode : childNodes) {
537 			String nodeName = childNode.getNodeName();
538 			if (nodeName.equals("datasource")) {
539 				datasource = getValue(childNode);
540 			}
541 			else if (nodeName.equals("color")) {
542 				color = getValueAsColor(childNode);
543 			}
544 			else if (nodeName.equals("legend")) {
545 				legend = getValue(childNode);
546 			}
547 		}
548 		if (datasource != null) {
549 			if (color != null) {
550 				rrdGraphDef.area(datasource, color, legend);
551 			}
552 			else {
553 				rrdGraphDef.area(datasource, BLIND_COLOR, legend);
554 			}
555 		}
556 		else {
557 			throw new RrdException("Incomplete AREA settings");
558 		}
559 	}
560 
561 	private void resolveDatasources(Node datasourcesNode) throws RrdException {
562 		validateTagsOnlyOnce(datasourcesNode, new String[] {"def*", "cdef*", "sdef*"});
563 		Node[] childNodes = getChildNodes(datasourcesNode);
564 		for (Node childNode : childNodes) {
565 			String nodeName = childNode.getNodeName();
566 			if (nodeName.equals("def")) {
567 				resolveDef(childNode);
568 			}
569 			else if (nodeName.equals("cdef")) {
570 				resolveCDef(childNode);
571 			}
572 			else if (nodeName.equals("sdef")) {
573 				resolveSDef(childNode);
574 			}
575 		}
576 	}
577 
578 	private void resolveSDef(Node parentNode) throws RrdException {
579 		validateTagsOnlyOnce(parentNode, new String[] {"name", "source", "cf"});
580 		String name = null, source = null, cf = null;
581 		Node[] childNodes = getChildNodes(parentNode);
582 		for (Node childNode : childNodes) {
583 			String nodeName = childNode.getNodeName();
584 			if (nodeName.equals("name")) {
585 				name = getValue(childNode);
586 			}
587 			else if (nodeName.equals("source")) {
588 				source = getValue(childNode);
589 			}
590 			else if (nodeName.equals("cf")) {
591 				cf = getValue(childNode);
592 			}
593 		}
594 		if (name != null && source != null && cf != null) {
595 			rrdGraphDef.datasource(name, source, cf);
596 		}
597 		else {
598 			throw new RrdException("Incomplete SDEF settings");
599 		}
600 	}
601 
602 	private void resolveCDef(Node parentNode) throws RrdException {
603 		validateTagsOnlyOnce(parentNode, new String[] {"name", "rpn"});
604 		String name = null, rpn = null;
605 		Node[] childNodes = getChildNodes(parentNode);
606 		for (Node childNode : childNodes) {
607 			String nodeName = childNode.getNodeName();
608 			if (nodeName.equals("name")) {
609 				name = getValue(childNode);
610 			}
611 			else if (nodeName.equals("rpn")) {
612 				rpn = getValue(childNode);
613 			}
614 		}
615 		if (name != null && rpn != null) {
616 			rrdGraphDef.datasource(name, rpn);
617 		}
618 		else {
619 			throw new RrdException("Incomplete CDEF settings");
620 		}
621 	}
622 
623 	private void resolveDef(Node parentNode) throws RrdException {
624 		validateTagsOnlyOnce(parentNode, new String[] {"name", "rrd", "source", "cf", "backend"});
625 		String name = null, rrd = null, source = null, cf = null, backend = null;
626 		Node[] childNodes = getChildNodes(parentNode);
627 		for (Node childNode : childNodes) {
628 			String nodeName = childNode.getNodeName();
629 			if (nodeName.equals("name")) {
630 				name = getValue(childNode);
631 			}
632 			else if (nodeName.equals("rrd")) {
633 				rrd = getValue(childNode);
634 			}
635 			else if (nodeName.equals("source")) {
636 				source = getValue(childNode);
637 			}
638 			else if (nodeName.equals("cf")) {
639 				cf = getValue(childNode);
640 			}
641 			else if (nodeName.equals("backend")) {
642 				backend = getValue(childNode);
643 			}
644 		}
645 		if (name != null && rrd != null && source != null && cf != null) {
646 			rrdGraphDef.datasource(name, rrd, source, cf, backend);
647 		}
648 		else {
649 			throw new RrdException("Incomplete DEF settings");
650 		}
651 	}
652 
653 	private void resolveFilename(Node filenameNode) {
654 		String filename = getValue(filenameNode);
655 		rrdGraphDef.setFilename(filename);
656 	}
657 
658 	private void resolveSpan(Node spanNode) throws RrdException {
659 		validateTagsOnlyOnce(spanNode, new String[] {"start", "end"});
660 		String startStr = getChildValue(spanNode, "start");
661 		String endStr = getChildValue(spanNode, "end");
662 		long[] span = Util.getTimestamps(startStr, endStr);
663 		rrdGraphDef.setStartTime(span[0]);
664 		rrdGraphDef.setEndTime(span[1]);
665 	}
666 
667 	private void resolveOptions(Node rootOptionNode) throws RrdException {
668 		validateTagsOnlyOnce(rootOptionNode, new String[] {
669 				"anti_aliasing", "use_pool", "time_grid", "value_grid", "alt_y_grid", "alt_y_mrtg",
670 				"no_minor_grid", "alt_autoscale", "alt_autoscale_max", "units_exponent", "units_length",
671 				"vertical_label", "width", "height", "interlaced", "image_info", "image_format",
672 				"image_quality", "background_image", "overlay_image", "unit", "lazy",
673 				"min_value", "max_value", "rigid", "base", "logarithmic", "colors",
674 				"no_legend", "only_graph", "force_rules_legend", "title", "step", "fonts",
675 				"first_day_of_week", "signature"
676 		});
677 		Node[] optionNodes = getChildNodes(rootOptionNode);
678 		for (Node optionNode : optionNodes) {
679 			String option = optionNode.getNodeName();
680 			if (option.equals("use_pool")) {
681 				rrdGraphDef.setPoolUsed(getValueAsBoolean(optionNode));
682 			}
683 			else if (option.equals("anti_aliasing")) {
684 				rrdGraphDef.setAntiAliasing(getValueAsBoolean(optionNode));
685 			}
686 			else if (option.equals("time_grid")) {
687 				resolveTimeGrid(optionNode);
688 			}
689 			else if (option.equals("value_grid")) {
690 				resolveValueGrid(optionNode);
691 			}
692 			else if (option.equals("no_minor_grid")) {
693 				rrdGraphDef.setNoMinorGrid(getValueAsBoolean(optionNode));
694 			}
695 			else if (option.equals("alt_y_grid")) {
696 				rrdGraphDef.setAltYGrid(getValueAsBoolean(optionNode));
697 			}
698 			else if (option.equals("alt_y_mrtg")) {
699 				rrdGraphDef.setAltYMrtg(getValueAsBoolean(optionNode));
700 			}
701 			else if (option.equals("alt_autoscale")) {
702 				rrdGraphDef.setAltAutoscale(getValueAsBoolean(optionNode));
703 			}
704 			else if (option.equals("alt_autoscale_max")) {
705 				rrdGraphDef.setAltAutoscaleMax(getValueAsBoolean(optionNode));
706 			}
707 			else if (option.equals("units_exponent")) {
708 				rrdGraphDef.setUnitsExponent(getValueAsInt(optionNode));
709 			}
710 			else if (option.equals("units_length")) {
711 				rrdGraphDef.setUnitsLength(getValueAsInt(optionNode));
712 			}
713 			else if (option.equals("vertical_label")) {
714 				rrdGraphDef.setVerticalLabel(getValue(optionNode));
715 			}
716 			else if (option.equals("width")) {
717 				rrdGraphDef.setWidth(getValueAsInt(optionNode));
718 			}
719 			else if (option.equals("height")) {
720 				rrdGraphDef.setHeight(getValueAsInt(optionNode));
721 			}
722 			else if (option.equals("interlaced")) {
723 				rrdGraphDef.setInterlaced(getValueAsBoolean(optionNode));
724 			}
725 			else if (option.equals("image_info")) {
726 				rrdGraphDef.setImageInfo(getValue(optionNode));
727 			}
728 			else if (option.equals("image_format")) {
729 				rrdGraphDef.setImageFormat(getValue(optionNode));
730 			}
731 			else if (option.equals("image_quality")) {
732 				rrdGraphDef.setImageQuality((float) getValueAsDouble(optionNode));
733 			}
734 			else if (option.equals("background_image")) {
735 				rrdGraphDef.setBackgroundImage(getValue(optionNode));
736 			}
737 			else if (option.equals("overlay_image")) {
738 				rrdGraphDef.setOverlayImage(getValue(optionNode));
739 			}
740 			else if (option.equals("unit")) {
741 				rrdGraphDef.setUnit(getValue(optionNode));
742 			}
743 			else if (option.equals("lazy")) {
744 				rrdGraphDef.setLazy(getValueAsBoolean(optionNode));
745 			}
746 			else if (option.equals("min_value")) {
747 				rrdGraphDef.setMinValue(getValueAsDouble(optionNode));
748 			}
749 			else if (option.equals("max_value")) {
750 				rrdGraphDef.setMaxValue(getValueAsDouble(optionNode));
751 			}
752 			else if (option.equals("rigid")) {
753 				rrdGraphDef.setRigid(getValueAsBoolean(optionNode));
754 			}
755 			else if (option.equals("base")) {
756 				rrdGraphDef.setBase(getValueAsDouble(optionNode));
757 			}
758 			else if (option.equals("logarithmic")) {
759 				rrdGraphDef.setLogarithmic(getValueAsBoolean(optionNode));
760 			}
761 			else if (option.equals("colors")) {
762 				resolveColors(optionNode);
763 			}
764 			else if (option.equals("no_legend")) {
765 				rrdGraphDef.setNoLegend(getValueAsBoolean(optionNode));
766 			}
767 			else if (option.equals("only_graph")) {
768 				rrdGraphDef.setOnlyGraph(getValueAsBoolean(optionNode));
769 			}
770 			else if (option.equals("force_rules_legend")) {
771 				rrdGraphDef.setForceRulesLegend(getValueAsBoolean(optionNode));
772 			}
773 			else if (option.equals("title")) {
774 				rrdGraphDef.setTitle(getValue(optionNode));
775 			}
776 			else if (option.equals("step")) {
777 				rrdGraphDef.setStep(getValueAsLong(optionNode));
778 			}
779 			else if (option.equals("fonts")) {
780 				resolveFonts(optionNode);
781 			}
782 			else if (option.equals("first_day_of_week")) {
783 				int dayIndex = resolveFirstDayOfWeek(getValue(optionNode));
784 				rrdGraphDef.setFirstDayOfWeek(dayIndex);
785 			}
786 			else if (option.equals("signature")) {
787 				rrdGraphDef.setShowSignature(getValueAsBoolean(optionNode));
788 			}
789 		}
790 	}
791 
792 	private int resolveFirstDayOfWeek(String firstDayOfWeek) throws RrdException {
793 		if (firstDayOfWeek.equalsIgnoreCase("sunday")) {
794 			return SUNDAY;
795 		}
796 		else if (firstDayOfWeek.equalsIgnoreCase("monday")) {
797 			return MONDAY;
798 		}
799 		else if (firstDayOfWeek.equalsIgnoreCase("tuesday")) {
800 			return TUESDAY;
801 		}
802 		else if (firstDayOfWeek.equalsIgnoreCase("wednesday")) {
803 			return WEDNESDAY;
804 		}
805 		else if (firstDayOfWeek.equalsIgnoreCase("thursday")) {
806 			return THURSDAY;
807 		}
808 		else if (firstDayOfWeek.equalsIgnoreCase("friday")) {
809 			return FRIDAY;
810 		}
811 		else if (firstDayOfWeek.equalsIgnoreCase("saturday")) {
812 			return SATURDAY;
813 		}
814 		throw new RrdException("Never heard for this day of week: " + firstDayOfWeek);
815 	}
816 
817 	private void resolveFonts(Node parentNode) throws RrdException {
818 		validateTagsOnlyOnce(parentNode, new String[] {"small_font", "large_font"});
819 		Node[] childNodes = getChildNodes(parentNode);
820 		for (Node childNode : childNodes) {
821 			String nodeName = childNode.getNodeName();
822 			if (nodeName.equals("small_font")) {
823 				rrdGraphDef.setSmallFont(resolveFont(childNode));
824 			}
825 			else if (nodeName.equals("large_font")) {
826 				rrdGraphDef.setLargeFont(resolveFont(childNode));
827 			}
828 		}
829 	}
830 
831 	private Font resolveFont(Node parentNode) throws RrdException {
832 		validateTagsOnlyOnce(parentNode, new String[] {"name", "style", "size"});
833 		String name = null, style = null;
834 		int size = 0;
835 		Node[] childNodes = getChildNodes(parentNode);
836 		for (Node childNode : childNodes) {
837 			String nodeName = childNode.getNodeName();
838 			if (nodeName.equals("name")) {
839 				name = getValue(childNode);
840 			}
841 			else if (nodeName.equals("style")) {
842 				style = getValue(childNode).toLowerCase();
843 			}
844 			else if (nodeName.equals("size")) {
845 				size = getValueAsInt(childNode);
846 			}
847 		}
848 		if (name != null && style != null && size > 0) {
849 			boolean isItalic = style.contains("italic"), isBold = style.contains("bold");
850 			int fstyle = Font.PLAIN;
851 			if (isItalic && isBold) {
852 				fstyle = Font.BOLD + Font.ITALIC;
853 			}
854 			else if (isItalic) {
855 				fstyle = Font.ITALIC;
856 			}
857 			else if (isBold) {
858 				fstyle = Font.BOLD;
859 			}
860 			return new Font(name, fstyle, size);
861 		}
862 		else {
863 			throw new RrdException("Incomplete font specification");
864 		}
865 	}
866 
867 	private void resolveColors(Node parentNode) throws RrdException {
868 		validateTagsOnlyOnce(parentNode, COLOR_NAMES);
869 		Node[] childNodes = getChildNodes(parentNode);
870 		for (Node childNode : childNodes) {
871 			String colorName = childNode.getNodeName();
872 			rrdGraphDef.setColor(colorName, getValueAsColor(childNode));
873 		}
874 	}
875 
876 	private void resolveValueGrid(Node parentNode) throws RrdException {
877 		validateTagsOnlyOnce(parentNode, new String[] {"show_grid", "grid_step", "label_factor"});
878 		boolean showGrid = true;
879 		double gridStep = Double.NaN;
880 		int NOT_SET = Integer.MIN_VALUE, labelFactor = NOT_SET;
881 		Node[] childNodes = getChildNodes(parentNode);
882 		for (Node childNode : childNodes) {
883 			String nodeName = childNode.getNodeName();
884 			if (nodeName.equals("show_grid")) {
885 				showGrid = getValueAsBoolean(childNode);
886 			}
887 			else if (nodeName.equals("grid_step")) {
888 				gridStep = getValueAsDouble(childNode);
889 			}
890 			else if (nodeName.equals("label_factor")) {
891 				labelFactor = getValueAsInt(childNode);
892 			}
893 		}
894 		rrdGraphDef.setDrawYGrid(showGrid);
895 		if (!Double.isNaN(gridStep) && labelFactor != NOT_SET) {
896 			rrdGraphDef.setValueAxis(gridStep, labelFactor);
897 		}
898 		else if (!Double.isNaN(gridStep) || labelFactor != NOT_SET) {
899 			throw new RrdException("Incomplete value axis settings");
900 		}
901 	}
902 
903 	private void resolveTimeGrid(Node parentNode) throws RrdException {
904 		validateTagsOnlyOnce(parentNode, new String[] {
905 				"show_grid", "minor_grid_unit",
906 				"minor_grid_unit_count", "major_grid_unit",
907 				"major_grid_unit_count", "label_unit", "label_unit_count",
908 				"label_span", "label_format"
909 		});
910 		boolean showGrid = true;
911 		final int NOT_SET = Integer.MIN_VALUE;
912 		int minorGridUnit = NOT_SET, minorGridUnitCount = NOT_SET,
913 				majorGridUnit = NOT_SET, majorGridUnitCount = NOT_SET,
914 				labelUnit = NOT_SET, labelUnitCount = NOT_SET, labelSpan = NOT_SET;
915 		String labelFormat = null;
916 		Node[] childNodes = getChildNodes(parentNode);
917 		for (Node childNode : childNodes) {
918 			String nodeName = childNode.getNodeName();
919 			if (nodeName.equals("show_grid")) {
920 				showGrid = getValueAsBoolean(childNode);
921 			}
922 			else if (nodeName.equals("minor_grid_unit")) {
923 				minorGridUnit = resolveTimeUnit(getValue(childNode));
924 			}
925 			else if (nodeName.equals("minor_grid_unit_count")) {
926 				minorGridUnitCount = getValueAsInt(childNode);
927 			}
928 			else if (nodeName.equals("major_grid_unit")) {
929 				majorGridUnit = resolveTimeUnit(getValue(childNode));
930 			}
931 			else if (nodeName.equals("major_grid_unit_count")) {
932 				majorGridUnitCount = getValueAsInt(childNode);
933 			}
934 			else if (nodeName.equals("label_unit")) {
935 				labelUnit = resolveTimeUnit(getValue(childNode));
936 			}
937 			else if (nodeName.equals("label_unit_count")) {
938 				labelUnitCount = getValueAsInt(childNode);
939 			}
940 			else if (nodeName.equals("label_span")) {
941 				labelSpan = getValueAsInt(childNode);
942 			}
943 			else if (nodeName.equals("label_format")) {
944 				labelFormat = getValue(childNode);
945 			}
946 		}
947 		rrdGraphDef.setDrawXGrid(showGrid);
948 		if (minorGridUnit != NOT_SET && minorGridUnitCount != NOT_SET &&
949 				majorGridUnit != NOT_SET && majorGridUnitCount != NOT_SET &&
950 				labelUnit != NOT_SET && labelUnitCount != NOT_SET && labelSpan != NOT_SET && labelFormat != null) {
951 			rrdGraphDef.setTimeAxis(minorGridUnit, minorGridUnitCount, majorGridUnit, majorGridUnitCount,
952 					labelUnit, labelUnitCount, labelSpan, labelFormat);
953 		}
954 		else if (minorGridUnit != NOT_SET || minorGridUnitCount != NOT_SET ||
955 				majorGridUnit != NOT_SET || majorGridUnitCount != NOT_SET ||
956 				labelUnit != NOT_SET || labelUnitCount != NOT_SET || labelSpan != NOT_SET || labelFormat != null) {
957 			throw new RrdException("Incomplete time axis settings");
958 		}
959 	}
960 
961 	private int resolveTimeUnit(String unit) throws RrdException {
962 		if (unit.equalsIgnoreCase("second")) {
963 			return RrdGraphConstants.SECOND;
964 		}
965 		else if (unit.equalsIgnoreCase("minute")) {
966 			return RrdGraphConstants.MINUTE;
967 		}
968 		else if (unit.equalsIgnoreCase("hour")) {
969 			return RrdGraphConstants.HOUR;
970 		}
971 		else if (unit.equalsIgnoreCase("day")) {
972 			return RrdGraphConstants.DAY;
973 		}
974 		else if (unit.equalsIgnoreCase("week")) {
975 			return RrdGraphConstants.WEEK;
976 		}
977 		else if (unit.equalsIgnoreCase("month")) {
978 			return RrdGraphConstants.MONTH;
979 		}
980 		else if (unit.equalsIgnoreCase("year")) {
981 			return RrdGraphConstants.YEAR;
982 		}
983 		throw new RrdException("Unknown time unit specified: " + unit);
984 	}
985 }