com.soulgalore.velocity.MergeXMLWithVelocity.java Source code

Java tutorial

Introduction

Here is the source code for com.soulgalore.velocity.MergeXMLWithVelocity.java

Source

/******************************************************
 * 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;
    }
}