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.core;
026    
027    import org.w3c.dom.Node;
028    import org.xml.sax.InputSource;
029    
030    import java.io.File;
031    import java.io.IOException;
032    import java.util.Calendar;
033    
034    /**
035     * Class used to create an arbitrary number of {@link RrdDef} (RRD definition) objects
036     * from a single XML template. XML template can be supplied as an XML InputSource,
037     * XML file or XML formatted string.<p>
038     * <p/>
039     * Here is an example of a properly formatted XML template with all available
040     * options in it (unwanted options can be removed):<p>
041     * <pre>
042     * &lt;rrd_def&gt;
043     *     &lt;path&gt;test.rrd&lt;/path&gt;
044     *     &lt;!-- not mandatory --&gt;
045     *     &lt;start&gt;1000123456&lt;/start&gt;
046     *     &lt;!-- not mandatory --&gt;
047     *     &lt;step&gt;300&lt;/step&gt;
048     *     &lt;!-- at least one datasource must be supplied --&gt;
049     *     &lt;datasource&gt;
050     *         &lt;name&gt;input&lt;/name&gt;
051     *         &lt;type&gt;COUNTER&lt;/type&gt;
052     *         &lt;heartbeat&gt;300&lt;/heartbeat&gt;
053     *         &lt;min&gt;0&lt;/min&gt;
054     *         &lt;max&gt;U&lt;/max&gt;
055     *     &lt;/datasource&gt;
056     *     &lt;datasource&gt;
057     *         &lt;name&gt;temperature&lt;/name&gt;
058     *         &lt;type&gt;GAUGE&lt;/type&gt;
059     *         &lt;heartbeat&gt;400&lt;/heartbeat&gt;
060     *         &lt;min&gt;U&lt;/min&gt;
061     *         &lt;max&gt;1000&lt;/max&gt;
062     *     &lt;/datasource&gt;
063     *     &lt;!-- at least one archive must be supplied --&gt;
064     *     &lt;archive&gt;
065     *         &lt;cf&gt;AVERAGE&lt;/cf&gt;
066     *         &lt;xff&gt;0.5&lt;/xff&gt;
067     *         &lt;steps&gt;1&lt;/steps&gt;
068     *         &lt;rows&gt;600&lt;/rows&gt;
069     *     &lt;/archive&gt;
070     *     &lt;archive&gt;
071     *         &lt;cf&gt;MAX&lt;/cf&gt;
072     *         &lt;xff&gt;0.6&lt;/xff&gt;
073     *         &lt;steps&gt;6&lt;/steps&gt;
074     *         &lt;rows&gt;7000&lt;/rows&gt;
075     *     &lt;/archive&gt;
076     * &lt;/rrd_def&gt;
077     * </pre>
078     * Notes on the template syntax:<p>
079     * <ul>
080     * <li>There is a strong relation between the XML template syntax and the syntax of
081     * {@link RrdDef} class methods. If you are not sure what some XML tag means, check javadoc
082     * for the corresponding class.
083     * <li>starting timestamp can be supplied either as a long integer
084     * (like: 1000243567) or as an ISO formatted string (like: 2004-02-21 12:25:45)
085     * <li>whitespaces are not harmful
086     * <li>floating point values: anything that cannot be parsed will be treated as Double.NaN
087     * (like: U, unknown, 12r.23)
088     * <li>comments are allowed.
089     * </ul>
090     * Any template value (text between <code>&lt;some_tag&gt;</code> and
091     * <code>&lt;/some_tag&gt;</code>) can be replaced with
092     * a variable of the following form: <code>${variable_name}</code>. Use
093     * {@link XmlTemplate#setVariable(String, String) setVariable()}
094     * methods from the base class to replace template variables with real values
095     * at runtime.<p>
096     * <p/>
097     * Typical usage scenario:<p>
098     * <ul>
099     * <li>Create your XML template and save it to a file (template.xml, for example)
100     * <li>Replace hardcoded template values with variables if you want to change them during runtime.
101     * For example, RRD path should not be hardcoded in the template - you probably want to create
102     * many different RRD files from the same XML template. For example, your XML
103     * template could start with:
104     * <pre>
105     * &lt;rrd_def&gt;
106     *     &lt;path&gt;${path}&lt;/path&gt;
107     *     &lt;step&gt;300&lt;/step&gt;
108     *     ...
109     * </pre>
110     * <li>In your Java code, create RrdDefTemplate object using your XML template file:
111     * <pre>
112     * RrdDefTemplate t = new RrdDefTemplate(new File(template.xml));
113     * </pre>
114     * <li>Then, specify real values for template variables:
115     * <pre>
116     * t.setVariable("path", "demo/test.rrd");
117     * </pre>
118     * <li>Once all template variables are set, just use the template object to create RrdDef
119     * object. This object is actually used to create JRobin RRD files:
120     * <pre>
121     * RrdDef def = t.getRrdDef();
122     * RrdDb rrd = new RrdDb(def);
123     * rrd.close();
124     * </pre>
125     * </ul>
126     * You should create new RrdDefTemplate object only once for each XML template. Single template
127     * object can be reused to create as many RrdDef objects as needed, with different values
128     * specified for template variables. XML synatax check is performed only once - the first
129     * definition object gets created relatively slowly, but it will be created much faster next time.
130     */
131    public class RrdDefTemplate extends XmlTemplate {
132            /**
133             * Creates RrdDefTemplate object from any parsable XML input source. Read general information
134             * for this class to find an example of a properly formatted RrdDef XML source.
135             *
136             * @param xmlInputSource Xml input source
137             * @throws IOException  Thrown in case of I/O error
138             * @throws RrdException Thrown in case of XML related error (parsing error, for example)
139             */
140            public RrdDefTemplate(InputSource xmlInputSource) throws IOException, RrdException {
141                    super(xmlInputSource);
142            }
143    
144            /**
145             * Creates RrdDefTemplate object from the string containing XML template.
146             * Read general information for this class to see an example of a properly formatted XML source.
147             *
148             * @param xmlString String containing XML template
149             * @throws IOException  Thrown in case of I/O error
150             * @throws RrdException Thrown in case of XML related error (parsing error, for example)
151             */
152            public RrdDefTemplate(String xmlString) throws IOException, RrdException {
153                    super(xmlString);
154            }
155    
156            /**
157             * Creates RrdDefTemplate object from the file containing XML template.
158             * Read general information for this class to see an example of a properly formatted XML source.
159             *
160             * @param xmlFile File object representing file with XML template
161             * @throws IOException  Thrown in case of I/O error
162             * @throws RrdException Thrown in case of XML related error (parsing error, for example)
163             */
164            public RrdDefTemplate(File xmlFile) throws IOException, RrdException {
165                    super(xmlFile);
166            }
167    
168            /**
169             * Returns RrdDef object constructed from the underlying XML template. Before this method
170             * is called, values for all non-optional placeholders must be supplied. To specify
171             * placeholder values at runtime, use some of the overloaded
172             * {@link XmlTemplate#setVariable(String, String) setVariable()} methods. Once this method
173             * returns, all placeholder values are preserved. To remove them all, call inhereted
174             * {@link XmlTemplate#clearValues() clearValues()} method explicitly.<p>
175             *
176             * @return RrdDef object constructed from the underlying XML template,
177             *         with all placeholders replaced with real values. This object can be passed to the constructor
178             *         of the new RrdDb object.
179             * @throws RrdException Thrown (in most cases) if the value for some placeholder
180             *                      was not supplied through {@link XmlTemplate#setVariable(String, String) setVariable()}
181             *                      method call
182             */
183            public RrdDef getRrdDef() throws RrdException {
184                    if (!root.getTagName().equals("rrd_def")) {
185                            throw new RrdException("XML definition must start with <rrd_def>");
186                    }
187                    validateTagsOnlyOnce(root, new String[] {
188                                    "path", "start", "step", "datasource*", "archive*"
189                    });
190                    // PATH must be supplied or exception is thrown
191                    String path = getChildValue(root, "path");
192                    RrdDef rrdDef = new RrdDef(path);
193                    try {
194                            String startStr = getChildValue(root, "start");
195                            Calendar startGc = Util.getCalendar(startStr);
196                            rrdDef.setStartTime(startGc);
197                    }
198                    catch (RrdException e) {
199                            // START is not mandatory
200                    }
201                    try {
202                            long step = getChildValueAsLong(root, "step");
203                            rrdDef.setStep(step);
204                    }
205                    catch (RrdException e) {
206                            // STEP is not mandatory
207                    }
208                    // datsources
209                    Node[] dsNodes = getChildNodes(root, "datasource");
210                    for (Node dsNode : dsNodes) {
211                            validateTagsOnlyOnce(dsNode, new String[] {
212                                            "name", "type", "heartbeat", "min", "max"
213                            });
214                            String name = getChildValue(dsNode, "name");
215                            String type = getChildValue(dsNode, "type");
216                            long heartbeat = getChildValueAsLong(dsNode, "heartbeat");
217                            double min = getChildValueAsDouble(dsNode, "min");
218                            double max = getChildValueAsDouble(dsNode, "max");
219                            rrdDef.addDatasource(name, type, heartbeat, min, max);
220                    }
221                    // archives
222                    Node[] arcNodes = getChildNodes(root, "archive");
223                    for (Node arcNode : arcNodes) {
224                            validateTagsOnlyOnce(arcNode, new String[] {
225                                            "cf", "xff", "steps", "rows"
226                            });
227                            String consolFun = getChildValue(arcNode, "cf");
228                            double xff = getChildValueAsDouble(arcNode, "xff");
229                            int steps = getChildValueAsInt(arcNode, "steps");
230                            int rows = getChildValueAsInt(arcNode, "rows");
231                            rrdDef.addArchive(consolFun, xff, steps, rows);
232                    }
233                    return rrdDef;
234            }
235    }