package org.rrd4j.core; import org.w3c.dom.Node; import org.xml.sax.InputSource; import org.rrd4j.DsType; import org.rrd4j.ConsolFun; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Calendar; /** * <p>Class used to create an arbitrary number of {@link org.rrd4j.core.RrdDef} (RRD definition) objects * from a single XML template. XML template can be supplied as an XML InputSource, * XML file or XML formatted string.</p> * * <p>Here is an example of a properly formatted XML template with all available * options in it (unwanted options can be removed):</p> * <pre> * <rrd_def> * <path>test.rrd</path> * <!-- not mandatory --> * <start>1000123456</start> * <!-- not mandatory --> * <step>300</step> * <!-- at least one datasource must be supplied --> * <datasource> * <name>input</name> * <type>COUNTER</type> * <heartbeat>300</heartbeat> * <min>0</min> * <max>U</max> * </datasource> * <datasource> * <name>temperature</name> * <type>GAUGE</type> * <heartbeat>400</heartbeat> * <min>U</min> * <max>1000</max> * </datasource> * <!-- at least one archive must be supplied --> * <archive> * <cf>AVERAGE</cf> * <xff>0.5</xff> * <steps>1</steps> * <rows>600</rows> * </archive> * <archive> * <cf>MAX</cf> * <xff>0.6</xff> * <steps>6</steps> * <rows>7000</rows> * </archive> * </rrd_def> * </pre> * <p>Notes on the template syntax:</p> * <ul> * <li>There is a strong relation between the XML template syntax and the syntax of * {@link org.rrd4j.core.RrdDef} class methods. If you are not sure what some XML tag means, check javadoc * for the corresponding class. * <li>starting timestamp can be supplied either as a long integer * (like: 1000243567) or as an ISO formatted string (like: 2004-02-21 12:25:45) * <li>whitespaces are not harmful * <li>floating point values: anything that cannot be parsed will be treated as Double.NaN * (like: U, unknown, 12r.23) * <li>comments are allowed. * </ul> * <p>Any template value (text between <code><some_tag></code> and * <code></some_tag></code>) can be replaced with * a variable of the following form: <code>${variable_name}</code>. Use * {@link org.rrd4j.core.XmlTemplate#setVariable(String, String) setVariable()} * methods from the base class to replace template variables with real values * at runtime.</p> * * <p>Typical usage scenario:</p> * <ul> * <li>Create your XML template and save it to a file (template.xml, for example) * <li>Replace hardcoded template values with variables if you want to change them during runtime. * For example, RRD path should not be hardcoded in the template - you probably want to create * many different RRD files from the same XML template. For example, your XML * template could start with: * <pre> * <rrd_def> * <path>${path}</path> * <step>300</step> * ... * </pre> * <li>In your Java code, create RrdDefTemplate object using your XML template file: * <pre> * RrdDefTemplate t = new RrdDefTemplate(new File(template.xml)); * </pre> * <li>Then, specify real values for template variables: * <pre> * t.setVariable("path", "demo/test.rrd"); * </pre> * <li>Once all template variables are set, just use the template object to create RrdDef * object. This object is actually used to create Rrd4j RRD files: * <pre> * RrdDef def = t.getRrdDef(); * RrdDb rrd = new RrdDb(def); * rrd.close(); * </pre> * </ul> * You should create new RrdDefTemplate object only once for each XML template. Single template * object can be reused to create as many RrdDef objects as needed, with different values * specified for template variables. XML syntax check is performed only once - the first * definition object gets created relatively slowly, but it will be created much faster next time. * */ public class RrdDefTemplate extends XmlTemplate { /** * Creates RrdDefTemplate object from any parsable XML input source. Read general information * for this class to find an example of a properly formatted RrdDef XML source. * * @param xmlInputSource Xml input source * @throws java.io.IOException Thrown in case of I/O error * @throws java.lang.IllegalArgumentException Thrown in case of XML related error (parsing error, for example) */ public RrdDefTemplate(InputSource xmlInputSource) throws IOException { super(xmlInputSource); } /** * Creates RrdDefTemplate object from the string containing XML template. * Read general information for this class to see an example of a properly formatted XML source. * * @param xmlString String containing XML template * @throws java.io.IOException Thrown in case of I/O error * @throws java.lang.IllegalArgumentException Thrown in case of XML related error (parsing error, for example) */ public RrdDefTemplate(String xmlString) throws IOException { super(xmlString); } /** * Creates RrdDefTemplate object from the file containing XML template. * Read general information for this class to see an example of a properly formatted XML source. * * @param xmlFile File object representing file with XML template * @throws java.io.IOException Thrown in case of I/O error * @throws java.lang.IllegalArgumentException Thrown in case of XML related error (parsing error, for example) */ public RrdDefTemplate(File xmlFile) throws IOException { super(xmlFile); } /** * Returns RrdDef object constructed from the underlying XML template. Before this method * is called, values for all non-optional placeholders must be supplied. To specify * placeholder values at runtime, use some of the overloaded * {@link org.rrd4j.core.XmlTemplate#setVariable(String, String) setVariable()} methods. Once this method * returns, all placeholder values are preserved. To remove them all, call inherited * {@link org.rrd4j.core.XmlTemplate#clearValues() clearValues()} method explicitly.<p> * * @return RrdDef object constructed from the underlying XML template, * with all placeholders replaced with real values. This object can be passed to the constructor * of the new RrdDb object. * @throws java.lang.IllegalArgumentException Thrown (in most cases) if the value for some placeholder * was not supplied through {@link org.rrd4j.core.XmlTemplate#setVariable(String, String) setVariable()} * method call */ public RrdDef getRrdDef() { if (!"rrd_def".equals(root.getTagName())) { throw new IllegalArgumentException("XML definition must start with <rrd_def>"); } validateTagsOnlyOnce(root, new String[]{ "path*", "uri*", "start", "step", "datasource*", "archive*" }); // PATH must be supplied or exception is thrown RrdDef rrdDef; if (hasChildNode(root, "path")) { String path = getChildValue(root, "path"); rrdDef = new RrdDef(path); } else if (hasChildNode(root, "uri")) { String uri = getChildValue(root, "uri"); try { rrdDef = new RrdDef(new URI(uri)); } catch (URISyntaxException e) { throw new IllegalArgumentException("Wrong URI: " + uri); } } else { throw new IllegalArgumentException("Neither path or URI defined"); } try { String startStr = getChildValue(root, "start"); Calendar startGc = Util.getCalendar(startStr); rrdDef.setStartTime(startGc); } catch (Exception e) { // START is not mandatory } try { long step = getChildValueAsLong(root, "step"); rrdDef.setStep(step); } catch (Exception e) { // STEP is not mandatory } // datsources Node[] dsNodes = getChildNodes(root, "datasource"); for (Node dsNode : dsNodes) { validateTagsOnlyOnce(dsNode, new String[]{ "name", "type", "heartbeat", "min", "max" }); String name = getChildValue(dsNode, "name"); DsType type = DsType.valueOf(getChildValue(dsNode, "type")); long heartbeat = getChildValueAsLong(dsNode, "heartbeat"); double min = getChildValueAsDouble(dsNode, "min"); double max = getChildValueAsDouble(dsNode, "max"); rrdDef.addDatasource(name, type, heartbeat, min, max); } // archives Node[] arcNodes = getChildNodes(root, "archive"); for (Node arcNode : arcNodes) { validateTagsOnlyOnce(arcNode, new String[]{ "cf", "xff", "steps", "rows" }); ConsolFun consolFun = ConsolFun.valueOf(getChildValue(arcNode, "cf")); double xff = getChildValueAsDouble(arcNode, "xff"); int steps = getChildValueAsInt(arcNode, "steps"); int rows = getChildValueAsInt(arcNode, "rows"); rrdDef.addArchive(consolFun, xff, steps, rows); } return rrdDef; } }