view gnv-artifacts/src/main/java/de/intevation/gnv/utils/MapfileGenerator.java @ 859:3fbabd4803d7

ISSUE252 Make it possible to export more than one Layer gnv-artifacts/trunk@983 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Tim Englich <tim.englich@intevation.de>
date Mon, 26 Apr 2010 09:09:20 +0000
parents 47578d91c4f0
children f953c9a559d8
line wrap: on
line source
package de.intevation.gnv.utils;

import de.intevation.artifactdatabase.Config;
import de.intevation.artifactdatabase.XMLUtils;

import de.intevation.artifacts.ArtifactNamespaceContext;

import de.intevation.gnv.wms.LayerInfo;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.xml.xpath.XPathConstants;

import org.apache.log4j.Logger;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;

import org.apache.velocity.app.VelocityEngine;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * This class iterates over a bunch of directories, searches for meta
 * information coresponding to shapefiles and creates a mapfile which is used by
 * a <i>MapServer</i>.
 *
 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
 */
public class MapfileGenerator
extends Thread
{
    /**
     * The configured template path.
     */
    public static final String TEMPLATE_PATH =
        "/artifact-database/gnv/map-generator/templates/path/text()";

    /**
     * The configured template mapfile.
     */
    public static final String TEMPLATE_MAPFILE =
        "/artifact-database/gnv/map-generator/templates/maptemplate/text()";

    /**
     * The configured mapfile path.
     */
    public static final String MAPFILE_PATH =
        "/artifact-database/gnv/map-generator/mapfile/@path";

    /**
     * The configured shapefile base directory where the subdirectories for each
     * artifact are stored.
     */
    public static final String SHAPEFILE_BASE_DIR =
        "/artifact-database/gnv/shapefile-directory/@path";

    /**
     * The configured velocity logfile.
     */
    public static final String VELOCITY_LOGFILE =
        "/artifact-database/velocity/logfile/@path";

    /**
     * The URL for calling the mapserver
     */
    public static final String MAPSERVER_URL =
        "/artifact-database/mapserver/server/@path";

    /**
     * The name of the file storing meta information coresponding to shapefiles.
     */
    public static final String META_FILE_NAME = "meta.xml";

    /**
     * The XPath to layer nodes in the meta information xml file.
     */
    public static final String XPATH_LAYER        = "/art:meta/art:layer";

    public static final String XPATH_LAYER_NAME   = "art:name";
    public static final String XPATH_LAYER_TITLE  = "art:title";
    public static final String XPATH_LAYER_TYPE   = "art:type";
    public static final String XPATH_LAYER_DATA   = "art:data";
    public static final String XPATH_LAYER_STATUS = "art:status";
    public static final String XPATH_LAYER_MODEL  = "art:model";

    protected static final long SLEEPTIME = 10 * 1000L; // 10 seconds

    private static Logger logger = Logger.getLogger(MapfileGenerator.class);

    private static MapfileGenerator instance;

    private File   mapfile;
    private String mapServerUrl;
    private String shapefileDirectory;
    private String templatePath;
    private String velocityLogfile;

    private VelocityEngine velocityEngine;
    private boolean lock[];



    private MapfileGenerator() {
        lock = new boolean[1];
    }


    /**
     * A main method which can be used to create a mapfile without a running
     * artifact server.<br>
     * <b>NOTE:</b> This method is not implemented yet!
     *
     * @param args Arguments.
     */
    public static void main(String[] args) {
        // TODO IMPLEMENT ME
    }


    /**
     * Returns the instance of this generator.
     *
     * @return the instance.
     */
    public static synchronized MapfileGenerator getInstance() {
        if (instance == null) {
            instance = new MapfileGenerator();
            instance.setDaemon(true);
            instance.start();
        }

        return instance;
    }


    /**
     * Triggers the mapfile generation process.
     */
    public void update() {
        synchronized (lock) {
            logger.debug("update");
            lock[0] = true;
            lock.notify();
        }
    }


    /**
     * Thread to generate a mapfile.
     */
    @Override
    public void run() {
        logger.debug("Start MapfileGenerator thread...");
        try {
            for (;;) {
                synchronized (lock) {
                    while (!lock[0]) {
                        lock.wait(SLEEPTIME);
                    }
                    lock[0] = false;
                }

                logger.debug("Start sync process now...");
                generate();
            }
        }
        catch (InterruptedException ie) {
            logger.debug("MapfileGenerator thread got an interrupt.");
        }
        finally {
            logger.debug("THREAD END");
        }
    }

    /**
     * Method to check the existance of a template file.
     *
     * @param templateID The name of a template.
     * @return true, of the template exists - otherwise false.
     */
    public boolean templateExists(String templateID){
        Template template = getTemplateByName(templateID);
        return template != null;
    }


    /**
     * Method which starts searching for meta information file and mapfile
     * generation.
     */
    protected void generate() {
        File basedir       = new File(getShapefileBaseDir());
        List layers        = new ArrayList();
        searchMetaInformation(basedir, layers);
        writeMapfile(layers);
    }


    /**
     * Returns the VelocityEngine used for the template mechanism.
     *
     * @return the velocity engine.
     */
    protected VelocityEngine getVelocityEngine() {
        if (velocityEngine == null) {
            velocityEngine = new VelocityEngine();
            try {
                setupVelocity(velocityEngine);
            }
            catch (Exception e) {
                logger.error(e, e);
                return null;
            }
        }
        return velocityEngine;
    }


    /**
     * Initialize velocity.
     *
     * @param engine Velocity engine.
     * @throws Exception if an error occured while initializing velocity.
     */
    protected void setupVelocity(VelocityEngine engine)
    throws Exception
    {
        engine.setProperty(
            "input.encoding",
            "UTF-8");

        engine.setProperty(
            VelocityEngine.RUNTIME_LOG,
            getVelocityLogfile());

        engine.setProperty(
            "resource.loader",
            "file");

        engine.setProperty(
            "file.resource.loader.path",
            getTemplateBaseDir());

        engine.init();
    }


    /**
     * Returns the path specifying the logfile for velocity.
     *
     * @return Velocity logfile path.
     */
    protected String getVelocityLogfile() {
        if (velocityLogfile == null)
            velocityLogfile = Config.getStringXPath(VELOCITY_LOGFILE);

        return velocityLogfile;
    }


    /**
     * Returns the base directory storing the templates.
     *
     * @return the template base directory.
     */
    protected String getTemplateBaseDir() {
        if (templatePath == null) {
            templatePath = Config.getStringXPath(TEMPLATE_PATH);
            templatePath = Config.replaceConfigDir(templatePath);
        }

        return templatePath;
    }


    /**
     * Returns the URL for calling the MapServer.
     *
     * @return the url for calling the MapServer.
     */
    protected String getMapServerUrl() {
        if (mapServerUrl == null) {
            mapServerUrl = Config.getStringXPath(MAPSERVER_URL);
            mapServerUrl = Config.replaceConfigDir(mapServerUrl);
        }

        return mapServerUrl;
    }


    /**
     * Returns a template specified by <i>model</i>.
     *
     * @param model The name of the template.
     * @return a template.
     */
    protected Template getTemplateByName(String model) {
        if (model.indexOf(".vm") < 0) {
            model = model.concat(".vm");
        }

        try {
            VelocityEngine engine = getVelocityEngine();
            if (engine == null) {
                logger.error("Error while fetching VelocityEngine.");
                return null;
            }

            return engine.getTemplate(model);
        }
        catch (Exception e) {
            logger.warn(e, e);
        }

        return null;
    }


    /**
     * Returns the mapfile  template.
     *
     * @return the mapfile template.
     * @throws Exception if an error occured while reading the configuration.
     */
    protected Template getMapfileTemplate()
    throws Exception
    {
        String mapfileName = Config.getStringXPath(TEMPLATE_MAPFILE);
        return getTemplateByName(mapfileName);
    }


    /**
     * Returns the base directory storing the shapefiles.
     *
     * @return the shapefile base directory.
     */
    protected String getShapefileBaseDir() {
        if (shapefileDirectory == null) {
            shapefileDirectory = Config.getStringXPath(SHAPEFILE_BASE_DIR);
            shapefileDirectory = Config.replaceConfigDir(shapefileDirectory);
        }

        return shapefileDirectory;
    }


    /**
     * Returns the mapfile.
     *
     * @return the mapfile.
     */
    protected File getMapfile() {
        if (mapfile == null) {
            String tmp = Config.getStringXPath(MAPFILE_PATH);
            tmp        = Config.replaceConfigDir(tmp);
            mapfile    = new File(tmp);
        }

        return mapfile;
    }


    /**
     * Search for meta information file in <i>file</i> and store the layer
     * information in <i>store</i> if a meta file was found.
     *
     * @param file The root directory to be searched for meta files.
     * @param store A list storing <code>LayerInfo</code> objects.
     */
    protected void searchMetaInformation(File file, List store) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();

            if (files != null && files.length != 0) {
                int size = files.length;
                for (File tmp: files) {
                    searchMetaInformation(tmp, store);
                }
            }
        }
        else if (file.getName().equals(META_FILE_NAME)) {
            LayerInfo[] info = parseMeta(file);

            int infoSize = info.length;
            for (int j = 0; j < infoSize; j++) {
                if (info[j] != null) {
                    store.add(info[j]);
                }
            }
        }
    }


    /**
     * Parses a meta information file and returns necessary information as
     * <code>LayerInfo</code> array.
     *
     * @param file Meta information file.
     * @return Array of LayerInfo objects.
     */
    protected LayerInfo[] parseMeta(File file) {
        Document meta = XMLUtils.parseDocument(file);
        List layers   = new ArrayList();

        NodeList layerset = (NodeList) XMLUtils.xpath(
            meta,
            XPATH_LAYER,
            XPathConstants.NODESET,
            ArtifactNamespaceContext.INSTANCE);

        int size = layerset.getLength();
        for (int i = 0; i < size; i++) {
            LayerInfo info = parseLayer(layerset.item(i));

            if (info != null && !info.isEmpty() && !info.isBroken()) {
                layers.add(info);
            }
            else {
                logger.warn("Found broken LayerInfo object.");
            }
        }

        return (LayerInfo[]) layers.toArray(new LayerInfo[layers.size()]);
    }


    /**
     * Parses a node storing layer information and returns them.
     *
     * @param layer Node storing information about a layer.
     * @return a LayerInfo object.
     */
    protected LayerInfo parseLayer(Node layer) {
        LayerInfo info  = new LayerInfo();

        String name = parseLayerAttr(layer, XPATH_LAYER_NAME);
        if (name != null && !name.equals("")) {
            info.setName(name);
        }

        String title = parseLayerAttr(layer, XPATH_LAYER_TITLE);
        if (title != null && !title.equals("")) {
            info.setTitle(title);
        }
        else {
            info.setTitle(name);
        }

        String model = parseLayerAttr(layer, XPATH_LAYER_MODEL);
        if (model != null && !model.equals("")) {
            info.setModel(model);
        }

        String type = parseLayerAttr(layer, XPATH_LAYER_TYPE);
        if (type != null && !type.equals("")) {
            info.setType(type);
        }

        String data = parseLayerAttr(layer, XPATH_LAYER_DATA);
        if (data != null && !data.equals("")) {
            info.setData(data);
        }

        String status = parseLayerAttr(layer, XPATH_LAYER_STATUS);
        if (status != null && !status.equals("")) {
            info.setStatus(status);
        }

        return info;
    }


    /**
     * Parses attributes in layer nodes.
     *
     * @param node A node containing layer information.
     * @param xpath XPath specifying an attribute.
     * @return Attribute as string.
     */
    protected String parseLayerAttr(Node node, String xpath) {
        return (String) XMLUtils.xpath(
            node,
            xpath,
            XPathConstants.STRING,
            ArtifactNamespaceContext.INSTANCE);
    }


    /**
     * Creates a mapfile with the layer information stored in <i>layers</i>.
     *
     * @param layers Layer information.
     */
    protected void writeMapfile(List layers) {
        String tmpMapName = "mapfile" + new Date().getTime();

        int layersize         = layers.size();
        StringBuilder sb      = new StringBuilder();
        StringWriter sw       = null;
        LayerInfo info        = null;

        for (int i = 0; i < layersize; i++) {
            sw   = new StringWriter();
            info = (LayerInfo) layers.get(i);

            Template layerTemplate  = getTemplateByName(info.getModel());
            VelocityContext context = new VelocityContext();
            context.put("info", info);

            try {
                layerTemplate.merge(context, sw);
                sb.append(sw.toString());
            }
            catch (IOException ioe) {
                logger.warn("Error while filling layer template.");
                logger.warn(ioe, ioe);
            }
        }

        File   map    = getMapfile();
        Writer writer = null;
        File   tmp    = null;

        try {
            tmp = new File(map.getParent(), tmpMapName);

            tmp.createNewFile();
            writer   = new FileWriter(tmp);

            VelocityContext context = new VelocityContext();
            context.put("MAPSERVERURL", getMapServerUrl());
            context.put("LAYERS", sb.toString());

            Template mapTemplate = getMapfileTemplate();
            if (mapTemplate == null) {
                logger.warn("No mapfile template found.");
                return;
            }

            mapTemplate.merge(context, writer);

            // we need to create a temporary mapfile first und rename it into
            // real mapfile because we don't run into race conditions on this
            // way. (iw)
            tmp.renameTo(map);
        }
        catch (FileNotFoundException fnfe) {
            logger.error(fnfe, fnfe);
        }
        catch (IOException ioe) {
            logger.error(ioe, ioe);
        }
        catch (Exception e) {
            logger.error(e, e);
        }
        finally {
            try {
                // close file writer
                if (writer != null) {
                    writer.close();
                }

                // remove temporary mapfile if an error occured and it still
                // exists
                if (tmp.exists()) {
                    tmp.delete();
                }
            }
            catch (IOException ioe) {
                logger.debug(ioe, ioe);
            }
        }
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org