001    /* ============================================================
002     * JRobin : Pure java implementation of RRDTool's functionality
003     * ============================================================
004     *
005     * Project Info:  http://www.jrobin.org
006     * Project Lead:  Sasa Markovic (saxon@jrobin.org);
007     *
008     * (C) Copyright 2003-2005, by Sasa Markovic.
009     *
010     * Developers:    Sasa Markovic (saxon@jrobin.org)
011     *
012     *
013     * This library is free software; you can redistribute it and/or modify it under the terms
014     * of the GNU Lesser General Public License as published by the Free Software Foundation;
015     * either version 2.1 of the License, or (at your option) any later version.
016     *
017     * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
018     * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
019     * See the GNU Lesser General Public License for more details.
020     *
021     * You should have received a copy of the GNU Lesser General Public License along with this
022     * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
023     * Boston, MA 02111-1307, USA.
024     */
025    package org.jrobin.graph;
026    
027    import org.jrobin.core.RrdException;
028    import org.jrobin.core.Util;
029    import org.jrobin.core.XmlTemplate;
030    import org.w3c.dom.Node;
031    import org.xml.sax.InputSource;
032    
033    import java.awt.*;
034    import java.io.File;
035    import java.io.IOException;
036    
037    /**
038     * Class used to create an arbitrary number of RrdGraphDef (graph definition) objects
039     * from a single XML template. XML template can be supplied as an XML InputSource,
040     * XML file or XML formatted string.<p>
041     * <p/>
042     * Here is an example of a properly formatted XML template with all available options in it
043     * (unwanted options can be removed/ignored):<p>
044     * <pre>
045     * &lt;rrd_graph_def&gt;
046     *     &lt;!-- use '-' to represent in-memory graph --&gt;
047     *     &lt;filename&gt;test.png&lt;/filename&gt;
048     *     &lt;!--
049     *         starting and ending timestamps can be specified by
050     *         using at-style time specification, or by specifying
051     *         exact timestamps since epoch (without milliseconds)
052     *     --&gt;
053     *     &lt;span&gt;
054     *         &lt;start&gt;now - 1d&lt;/start&gt;
055     *         &lt;end&gt;now&lt;/end&gt;
056     *     &lt;/span&gt;
057     *     &lt;options&gt;
058     *         &lt;!--
059     *             specify 'true' if you want to use RrdDbPool while
060     *             creating graph
061     *         --&gt;
062     *         &lt;use_pool&gt;false&lt;/use_pool&gt;
063     *         &lt;anti_aliasing&gt;true&lt;/anti_aliasing&gt;
064     *         &lt;time_grid&gt;
065     *             &lt;show_grid&gt;true&lt;/show_grid&gt;
066     *             &lt;!-- allowed units: second, minute, hour, day, week, month, year --&gt;
067     *             &lt;minor_grid_unit&gt;minute&lt;/minor_grid_unit&gt;
068     *             &lt;minor_grid_unit_count&gt;60&lt;/minor_grid_unit_count&gt;
069     *             &lt;major_grid_unit&gt;hour&lt;/major_grid_unit&gt;
070     *             &lt;major_grid_unit_count&gt;2&lt;/major_grid_unit_count&gt;
071     *             &lt;label_unit&gt;hour&lt;/label_unit&gt;
072     *             &lt;label_unit_count&gt;2&lt;/label_unit_count&gt;
073     *             &lt;label_span&gt;1200&lt;/label_span&gt;
074     *             &lt;!-- use SimpleDateFormat or strftime-like format to format labels --&gt;
075     *             &lt;label_format&gt;dd-MMM-yy&lt;/label_format&gt;
076     *         &lt;/time_grid&gt;
077     *         &lt;value_grid&gt;
078     *             &lt;show_grid&gt;true&lt;/show_grid&gt;
079     *             &lt;grid_step&gt;100.0&lt;/grid_step&gt;
080     *             &lt;label_factor&gt;5&lt;/label_factor&gt;
081     *         &lt;/value_grid&gt;
082     *         &lt;no_minor_grid&gt;true&lt;/no_minor_grid&gt;
083     *         &lt;alt_y_grid&gt;true&lt;/alt_y_grid&gt;
084     *         &lt;alt_y_mrtg&gt;true&lt;/alt_y_mrtg&gt;
085     *         &lt;alt_autoscale&gt;true&lt;/alt_autoscale&gt;
086     *         &lt;alt_autoscale_max&gt;true&lt;/alt_autoscale_max&gt;
087     *         &lt;units_exponent&gt;3&lt;/units_exponent&gt;
088     *         &lt;units_length&gt;13&lt;/units_length&gt;
089     *         &lt;vertical_label&gt;Speed (kbits/sec)&lt;/vertical_label&gt;
090     *         &lt;width&gt;444&lt;/width&gt;
091     *         &lt;height&gt;222&lt;/height&gt;
092     *         &lt;interlaced&gt;true&lt;/interlaced&gt;
093     *         &lt;image_info&gt;filename = %s, width=%d, height=%d&lt;/image_info&gt;
094     *         &lt;image_format&gt;png&lt;/image_format&gt;
095     *         &lt;image_quality&gt;0.8&lt;/image_quality&gt;
096     *         &lt;background_image&gt;luka.png&lt;/background_image&gt;
097     *         &lt;overlay_image&gt;luka.png&lt;/overlay_image&gt;
098     *         &lt;unit&gt;kilos&lt;/unit&gt;
099     *         &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    }