bboss.org.apache.velocity.anakia.AnakiaTask.java Source code

Java tutorial

Introduction

Here is the source code for bboss.org.apache.velocity.anakia.AnakiaTask.java

Source

package bboss.org.apache.velocity.anakia;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.    
 */

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.commons.collections.ExtendedProperties;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.MatchingTask;
import bboss.org.apache.velocity.Template;
import bboss.org.apache.velocity.VelocityContext;
import bboss.org.apache.velocity.app.VelocityEngine;
import bboss.org.apache.velocity.runtime.RuntimeConstants;
import bboss.org.apache.velocity.util.StringUtils;

import org.jdom.Document;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.xml.sax.SAXParseException;

/**
 * The purpose of this Ant Task is to allow you to use
 * Velocity as an XML transformation tool like XSLT is.
 * So, instead of using XSLT, you will be able to use this
 * class instead to do your transformations. It works very
 * similar in concept to Ant's <style> task.
 * <p>
 * You can find more documentation about this class on the
 * Velocity
 * <a href="http://velocity.apache.org/engine/devel/docs/anakia.html">Website</a>.
 *
 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
 * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
 * @version $Id: AnakiaTask.java 501574 2007-01-30 21:32:26Z henning $
 */
public class AnakiaTask extends MatchingTask {
    /** <code>{@link SAXBuilder}</code> instance to use */
    SAXBuilder builder;

    /** the destination directory */
    private File destDir = null;

    /** the base directory */
    File baseDir = null;

    /** the style= attribute */
    private String style = null;

    /** last modified of the style sheet */
    private long styleSheetLastModified = 0;

    /** the projectFile= attribute */
    private String projectAttribute = null;

    /** the File for the project.xml file */
    private File projectFile = null;

    /** last modified of the project file if it exists */
    private long projectFileLastModified = 0;

    /** check the last modified date on files. defaults to true */
    private boolean lastModifiedCheck = true;

    /** the default output extension is .html */
    private String extension = ".html";

    /** the template path */
    private String templatePath = null;

    /** the file to get the velocity properties file */
    private File velocityPropertiesFile = null;

    /** the VelocityEngine instance to use */
    private VelocityEngine ve = new VelocityEngine();

    /** the Velocity subcontexts */
    private List contexts = new LinkedList();

    /**
     * Constructor creates the SAXBuilder.
     */
    public AnakiaTask() {
        builder = new SAXBuilder();
        builder.setFactory(new AnakiaJDOMFactory());
    }

    /**
     * Set the base directory.
     * @param dir
     */
    public void setBasedir(File dir) {
        baseDir = dir;
    }

    /**
     * Set the destination directory into which the VSL result
     * files should be copied to
     * @param dir the name of the destination directory
     */
    public void setDestdir(File dir) {
        destDir = dir;
    }

    /**
     * Allow people to set the default output file extension
     * @param extension
     */
    public void setExtension(String extension) {
        this.extension = extension;
    }

    /**
     * Allow people to set the path to the .vsl file
     * @param style
     */
    public void setStyle(String style) {
        this.style = style;
    }

    /**
     * Allow people to set the path to the project.xml file
     * @param projectAttribute
     */
    public void setProjectFile(String projectAttribute) {
        this.projectAttribute = projectAttribute;
    }

    /**
     * Set the path to the templates.
     * The way it works is this:
     * If you have a Velocity.properties file defined, this method
     * will <strong>override</strong> whatever is set in the
     * Velocity.properties file. This allows one to not have to define
     * a Velocity.properties file, therefore using Velocity's defaults
     * only.
     * @param templatePath
     */

    public void setTemplatePath(File templatePath) {
        try {
            this.templatePath = templatePath.getCanonicalPath();
        } catch (java.io.IOException ioe) {
            throw new BuildException(ioe);
        }
    }

    /**
     * Allow people to set the path to the velocity.properties file
     * This file is found relative to the path where the JVM was run.
     * For example, if build.sh was executed in the ./build directory,
     * then the path would be relative to this directory.
     * This is optional based on the setting of setTemplatePath().
     * @param velocityPropertiesFile
     */
    public void setVelocityPropertiesFile(File velocityPropertiesFile) {
        this.velocityPropertiesFile = velocityPropertiesFile;
    }

    /**
     * Turn on/off last modified checking. by default, it is on.
     * @param lastmod
     */
    public void setLastModifiedCheck(String lastmod) {
        if (lastmod.equalsIgnoreCase("false") || lastmod.equalsIgnoreCase("no")
                || lastmod.equalsIgnoreCase("off")) {
            this.lastModifiedCheck = false;
        }
    }

    /**
     * Main body of the application
     * @throws BuildException
     */
    public void execute() throws BuildException {
        DirectoryScanner scanner;
        String[] list;

        if (baseDir == null) {
            baseDir = project.resolveFile(".");
        }
        if (destDir == null) {
            String msg = "destdir attribute must be set!";
            throw new BuildException(msg);
        }
        if (style == null) {
            throw new BuildException("style attribute must be set!");
        }

        if (velocityPropertiesFile == null) {
            velocityPropertiesFile = new File("velocity.properties");
        }

        /*
         * If the props file doesn't exist AND a templatePath hasn't
         * been defined, then throw the exception.
         */
        if (!velocityPropertiesFile.exists() && templatePath == null) {
            throw new BuildException("No template path and could not " + "locate velocity.properties file: "
                    + velocityPropertiesFile.getAbsolutePath());
        }

        log("Transforming into: " + destDir.getAbsolutePath(), Project.MSG_INFO);

        // projectFile relative to baseDir
        if (projectAttribute != null && projectAttribute.length() > 0) {
            projectFile = new File(baseDir, projectAttribute);
            if (projectFile.exists()) {
                projectFileLastModified = projectFile.lastModified();
            } else {
                log("Project file is defined, but could not be located: " + projectFile.getAbsolutePath(),
                        Project.MSG_INFO);
                projectFile = null;
            }
        }

        Document projectDocument = null;
        try {
            if (velocityPropertiesFile.exists()) {
                String file = velocityPropertiesFile.getAbsolutePath();
                ExtendedProperties config = new ExtendedProperties(file);
                ve.setExtendedProperties(config);
            }

            // override the templatePath if it exists
            if (templatePath != null && templatePath.length() > 0) {
                ve.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, templatePath);
            }

            ve.init();

            // get the last modification of the VSL stylesheet
            styleSheetLastModified = ve.getTemplate(style).getLastModified();

            // Build the Project file document
            if (projectFile != null) {
                projectDocument = builder.build(projectFile);
            }
        } catch (Exception e) {
            log("Error: " + e.toString(), Project.MSG_INFO);
            throw new BuildException(e);
        }

        // find the files/directories
        scanner = getDirectoryScanner(baseDir);

        // get a list of files to work on
        list = scanner.getIncludedFiles();
        for (int i = 0; i < list.length; ++i) {
            process(list[i], projectDocument);
        }

    }

    /**
     * Process an XML file using Velocity
     */
    private void process(String xmlFile, Document projectDocument) throws BuildException {
        File outFile = null;
        File inFile = null;
        Writer writer = null;
        try {
            // the current input file relative to the baseDir
            inFile = new File(baseDir, xmlFile);
            // the output file relative to basedir
            outFile = new File(destDir, xmlFile.substring(0, xmlFile.lastIndexOf('.')) + extension);

            // only process files that have changed
            if (lastModifiedCheck == false || (inFile.lastModified() > outFile.lastModified()
                    || styleSheetLastModified > outFile.lastModified()
                    || projectFileLastModified > outFile.lastModified()
                    || userContextsModifed(outFile.lastModified()))) {
                ensureDirectoryFor(outFile);

                //-- command line status
                log("Input:  " + xmlFile, Project.MSG_INFO);

                // Build the JDOM Document
                Document root = builder.build(inFile);

                // Shove things into the Context
                VelocityContext context = new VelocityContext();

                /*
                 *  get the property TEMPLATE_ENCODING
                 *  we know it's a string...
                 */
                String encoding = (String) ve.getProperty(RuntimeConstants.OUTPUT_ENCODING);
                if (encoding == null || encoding.length() == 0 || encoding.equals("8859-1")
                        || encoding.equals("8859_1")) {
                    encoding = "ISO-8859-1";
                }

                Format f = Format.getRawFormat();
                f.setEncoding(encoding);

                OutputWrapper ow = new OutputWrapper(f);

                context.put("root", root.getRootElement());
                context.put("xmlout", ow);
                context.put("relativePath", getRelativePath(xmlFile));
                context.put("treeWalk", new TreeWalker());
                context.put("xpath", new XPathTool());
                context.put("escape", new Escape());
                context.put("date", new java.util.Date());

                /**
                 * only put this into the context if it exists.
                 */
                if (projectDocument != null) {
                    context.put("project", projectDocument.getRootElement());
                }

                /**
                 *  Add the user subcontexts to the to context
                 */
                for (Iterator iter = contexts.iterator(); iter.hasNext();) {
                    Context subContext = (Context) iter.next();
                    if (subContext == null) {
                        throw new BuildException("Found an undefined SubContext!");
                    }

                    if (subContext.getContextDocument() == null) {
                        throw new BuildException("Could not build a subContext for " + subContext.getName());
                    }

                    context.put(subContext.getName(), subContext.getContextDocument().getRootElement());
                }

                /**
                 * Process the VSL template with the context and write out
                 * the result as the outFile.
                 */
                writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), encoding));

                /**
                 * get the template to process
                 */
                Template template = ve.getTemplate(style);
                template.merge(context, writer);

                log("Output: " + outFile, Project.MSG_INFO);
            }
        } catch (JDOMException e) {
            outFile.delete();

            if (e.getCause() != null) {
                Throwable rootCause = e.getCause();
                if (rootCause instanceof SAXParseException) {
                    System.out.println("");
                    System.out.println("Error: " + rootCause.getMessage());
                    System.out.println("       Line: " + ((SAXParseException) rootCause).getLineNumber()
                            + " Column: " + ((SAXParseException) rootCause).getColumnNumber());
                    System.out.println("");
                } else {
                    rootCause.printStackTrace();
                }
            } else {
                e.printStackTrace();
            }
        } catch (Throwable e) {
            if (outFile != null) {
                outFile.delete();
            }
            e.printStackTrace();
        } finally {
            if (writer != null) {
                try {
                    writer.flush();
                } catch (IOException e) {
                    // Do nothing
                }

                try {
                    writer.close();
                } catch (IOException e) {
                    // Do nothing
                }
            }
        }
    }

    /**
     * Hacky method to figure out the relative path
     * that we are currently in. This is good for getting
     * the relative path for images and anchor's.
     */
    private String getRelativePath(String file) {
        if (file == null || file.length() == 0)
            return "";
        StringTokenizer st = new StringTokenizer(file, "/\\");
        // needs to be -1 cause ST returns 1 even if there are no matches. huh?
        int slashCount = st.countTokens() - 1;
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < slashCount; i++) {
            sb.append("../");
        }

        if (sb.toString().length() > 0) {
            return StringUtils.chop(sb.toString(), 1);
        }

        return ".";
    }

    /**
     * create directories as needed
     */
    private void ensureDirectoryFor(File targetFile) throws BuildException {
        File directory = new File(targetFile.getParent());
        if (!directory.exists()) {
            if (!directory.mkdirs()) {
                throw new BuildException("Unable to create directory: " + directory.getAbsolutePath());
            }
        }
    }

    /**
     * Check to see if user context is modified.
     */
    private boolean userContextsModifed(long lastModified) {
        for (Iterator iter = contexts.iterator(); iter.hasNext();) {
            AnakiaTask.Context ctx = (AnakiaTask.Context) iter.next();
            if (ctx.getLastModified() > lastModified) {
                return true;
            }
        }
        return false;
    }

    /**
     * Create a new context.
     * @return A new context.
     */
    public Context createContext() {
        Context context = new Context();
        contexts.add(context);
        return context;
    }

    /**
     * A context implementation that loads all values from an XML file.
     */
    public class Context {

        private String name;
        private Document contextDoc = null;
        private String file;

        /**
         * Public constructor.
         */
        public Context() {
        }

        /**
         * Get the name of the context.
         * @return The name of the context.
         */
        public String getName() {
            return name;
        }

        /**
         * Set the name of the context.
         * @param name
         *
         * @throws IllegalArgumentException if a reserved word is used as a
         * name, specifically any of "relativePath", "treeWalk", "xpath",
         * "escape", "date", or "project"
         */
        public void setName(String name) {
            if (name.equals("relativePath") || name.equals("treeWalk") || name.equals("xpath")
                    || name.equals("escape") || name.equals("date") || name.equals("project")) {

                throw new IllegalArgumentException("Context name '" + name + "' is reserved by Anakia");
            }

            this.name = name;
        }

        /**
         * Build the context based on a file path.
         * @param file
         */
        public void setFile(String file) {
            this.file = file;
        }

        /**
         * Retrieve the time the source file was last modified.
         * @return The time the source file was last modified.
         */
        public long getLastModified() {
            return new File(baseDir, file).lastModified();
        }

        /**
         * Retrieve the context document object.
         * @return The context document object.
         */
        public Document getContextDocument() {
            if (contextDoc == null) {
                File contextFile = new File(baseDir, file);

                try {
                    contextDoc = builder.build(contextFile);
                } catch (Exception e) {
                    throw new BuildException(e);
                }
            }
            return contextDoc;
        }
    }

}