Java tutorial
/****************************************************** * Merge XML with a Velocity template * * * Copyright (C) 2012 by Peter Hedenskog (http://peterhedenskog.com) * ****************************************************** * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. * ******************************************************* */ package com.soulgalore.velocity; import java.io.File; import java.io.FileOutputStream; import java.io.FilePermission; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.runtime.RuntimeConstants; import org.apache.velocity.runtime.resource.loader.FileResourceLoader; import org.jdom2.Document; import org.jdom2.JDOMException; import org.jdom2.input.SAXBuilder; import org.jdom2.input.sax.XMLReaderSAX2Factory; /** * Merge a XML file with a Velocity template. <code> * java -jar xml-velocity-1.0-full.jar test.xml test.vm test.properties newfile.xml * </code> * * <p> * The parameters: * <ol> * <li>the xml file</li> * <li>the Velocity template file</li> * <li>the properties file, key/vale will be added to the Velocity context, used when merged</li> * <li>the output file name</li> * <li>Optional second XML file (sitespeed.io uses this when parsing the BrowserTime XML)</li> * </ol> * </p> * <p> * The document is added to the context with {@link #CONTEXT_DOCUMENT} as key. * </p> * <p> * You can also add objects to the velocity context by configuration, in the properties file, add: * * <pre>velocity.context.object.1=CLASSNAME:KEYNAME</pre> * And make sure the class exists in the classpath and have a constructor without arguments. See * this example: * * <pre> * velocity.context.object.1=org.apache.velocity.tools.generic.MathTool:math * velocity.context.object.2=org.apache.velocity.tools.generic.EscapeTool:esc * velocity.context.object.3=org.apache.commons.math3.stat.descriptive.DescriptiveStatistics:stats * velocity.context.object.4=com.soulgalore.velocity.HostTool:host * </pre> * </p> * <p> * You can also supply System properties that will be merged, the property needs to start with * {@link #SYSTEM_PROPERTY_START}. It works like this: Set a key * * <pre>com.soulgalore.velocity.key.header</pre> * and in the Velocity file you get the value by $header * </p> */ public class MergeXMLWithVelocity { /** * The name of the xml document, added to the context. */ public static final String CONTEXT_DOCUMENT = "document"; public static final String CONTEXT_PROPERTY_OBJECT = "velocity.context.object"; public static final String SYSTEM_PROPERTY_START = "com.soulgalore.velocity.key."; private final VelocityContext context = new VelocityContext(); private final VelocityEngine ve; MergeXMLWithVelocity(Properties properties) { ve = new VelocityEngine(properties); ve.setProperty("resource.loader", "plainfile"); ve.setProperty("plainfile.resource.loader.instance", new PlainFileResourceLoader()); ve.init(); Map<String, String> keyAndClasses = getClasses(properties); for (Entry<String, String> value : keyAndClasses.entrySet()) { try { Class clazz = Class.forName(value.getValue()); context.put(value.getKey(), clazz.newInstance()); } catch (Exception e) { System.err.println("Couldn't instantiate class:" + value.getValue() + " with key:" + value.getKey() + " and put it in Velocity context:" + e.toString()); } } final Enumeration<?> e = properties.propertyNames(); while (e.hasMoreElements()) { final String key = (String) e.nextElement(); context.put(key, properties.getProperty(key)); } // Add all system properties starting with specific key Enumeration<Object> keys = System.getProperties().keys(); while (keys.hasMoreElements()) { String key = (String) keys.nextElement(); if (key.startsWith(SYSTEM_PROPERTY_START)) { context.put(key.replace(SYSTEM_PROPERTY_START, ""), System.getProperties().get(key)); } } } /** * Merge a xml file with Velocity template. The third parameter is the properties file, where the * key/value is added to the velocity context. The fourth parameter is the output file. If not * included, the result is printed to system.out. * * @param args are file.xml template.vm properties.prop [output.file] * @throws JDOMException if the xml file couldn't be parsed * @throws IOException couldn't find one of the files */ public static void main(String[] args) throws JDOMException, IOException { if (args.length < 3) { System.out.println( "Missing input files. XMLToVelocity file.xml template.vm prop.properties [output.file]"); return; } final Properties properties = new Properties(); final File prop = new File(args[2]); if (prop.exists()) properties.load(new FileReader(prop)); else throw new IOException("Couldn't find the property file:" + prop.getAbsolutePath()); final MergeXMLWithVelocity xtv = new MergeXMLWithVelocity(properties); xtv.create(args); } void create(String[] args) throws JDOMException, IOException { Set<String> xmls = new HashSet<String>(); if (args.length > 4) { for (int i = 4; i < args.length; i++) { xmls.add(args[i]); } } String result = (args.length > 4) ? merge(args[0], args[1], xmls.toArray(new String[0])) : merge(args[0], args[1]); final PrintWriter out = new PrintWriter(new FileOutputStream(args[3])); out.write(result); out.close(); } String merge(String xml, String template) throws JDOMException, IOException { return merge(xml, template, new String[] {}); } String merge(String xml, String template, String[] extraXMLs) throws JDOMException, IOException { final File xmlFile = new File(xml); final SAXBuilder b = new SAXBuilder(new XMLReaderSAX2Factory(false)); final Document doc = b.build(xmlFile); context.put(CONTEXT_DOCUMENT, doc); // TODO Old legacy naming, make this cleaner in the future int name = 2; for (String extraXML : extraXMLs) { final File xmlFileExtra = new File(extraXML); final Document doc2 = b.build(xmlFileExtra); context.put(CONTEXT_DOCUMENT + name, doc2); name++; } final StringWriter writer = new StringWriter(); final Template fromTemplate = ve.getTemplate(template); fromTemplate.merge(context, writer); return writer.toString(); } private Map<String, String> getClasses(Properties properties) { Map<String, String> keyAndClass = new HashMap<String, String>(); Set<String> keys = properties.stringPropertyNames(); for (String key : keys) { if (key.startsWith(CONTEXT_PROPERTY_OBJECT)) { String value = properties.getProperty(key); String className = value.substring(0, value.indexOf(":")); String contextKey = value.substring(value.indexOf(":") + 1, value.length()); keyAndClass.put(contextKey, className); } } return keyAndClass; } }