ingo@621: package de.intevation.gnv.utils;
ingo@621: 
sascha@779: import de.intevation.artifactdatabase.Config;
sascha@779: import de.intevation.artifactdatabase.XMLUtils;
sascha@779: 
sascha@779: import de.intevation.artifacts.ArtifactNamespaceContext;
sascha@779: 
sascha@779: import de.intevation.gnv.wms.LayerInfo;
sascha@779: 
ingo@622: import java.io.File;
ingo@622: import java.io.FileNotFoundException;
ingo@622: import java.io.FileWriter;
ingo@622: import java.io.IOException;
ingo@622: import java.io.StringWriter;
ingo@622: import java.io.Writer;
sascha@779: 
ingo@622: import java.util.ArrayList;
ingo@622: import java.util.Date;
ingo@622: import java.util.List;
ingo@622: 
ingo@622: import javax.xml.xpath.XPathConstants;
ingo@622: 
ingo@621: import org.apache.log4j.Logger;
sascha@779: 
ingo@622: import org.apache.velocity.Template;
ingo@622: import org.apache.velocity.VelocityContext;
sascha@779: 
ingo@622: import org.apache.velocity.app.VelocityEngine;
sascha@779: 
ingo@622: import org.w3c.dom.Document;
ingo@622: import org.w3c.dom.Node;
ingo@622: import org.w3c.dom.NodeList;
ingo@621: 
ingo@621: /**
ingo@806:  * This class iterates over a bunch of directories, searches for meta
ingo@806:  * information coresponding to shapefiles and creates a mapfile which is used by
ingo@806:  * a <i>MapServer</i>.
sascha@807:  *
sascha@780:  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
ingo@621:  */
ingo@621: public class MapfileGenerator
ingo@621: extends Thread
ingo@621: {
ingo@806:     /**
ingo@806:      * The configured template path.
ingo@806:      */
ingo@622:     public static final String TEMPLATE_PATH =
ingo@625:         "/artifact-database/gnv/map-generator/templates/path/text()";
ingo@622: 
ingo@806:     /**
ingo@806:      * The configured template mapfile.
ingo@806:      */
ingo@622:     public static final String TEMPLATE_MAPFILE =
ingo@625:         "/artifact-database/gnv/map-generator/templates/maptemplate/text()";
ingo@621: 
ingo@806:     /**
ingo@806:      * The configured mapfile path.
ingo@806:      */
ingo@621:     public static final String MAPFILE_PATH =
ingo@625:         "/artifact-database/gnv/map-generator/mapfile/@path";
ingo@622: 
ingo@806:     /**
ingo@806:      * The configured shapefile base directory where the subdirectories for each
ingo@806:      * artifact are stored.
ingo@806:      */
ingo@622:     public static final String SHAPEFILE_BASE_DIR =
ingo@625:         "/artifact-database/gnv/shapefile-directory/@path";
ingo@622: 
ingo@806:     /**
ingo@806:      * The configured velocity logfile.
ingo@806:      */
ingo@624:     public static final String VELOCITY_LOGFILE =
ingo@624:         "/artifact-database/velocity/logfile/@path";
ingo@624: 
ingo@806:     /**
ingo@806:      * The name of the file storing meta information coresponding to shapefiles.
ingo@806:      */
ingo@622:     public static final String META_FILE_NAME = "meta.xml";
ingo@622: 
ingo@806:     /**
ingo@806:      * The XPath to layer nodes in the meta information xml file.
ingo@806:      */
ingo@622:     public static final String XPATH_LAYER        = "/art:meta/art:layer";
sascha@807: 
ingo@622:     public static final String XPATH_LAYER_NAME   = "art:name";
ingo@730:     public static final String XPATH_LAYER_TITLE  = "art:title";
ingo@622:     public static final String XPATH_LAYER_TYPE   = "art:type";
ingo@622:     public static final String XPATH_LAYER_DATA   = "art:data";
ingo@622:     public static final String XPATH_LAYER_STATUS = "art:status";
ingo@622:     public static final String XPATH_LAYER_MODEL  = "art:model";
ingo@621: 
ingo@621:     protected static final long SLEEPTIME = 10 * 1000L; // 10 seconds
ingo@621: 
ingo@621:     private static Logger logger = Logger.getLogger(MapfileGenerator.class);
ingo@621: 
ingo@621:     private static MapfileGenerator instance;
ingo@621: 
ingo@625:     private File   mapfile;
ingo@625:     private String shapefileDirectory;
ingo@625:     private String templatePath;
ingo@625:     private String velocityLogfile;
ingo@625: 
ingo@622:     private VelocityEngine velocityEngine;
ingo@621:     private boolean lock[];
ingo@621: 
ingo@621: 
ingo@621: 
ingo@621:     private MapfileGenerator() {
ingo@621:         lock = new boolean[1];
ingo@621:     }
ingo@621: 
ingo@621: 
ingo@806:     /**
ingo@806:      * A main method which can be used to create a mapfile without a running
ingo@806:      * artifact server.<br>
ingo@806:      * <b>NOTE:</b> This method is not implemented yet!
sascha@807:      *
ingo@806:      * @param args Arguments.
ingo@806:      */
ingo@621:     public static void main(String[] args) {
ingo@622:         // TODO IMPLEMENT ME
ingo@621:     }
ingo@621: 
ingo@621: 
ingo@806:     /**
ingo@806:      * Returns the instance of this generator.
ingo@806:      *
ingo@806:      * @return the instance.
ingo@806:      */
ingo@621:     public static synchronized MapfileGenerator getInstance() {
ingo@621:         if (instance == null) {
ingo@621:             instance = new MapfileGenerator();
ingo@621:             instance.setDaemon(true);
ingo@621:             instance.start();
ingo@621:         }
ingo@621: 
ingo@621:         return instance;
ingo@621:     }
ingo@621: 
ingo@621: 
ingo@806:     /**
ingo@806:      * Triggers the mapfile generation process.
ingo@806:      */
ingo@621:     public void update() {
ingo@621:         synchronized (lock) {
ingo@622:             logger.debug("update");
ingo@621:             lock[0] = true;
ingo@621:             lock.notify();
ingo@621:         }
ingo@621:     }
ingo@621: 
ingo@621: 
ingo@806:     /**
ingo@806:      * Thread to generate a mapfile.
ingo@806:      */
ingo@806:     @Override
ingo@621:     public void run() {
ingo@622:         logger.debug("Start MapfileGenerator thread...");
ingo@621:         try {
ingo@621:             for (;;) {
ingo@621:                 synchronized (lock) {
ingo@621:                     while (!lock[0]) {
ingo@621:                         lock.wait(SLEEPTIME);
ingo@621:                     }
ingo@621:                     lock[0] = false;
ingo@621:                 }
ingo@622: 
ingo@622:                 logger.debug("Start sync process now...");
ingo@621:                 generate();
ingo@621:             }
ingo@621:         }
ingo@621:         catch (InterruptedException ie) {
ingo@622:             logger.debug("MapfileGenerator thread got an interrupt.");
ingo@622:         }
ingo@622:         finally {
ingo@622:             logger.debug("THREAD END");
ingo@621:         }
ingo@621:     }
sascha@778: 
ingo@806:     /**
ingo@806:      * Method to check the existance of a template file.
ingo@806:      *
ingo@806:      * @param templateID The name of a template.
ingo@806:      * @return true, of the template exists - otherwise false.
ingo@806:      */
tim@724:     public boolean templateExists(String templateID){
tim@724:         Template template = getTemplateByName(templateID);
tim@724:         return template != null;
tim@724:     }
ingo@621: 
ingo@621: 
ingo@806:     /**
ingo@806:      * Method which starts searching for meta information file and mapfile
ingo@806:      * generation.
ingo@806:      */
ingo@622:     protected void generate() {
ingo@622:         File basedir       = new File(getShapefileBaseDir());
ingo@622:         List layers        = new ArrayList();
ingo@622:         searchMetaInformation(basedir, layers);
ingo@622:         writeMapfile(layers);
ingo@622:     }
ingo@622: 
ingo@622: 
ingo@806:     /**
ingo@806:      * Returns the VelocityEngine used for the template mechanism.
ingo@806:      *
ingo@806:      * @return the velocity engine.
ingo@806:      */
ingo@622:     protected VelocityEngine getVelocityEngine() {
ingo@622:         if (velocityEngine == null) {
ingo@622:             velocityEngine = new VelocityEngine();
ingo@622:             try {
ingo@622:                 setupVelocity(velocityEngine);
ingo@622:             }
ingo@622:             catch (Exception e) {
ingo@622:                 logger.error(e, e);
ingo@622:                 return null;
ingo@622:             }
ingo@622:         }
ingo@622:         return velocityEngine;
ingo@622:     }
ingo@622: 
ingo@622: 
ingo@806:     /**
ingo@806:      * Initialize velocity.
ingo@806:      *
ingo@806:      * @param engine Velocity engine.
ingo@806:      * @throws Exception if an error occured while initializing velocity.
ingo@806:      */
ingo@622:     protected void setupVelocity(VelocityEngine engine)
ingo@622:     throws Exception
ingo@622:     {
ingo@624:         engine.setProperty(
ingo@622:             "input.encoding",
ingo@622:             "UTF-8");
ingo@622: 
ingo@624:         engine.setProperty(
ingo@624:             VelocityEngine.RUNTIME_LOG,
ingo@624:             getVelocityLogfile());
ingo@622: 
ingo@624:         engine.setProperty(
ingo@624:             "resource.loader",
ingo@624:             "file");
ingo@622: 
ingo@624:         engine.setProperty(
ingo@624:             "file.resource.loader.path",
ingo@624:             getTemplateBaseDir());
ingo@624: 
ingo@624:         engine.init();
ingo@624:     }
ingo@624: 
ingo@624: 
ingo@806:     /**
ingo@806:      * Returns the path specifying the logfile for velocity.
ingo@806:      *
ingo@806:      * @return Velocity logfile path.
ingo@806:      */
ingo@624:     protected String getVelocityLogfile() {
ingo@624:         if (velocityLogfile == null)
ingo@624:             velocityLogfile = Config.getStringXPath(VELOCITY_LOGFILE);
ingo@624: 
ingo@624:         return velocityLogfile;
ingo@622:     }
ingo@622: 
ingo@622: 
ingo@806:     /**
ingo@806:      * Returns the base directory storing the templates.
ingo@806:      *
ingo@806:      * @return the template base directory.
ingo@806:      */
ingo@622:     protected String getTemplateBaseDir() {
ingo@625:         if (templatePath == null) {
ingo@624:             templatePath = Config.getStringXPath(TEMPLATE_PATH);
ingo@625:             templatePath = Config.replaceConfigDir(templatePath);
ingo@625:         }
ingo@624: 
ingo@624:         return templatePath;
ingo@622:     }
ingo@622: 
ingo@622: 
ingo@806:     /**
ingo@806:      * Returns a template specified by <i>model</i>.
ingo@806:      *
ingo@806:      * @param model The name of the template.
ingo@806:      * @return a template.
ingo@806:      */
ingo@622:     protected Template getTemplateByName(String model) {
ingo@622:         if (model.indexOf(".vm") < 0) {
ingo@622:             model = model.concat(".vm");
ingo@622:         }
ingo@622: 
ingo@622:         try {
ingo@622:             VelocityEngine engine = getVelocityEngine();
ingo@622:             if (engine == null) {
ingo@622:                 logger.error("Error while fetching VelocityEngine.");
ingo@622:                 return null;
ingo@622:             }
ingo@622: 
ingo@624:             return engine.getTemplate(model);
ingo@622:         }
ingo@622:         catch (Exception e) {
ingo@622:             logger.warn(e, e);
ingo@622:         }
ingo@622: 
ingo@622:         return null;
ingo@622:     }
ingo@622: 
ingo@622: 
ingo@806:     /**
ingo@806:      * Returns the mapfile  template.
ingo@806:      *
ingo@806:      * @return the mapfile template.
ingo@806:      * @throws Exception if an error occured while reading the configuration.
ingo@806:      */
ingo@622:     protected Template getMapfileTemplate()
ingo@622:     throws Exception
ingo@622:     {
ingo@622:         String mapfileName = Config.getStringXPath(TEMPLATE_MAPFILE);
ingo@622:         return getTemplateByName(mapfileName);
ingo@622:     }
ingo@622: 
ingo@622: 
ingo@806:     /**
ingo@806:      * Returns the base directory storing the shapefiles.
ingo@806:      *
ingo@806:      * @return the shapefile base directory.
ingo@806:      */
ingo@622:     protected String getShapefileBaseDir() {
ingo@625:         if (shapefileDirectory == null) {
ingo@625:             shapefileDirectory = Config.getStringXPath(SHAPEFILE_BASE_DIR);
ingo@625:             shapefileDirectory = Config.replaceConfigDir(shapefileDirectory);
ingo@625:         }
ingo@625: 
ingo@625:         return shapefileDirectory;
ingo@625:     }
ingo@625: 
ingo@625: 
ingo@806:     /**
ingo@806:      * Returns the mapfile.
sascha@807:      *
ingo@806:      * @return the mapfile.
ingo@806:      */
ingo@625:     protected File getMapfile() {
ingo@625:         if (mapfile == null) {
ingo@625:             String tmp = Config.getStringXPath(MAPFILE_PATH);
ingo@625:             tmp        = Config.replaceConfigDir(tmp);
ingo@625:             mapfile    = new File(tmp);
ingo@625:         }
ingo@625: 
ingo@625:         return mapfile;
ingo@622:     }
ingo@622: 
ingo@622: 
ingo@806:     /**
ingo@806:      * Search for meta information file in <i>file</i> and store the layer
ingo@806:      * information in <i>store</i> if a meta file was found.
sascha@807:      *
ingo@806:      * @param file The root directory to be searched for meta files.
ingo@806:      * @param store A list storing <code>LayerInfo</code> objects.
ingo@806:      */
ingo@622:     protected void searchMetaInformation(File file, List store) {
ingo@622:         if (file.isDirectory()) {
ingo@622:             File[] files = file.listFiles();
ingo@622: 
ingo@622:             if (files != null && files.length != 0) {
ingo@622:                 int size = files.length;
ingo@622:                 for (File tmp: files) {
ingo@622:                     searchMetaInformation(tmp, store);
ingo@622:                 }
ingo@622:             }
ingo@622:         }
ingo@622:         else if (file.getName().equals(META_FILE_NAME)) {
ingo@622:             LayerInfo[] info = parseMeta(file);
ingo@622: 
ingo@622:             int infoSize = info.length;
ingo@622:             for (int j = 0; j < infoSize; j++) {
ingo@622:                 if (info[j] != null) {
ingo@622:                     store.add(info[j]);
ingo@622:                 }
ingo@622:             }
ingo@622:         }
ingo@622:     }
ingo@622: 
ingo@622: 
ingo@806:     /**
ingo@806:      * Parses a meta information file and returns necessary information as
ingo@806:      * <code>LayerInfo</code> array.
ingo@806:      *
ingo@806:      * @param file Meta information file.
ingo@806:      * @return Array of LayerInfo objects.
ingo@806:      */
ingo@622:     protected LayerInfo[] parseMeta(File file) {
ingo@622:         Document meta = XMLUtils.parseDocument(file);
ingo@622:         List layers   = new ArrayList();
ingo@622: 
ingo@622:         NodeList layerset = (NodeList) XMLUtils.xpath(
ingo@622:             meta,
ingo@622:             XPATH_LAYER,
ingo@622:             XPathConstants.NODESET,
ingo@622:             ArtifactNamespaceContext.INSTANCE);
ingo@622: 
ingo@622:         int size = layerset.getLength();
ingo@622:         for (int i = 0; i < size; i++) {
ingo@622:             LayerInfo info = parseLayer(layerset.item(i));
ingo@622: 
ingo@622:             if (info != null && !info.isEmpty() && !info.isBroken()) {
ingo@622:                 layers.add(info);
ingo@622:             }
ingo@622:             else {
ingo@622:                 logger.warn("Found broken LayerInfo object.");
ingo@622:             }
ingo@622:         }
ingo@622: 
ingo@622:         return (LayerInfo[]) layers.toArray(new LayerInfo[layers.size()]);
ingo@622:     }
ingo@622: 
ingo@622: 
ingo@806:     /**
ingo@806:      * Parses a node storing layer information and returns them.
ingo@806:      *
ingo@806:      * @param layer Node storing information about a layer.
ingo@806:      * @return a LayerInfo object.
ingo@806:      */
ingo@622:     protected LayerInfo parseLayer(Node layer) {
ingo@622:         LayerInfo info  = new LayerInfo();
ingo@622: 
ingo@622:         String name = parseLayerAttr(layer, XPATH_LAYER_NAME);
ingo@622:         if (name != null && !name.equals("")) {
ingo@622:             info.setName(name);
ingo@622:         }
ingo@622: 
ingo@730:         String title = parseLayerAttr(layer, XPATH_LAYER_TITLE);
ingo@730:         if (title != null && !title.equals("")) {
ingo@730:             info.setTitle(title);
ingo@730:         }
ingo@730:         else {
ingo@730:             info.setTitle(name);
ingo@730:         }
ingo@730: 
ingo@622:         String model = parseLayerAttr(layer, XPATH_LAYER_MODEL);
ingo@622:         if (model != null && !model.equals("")) {
ingo@622:             info.setModel(model);
ingo@622:         }
ingo@622: 
ingo@622:         String type = parseLayerAttr(layer, XPATH_LAYER_TYPE);
ingo@622:         if (type != null && !type.equals("")) {
ingo@622:             info.setType(type);
ingo@622:         }
ingo@622: 
ingo@622:         String data = parseLayerAttr(layer, XPATH_LAYER_DATA);
ingo@622:         if (data != null && !data.equals("")) {
ingo@622:             info.setData(data);
ingo@622:         }
ingo@622: 
ingo@622:         String status = parseLayerAttr(layer, XPATH_LAYER_STATUS);
ingo@622:         if (status != null && !status.equals("")) {
ingo@622:             info.setStatus(status);
ingo@622:         }
ingo@622: 
ingo@622:         return info;
ingo@622:     }
ingo@622: 
ingo@622: 
ingo@806:     /**
ingo@806:      * Parses attributes in layer nodes.
ingo@806:      *
ingo@806:      * @param node A node containing layer information.
ingo@806:      * @param xpath XPath specifying an attribute.
ingo@806:      * @return Attribute as string.
ingo@806:      */
ingo@622:     protected String parseLayerAttr(Node node, String xpath) {
ingo@622:         return (String) XMLUtils.xpath(
ingo@622:             node,
ingo@622:             xpath,
ingo@622:             XPathConstants.STRING,
ingo@622:             ArtifactNamespaceContext.INSTANCE);
ingo@622:     }
ingo@622: 
ingo@622: 
ingo@806:     /**
ingo@806:      * Creates a mapfile with the layer information stored in <i>layers</i>.
sascha@807:      *
ingo@806:      * @param layers Layer information.
ingo@806:      */
ingo@622:     protected void writeMapfile(List layers) {
ingo@622:         String tmpMapName = "mapfile" + new Date().getTime();
ingo@622: 
ingo@622:         int layersize         = layers.size();
ingo@622:         StringBuilder sb      = new StringBuilder();
ingo@622:         StringWriter sw       = null;
ingo@622:         LayerInfo info        = null;
ingo@622: 
ingo@622:         for (int i = 0; i < layersize; i++) {
ingo@622:             sw   = new StringWriter();
ingo@622:             info = (LayerInfo) layers.get(i);
ingo@622: 
ingo@622:             Template layerTemplate  = getTemplateByName(info.getModel());
ingo@622:             VelocityContext context = new VelocityContext();
ingo@622:             context.put("info", info);
ingo@622: 
ingo@622:             try {
ingo@622:                 layerTemplate.merge(context, sw);
ingo@622:                 sb.append(sw.toString());
ingo@622:             }
ingo@622:             catch (IOException ioe) {
ingo@622:                 logger.warn("Error while filling layer template.");
ingo@622:                 logger.warn(ioe, ioe);
ingo@622:             }
ingo@622:         }
ingo@622: 
ingo@625:         File   map    = getMapfile();
ingo@622:         Writer writer = null;
ingo@622:         File   tmp    = null;
ingo@622: 
ingo@622:         try {
ingo@625:             tmp = new File(map.getParent(), tmpMapName);
ingo@622: 
ingo@622:             tmp.createNewFile();
ingo@622:             writer   = new FileWriter(tmp);
ingo@622: 
ingo@622:             VelocityContext context = new VelocityContext();
ingo@622:             context.put("LAYERS", sb.toString());
ingo@622: 
ingo@622:             Template mapTemplate = getMapfileTemplate();
ingo@622:             if (mapTemplate == null) {
ingo@622:                 logger.warn("No mapfile template found.");
ingo@622:                 return;
ingo@622:             }
ingo@622: 
ingo@622:             mapTemplate.merge(context, writer);
ingo@622: 
ingo@622:             // we need to create a temporary mapfile first und rename it into
ingo@622:             // real mapfile because we don't run into race conditions on this
ingo@622:             // way. (iw)
ingo@622:             tmp.renameTo(map);
ingo@622:         }
ingo@622:         catch (FileNotFoundException fnfe) {
ingo@622:             logger.error(fnfe, fnfe);
ingo@622:         }
ingo@622:         catch (IOException ioe) {
ingo@622:             logger.error(ioe, ioe);
ingo@622:         }
ingo@622:         catch (Exception e) {
ingo@622:             logger.error(e, e);
ingo@622:         }
ingo@622:         finally {
ingo@622:             try {
ingo@622:                 // close file writer
ingo@622:                 if (writer != null) {
ingo@622:                     writer.close();
ingo@622:                 }
ingo@622: 
ingo@622:                 // remove temporary mapfile if an error occured and it still
ingo@622:                 // exists
ingo@622:                 if (tmp.exists()) {
ingo@622:                     tmp.delete();
ingo@622:                 }
ingo@622:             }
ingo@622:             catch (IOException ioe) {
ingo@622:                 logger.debug(ioe, ioe);
ingo@622:             }
ingo@622:         }
ingo@621:     }
ingo@621: }
sascha@798: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :