view gnv-artifacts/src/main/java/de/intevation/gnv/state/layer/LayerOutputState.java @ 1034:50a5ce7a47b7

Implemented an odv exporter for product type 'Horizontales Schnittprofil' (issue260). gnv-artifacts/trunk@1082 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Mon, 10 May 2010 10:29:55 +0000
parents dfd02f8d3602
children 461d4489705c
line wrap: on
line source
package de.intevation.gnv.state.layer;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;

import de.intevation.artifactdatabase.Config;
import de.intevation.artifactdatabase.XMLUtils;
import de.intevation.artifacts.ArtifactNamespaceContext;
import de.intevation.artifacts.CallContext;
import de.intevation.gnv.artifacts.context.GNVArtifactContext;
import de.intevation.gnv.geobackend.base.Result;
import de.intevation.gnv.geobackend.base.query.QueryExecutor;
import de.intevation.gnv.geobackend.base.query.QueryExecutorFactory;
import de.intevation.gnv.geobackend.base.query.exception.QueryException;
import de.intevation.gnv.state.InputData;
import de.intevation.gnv.state.OutputStateBase;
import de.intevation.gnv.state.exception.StateException;
import de.intevation.gnv.utils.ArtifactXMLUtilities;
import de.intevation.gnv.utils.ExclusiveExec;
import de.intevation.gnv.utils.FileUtils;
import de.intevation.gnv.utils.MapfileGenerator;
import de.intevation.gnv.utils.MetaWriter;
import de.intevation.gnv.utils.ShapeFileWriter;

/**
 * This <code>OutputState</code> is used for Layer-Products.
 * @author <a href="mailto:tim.englich@intevation.de">Tim Englich</a>
 *
 */
public class LayerOutputState extends OutputStateBase {

    /**
     * the logger, used to log exceptions and additonaly information
     */
    private static Logger log = Logger.getLogger(LayerOutputState.class);

    /**
     * The UID of this Class.
     */
    private static final long serialVersionUID = 9180957321704424049L;

    /**
     * The Basename of the Templates for the WMS-Exports
     */
    public static final String LAYER_MODEL    = "layer";

    /**
     * The Name of the Shapefile which will be generated.
     */
    public static final String SHAPEFILE_NAME   = "data";

    /**
     * The ID for the Query fetching the Layer from the DB
     */
    private String dataQueryID = null;

    /**
     * The ID for the Query fetching the Geometry from the DB
     * which should be used to Clip the Layerdata
     */
    private String geometryQueryID = null;

    /**
     * The ID of the Query for fetching the Columnnames of the Table which
     * should put into the Shapefile.
     */
    private String columnQueryID = null;

    /**
     * The ID of the Query for fetching the Information which kind of Geometry
     * should be put into the Shapefile.
     */
    private String geometryTypeQueryID = null;

    /**
     * The ID for the Value which will hold the Geometry-Value
     */
    private String geometryID = null;

    /**
     * Flag for synchronized Access of the Shapefile.
     */
    private Boolean shapeFileLock = new Boolean(true);

    /**
     * The Path where the Shapefile is stored.
     */
    private String shapeFilePath;

    /**
     * Constructor
     */
    public LayerOutputState() {
        super();
    }

    public void out(Document format, Collection<InputData> inputData,
                    OutputStream outputStream, String uuid,
                    CallContext callContext) throws StateException {
        log.debug("LayerOutputState.out");
        String outputMode = XMLUtils.xpathString(
                format, XPATH_OUTPUT_MODE, ArtifactNamespaceContext.INSTANCE);
        if (outputMode.equalsIgnoreCase("wms")) {

            Collection<LayerMetaData> layerMetaData =
                                          this.getRequestedLayerMetadata();
            if (layerMetaData != null && !layerMetaData.isEmpty()){
                XMLUtils.toStream(this.getWMS(uuid, callContext,
                                              layerMetaData,inputData),
                                  outputStream);
            }else{
                this.writeExceptionReport2Stream(outputStream);
            }
        }else if (outputMode.equalsIgnoreCase("zip")){
            Collection<LayerMetaData> layerMetaData =
                this.getRequestedLayerMetadata();

            if (layerMetaData != null && !layerMetaData.isEmpty()){
                this.writeZip(uuid, callContext,
                              outputStream, layerMetaData);
            }else{
                this.writeExceptionReport2Stream(outputStream);
            }

        }
    }

    /**
     * Writes an exception to an output stream.
     *
     * @param outputStream The output stream used to write the exception to.
     */
    private void writeExceptionReport2Stream(OutputStream outputStream) {
        Document document = XMLUtils.newDocument();
        ArtifactXMLUtilities.
        createExceptionReport("No Data to Export", document);
        XMLUtils.toStream(document,outputStream);
    }

    /**
     * Returns the Metadata for the requested Layers for fetching the data
     * of the Layer and generating the Layer and WMS.
     * @return the Metadata for the requested Layers
     */
    private Collection<LayerMetaData> getRequestedLayerMetadata(){
        log.debug("LayerOutputState.getRequestedLayerMetadata");
        Collection<Result> result = this.getData(this.queryID);
        Collection<LayerMetaData> returnValue = null;
        if (result != null){
            QueryExecutor queryExecutor = QueryExecutorFactory.getInstance()
                                                              .getQueryExecutor();
            Iterator<Result> it = result.iterator();
            returnValue = new ArrayList<LayerMetaData>(result.size());
            while (it.hasNext()){
                Result resultValue = it.next();
                String table = resultValue.getString(0);
                String geometryType = this.getGeometryType(table, queryExecutor);
                String where = resultValue.getString(1);
                String columns = this.fetchColumns(table);
                String templateID = resultValue.getString(2);
                String[] queryValues = null;
                String geometryWKT = null;
                if (this.geometryID != null){
                    InputData geometryInputData =
                         this.inputData.get(this.geometryID);
                    if (geometryInputData != null){
                        try {

                            Collection<Result> geometryData = queryExecutor
                                                      .executeQuery(this.geometryQueryID,
                                                                    new String[]{geometryInputData.getValue()});
                            Iterator<Result> git = geometryData.iterator();
                            if (git.hasNext()){
                                Result geometryValue = git.next();
                                geometryWKT = geometryValue.getString(0);
                            }
                        } catch (QueryException e) {
                            log.error(e,e);
                        }
                       queryValues = new String[]{columns,
                                                  table,
                                                  where,
                                                  geometryWKT};
                    }else{
                        //Look into the presetting for an WKT
                        InputData geometryWKTData = this.preSettings != null ?
                                                    this.preSettings.get("geometry") :
                                                    null ;
                        if (geometryWKTData != null){
                            queryValues = new String[]{columns,
                                                       table,
                                                       where,
                                                       geometryWKTData.getValue()};
                        }else{
                            queryValues = new String[]{columns,table,where};
                        }
                    }
                }else{
                    //Look into the presetting for an WKT
                    InputData geometryWKTData = this.preSettings != null ?
                                                this.preSettings.get("geometry") :
                                                null ;
                    if (geometryWKTData != null){
                        queryValues = new String[]{columns,
                                                   table,
                                                   where,
                                                   geometryWKTData.getValue()};
                    }else{
                        queryValues = new String[]{columns,table,where};
                    }
                }
                returnValue.add(new LayerMetaData(table, geometryType,
                                  where, columns,
                                  templateID, queryValues,
                                  geometryWKT));
            }
        }
        return returnValue;
    }

    /**
     * Fetches the Data from the Databasebackend.
     *
     * @return the resultdata.
     */
    protected Collection<Result> fetchData(LayerMetaData layerMetaData){
        log.debug("LayerOutputState.fetchData");
        Collection<Result> data = null;
        QueryExecutor queryExecutor = QueryExecutorFactory.getInstance()
                                                           .getQueryExecutor();
        try {
            data  = queryExecutor.executeQuery(dataQueryID,
                                               layerMetaData.getQueryValues());
            if (data != null && layerMetaData.getGeometryWKT() != null){
                WKTReader wktReader = new WKTReader();
                Geometry border = wktReader.read(layerMetaData.getGeometryWKT());
                Iterator<Result> dataIt = data.iterator();
                while (dataIt.hasNext()){
                    // Trim the Geometries using the
                    // Geometry if one is available.
                    Result current = dataIt.next();
                    String currentWKT = current.getString(0);
                    Geometry currentGeometry = null;
                    try {
                        currentGeometry = wktReader.read(currentWKT);
                    } catch (Exception e) {
                        log.error("Error parsing Geometry "+ currentWKT);
                        log.error(e,e);
                    }
                    if (currentGeometry != null){
                        Geometry newGeometry = currentGeometry.intersection(border);
                        current.addColumnValue(0, newGeometry.toText());
                    }
                }
            }
        } catch (QueryException e) {
            log.error(e,e);
        } catch (ParseException e){
            log.error(e,e);
        }
        return data;
    }


    /**
     * This method determines the geometry type on basis of a table name.
     *
     * @param tableName Name of the table in the database.
     * @param queryExecutor The QueryExecutor.
     * @return the geometry type as string (e.g. MultiPolygon, Polygon, etc).
     */
    private String getGeometryType(String tableName,
                                   QueryExecutor queryExecutor){
        String returnValue = null;
        String[] tables = tableName.toUpperCase().split(",");
        String[] filter = tables[0].split("\\.");
        try {
            Collection<Result> result =
                queryExecutor.executeQuery(this.geometryTypeQueryID, filter);
            if (result != null && !result.isEmpty())
            {
                int geometryCode = result.iterator().next().getInteger(0);
                if (geometryCode == 11 ||
                    geometryCode == 10){
                    returnValue = "MultiPolygon";
                }else if (geometryCode == 9 ||
                          geometryCode == 8){
                    returnValue = "MultiLineString";
                }else if (geometryCode == 7){
                    returnValue = "MultiPoint";
                }else if (geometryCode == 6){
                    returnValue = "GeometryCollection";
                }else if (geometryCode == 5 ||
                          geometryCode == 4){
                    returnValue = "Polygon";
                }else if (geometryCode == 3 ||
                          geometryCode == 2){
                    returnValue = "LineString";
                }else if (geometryCode == 1){
                    returnValue = "Point";
                }else if (geometryCode == 0){
                    returnValue = "Geometry";
                }
            }
        } catch (QueryException e) {
            log.error(e,e);
        }
        return returnValue;
    }


    /**
     * Fetch the columns of a specific table.
     *
     * @param tableName The name of the table.
     * @return the columns as string.
     */
    private String fetchColumns(String tableName){
        String returnValue = null;
        try {
            String[] tables = tableName.toUpperCase().split(",");
            String[] filter = tables[0].split("\\.");
            // Only use the first Table the second one will be ignored.
            QueryExecutor queryExecutor = QueryExecutorFactory.getInstance()
                                                            .getQueryExecutor();
            Collection<Result> columnData = queryExecutor.
                                                executeQuery(this.columnQueryID,
                                                             filter);
            if (columnData != null && !columnData.isEmpty()){
                StringBuffer sb = new StringBuffer();
                synchronized (sb) {
                    Iterator<Result> it = columnData.iterator();
                    while(it.hasNext()){
                        Result current = it.next();
                        sb.append(current.getString(0));
                        if (it.hasNext()){
                            sb.append(" , ");
                        }
                    }
                }
                returnValue = sb.toString();
            }
        } catch (QueryException e) {
            log.error(e,e);
        }
        return returnValue;
    }


    @Override
    public void setup(Node configuration) {
        log.debug("LayerOutputState.setup");
        super.setup(configuration);
        this.dataQueryID = Config.getStringXPath(configuration,
                                                 "queryID-layerdata");
        this.geometryID = Config.getStringXPath(configuration,
                                                "inputvalue-geometry");
        this.geometryQueryID =  Config.getStringXPath(configuration,
                                                "queryID-geometry");

        this.columnQueryID =   "layer_colums"; //Config.getStringXPath(configuration,
                                               // "queryID-columns");
        this.geometryTypeQueryID = "geometry_type";
    }


    /**
     * Write the resultdata to shapefiles.
     *
     * @param uuid The UUID of the current artifact.
     * @param data The finalized data used for shapefile creation.
     * @param callContext The CallContext object.
     * @param geometryType The geometry type.
     * @return the shapefile path.
     */
    protected String writeToShapeFile(
                                      String             uuid,
                                      Collection<Result> data,
                                      CallContext        callContext,
                                      String geometryType,
                                      int layerNumber
                                  ) {
        boolean success    = false;
        if (data != null && !data.isEmpty()){
            File shapeDir = new File(shapeFilePath);
            try {
                File shapeFile = new File(shapeDir, createShapeFileName(layerNumber));
                if (!ShapeFileWriter.writeDataToFile(shapeFile, "data", data,geometryType)){
                    log.error("writing data into shapefile failed");
                    return null;
                }
                success = true;
                callContext.afterCall(CallContext.STORE);
                return shapeFilePath;
            }
            finally {
                if (!success) {
                    FileUtils.deleteRecursive(shapeDir);
                }
            }
        }else{
            return null;
        }
    }

    /**
     * Check if the ShapeDir exists and if it exists delete all Contents
     * in it. If it not exists the Director will be created.
     * @param baseDir the BaseDirectory for all ShapeDirs
     * @param uuid the UUID  which is used to create the Directory
     * @return true if the directory exists or could be created.
     *         false if the directory could not be created.
     */
    private boolean createShapeDir(File baseDir, String uuid){
        File shapeDir = new File(baseDir, uuid);
        boolean createdDir = false;
        synchronized (shapeFileLock) {
            if (shapeDir.exists()) {
                FileUtils.deleteContent(shapeDir); // TODO Place on getZip and getWMS
            }
            else if (!shapeDir.mkdirs()) {
                log.error("cannot create directory '"
                    + shapeDir.getAbsolutePath() + "'");
                return false;
            }
            createdDir = true;
        }
        shapeFilePath = shapeDir.getAbsolutePath();
        return createdDir;
    }

    /**
     * Create a zip archive with the shapefiles of the given shapefiles path and
     * write it to <code>output</code>.
     *
     * @param uuid The UUID of the current artifact.
     * @param callContext The CallContext object.
     * @param output The output stream.
     * @param data The data to be written to shapefile.
     * @param geometryType The geometry type.
     * @throws StateException if an error occured while zipfile creation.
     */
    protected void writeZip(
            String       uuid,
            CallContext  callContext,
            OutputStream output,
            Collection<LayerMetaData> layerMetaData
        )
        throws StateException
        {
            try {
                String p = getShapeFilePath();
                if (p != null) {
                    File dir = new File(p);
                    if (dir.isDirectory()) {
                        FileUtils.createZipArchive(dir, output);
                    }
                }
                else {
                    File baseDir = shapefileDirectory(callContext);
                    if (!this.createShapeDir(baseDir, uuid)){
                        return;
                    }
                    Iterator<LayerMetaData> it = layerMetaData.iterator();
                    int i = 1;
                    while(it.hasNext()){
                        LayerMetaData lmd = it.next();
                        Collection<Result> data = this.fetchData(lmd);
                        p = writeToShapeFile(uuid, data, callContext,lmd.getGeometryType(),i++);
                    }
                    if (p != null) {
                        FileUtils.createZipArchive(new File(p), output);
                    }
                }
            }
            catch (IOException ioe) {
                log.error(ioe.getLocalizedMessage(), ioe);
            }
        }

    /**
     * Returns the shapefile path.
     *
     * @return the shapefile path.
     */
    public String getShapeFilePath() {
        synchronized (shapeFileLock) {
            return shapeFilePath;
        }
    }

    /**
     * Returns the basic-directory where the Shapefiles should be placed in.
     * @param callContext the Context of this Call
     * @return the Directory where the Shapefiles could be placed in.
     *         (Please create an own directory in this dir and not put the
     *          Files directly in it)
     */
    private static File shapefileDirectory(CallContext callContext) {
        // Code was taken from HorizontalCrossSectionMeshOutputState
        GNVArtifactContext context =
            (GNVArtifactContext)callContext.globalContext();
        File dir = (File)context.get(
            GNVArtifactContext.HORIZONTAL_CROSS_SECTION_RESULT_SHAPEFILE_PATH_KEY);
        return dir != null
            ? dir
            : GNVArtifactContext.DEFAULT_HORIZONTAL_CROSS_SECTION_PROFILE_SHAPEFILE_PATH;
    }


    @Override
    public void endOfLife(Object globalContext) {
        super.endOfLife(globalContext);
        // do it in background
        new Thread() {
            @Override
            public void run() {
                String path = resetShapeFilePath();
                if (path == null) {
                    return;
                }
                File dir = new File(path);
                for (int i = 0; i < 10; ++i) {
                    if (!dir.exists() || FileUtils.deleteRecursive(dir)) {
                        MapfileGenerator.getInstance().update();
                        return;
                    }
                    try {
                        Thread.sleep(10000L);
                    }
                    catch (InterruptedException ie) {
                    }
                }

                log.error("failed to remove directory '" + path + "'");
            } // run
        }.start();
    }

    /**
     * Resets the Settings e.g shapeFilePath and templateID
     * @return
     */
    private String resetShapeFilePath() {
        synchronized (shapeFileLock) {
            String path = shapeFilePath;
            shapeFilePath = null;
            return path;
        }
    }


    /**
     * Write data to shapefiles and feed a map service with information about
     * these shapefiles. The map service can be queried for displaying
     * corresponding layers as WMS.
     *
     * @param uuid The UUID of the current artifact.
     * @param callContext The CallContext object.
     * @param layerMetaData The Metadata which is required to create the
     *                      different Layers.
     * @param inputData the Parameters which are send by the out-Call.
     * @return a document with some meta information (shapefile path, geometry
     * type, time to live of the current artifact, etc).
     * @throws StateException if an error occured while shapefile writing.
     */
    protected Document getWMS(String uuid,
                              CallContext callContext,
                              Collection<LayerMetaData> layerMetaData,
                              Collection<InputData> inputData)
    throws StateException
    {
        String path = getShapeFilePath();
        if (path != null &&  new File(path).isDirectory()){
            return this.refreshMetaFile(layerMetaData, inputData,
                                        uuid, callContext);
        }else{
            Document document = XMLUtils.newDocument();
            if (this.shapeFilePath == null){
                File baseDir = shapefileDirectory(callContext);
                if (!this.createShapeDir(baseDir, uuid)){
                    // TODO Insert Error Report
                    return document;
                }
            }
            path = getShapeFilePath();
            Iterator<LayerMetaData> it = layerMetaData.iterator();
            Node meta = null;
            int layerNumber = 0;
            while (it.hasNext()){
                LayerMetaData lmd = it.next();
                layerNumber ++;
                String geometryType = lmd.getGeometryType();
                String templateId = lmd.getTemplateID();
                ExclusiveExec.UniqueKey key = ExclusiveExec.INSTANCE.acquire(uuid);
                try{
                    Collection<Result> data = this.fetchData(lmd);
                    if (data != null &&
                        (this.writeToShapeFile(uuid, data, callContext,
                                                 geometryType,layerNumber)) != null) {
                        String paramType = findParameterTitle(geometryType,templateId);
                        String    title      = getLayerTitle(inputData);
                        if (title == null) {
                            title = uuid+"_"+layerNumber;
                        }else{
                            title = title+"_"+layerNumber;
                        }
                        if (meta == null){
                            meta = MetaWriter.writeLayerMeta(callContext,document);
                        }
                        MetaWriter.writeLayerMeta(callContext, document,
                                                  meta, uuid, paramType,
                                                  this.determineGeometryType(geometryType),
                                                  createShapeFileName(layerNumber),
                                                  title);
                    }
                    if (meta != null && !it.hasNext()) {
                        MetaWriter.writeMetaFile(path,document);
                        MapfileGenerator.getInstance().update();
                        return document;
                    }
                }finally{
                    ExclusiveExec.INSTANCE.release(key);
                }
            }
            return document;
        }
    }

    /**
     * Creates the name of the Shapefile
     * @param layerNumber the Number of the Layer
     * @return the create name of the Shapefile.
     */
    private String createShapeFileName(int layerNumber) {
        return SHAPEFILE_NAME+"_"+layerNumber+".shp";
    }

    /**
     * Method that refreshes the Metadatafile for publishing the WMS
     * Without generating the Data ones again.
     * @param layerMetaData the Metadata which is required to create the Layers
     * @param inputData the Inputdata which was sent by the Client
     * @param uuid the uuid of the Artifact
     * @param callContext the context of this Call
     * @return a refreshed Metadata-Document
     */
    private Document refreshMetaFile(Collection<LayerMetaData> layerMetaData,
                                     Collection<InputData> inputData,
                                     String uuid,
                                     CallContext callContext){
        Document document = XMLUtils.newDocument();
        Node meta = null;
        int layerNumber = 0;
        Iterator<LayerMetaData> it = layerMetaData.iterator();
        while (it.hasNext()){
            LayerMetaData lmd = it.next();
            layerNumber ++;
            String geometryType = lmd.getGeometryType();
            String templateId = lmd.getTemplateID();
            String title = getLayerTitle(inputData);
            if (title == null) {
                title = uuid+"_"+layerNumber;
            }else{
                title = title+"_"+layerNumber;
            }
            callContext.putContextValue(
                MetaWriter.CONTEXT_LAYER_TITLE, title);
            String paramType = findParameterTitle(geometryType,templateId);
            if (log.isDebugEnabled()) {
                log.debug("Layer title: " + title);
                log.debug("Layer type: " + paramType);
            }
            if (meta == null){
                meta = MetaWriter.writeLayerMeta(callContext,document);
            }
            MetaWriter.writeLayerMeta(callContext, document,
                                      meta, uuid, paramType,
                                      this.determineGeometryType(geometryType),
                                      createShapeFileName(layerNumber),
                                      title);
            if (meta != null && !it.hasNext()) {
                MetaWriter.writeMetaFile(getShapeFilePath(),document);
                MapfileGenerator.getInstance().update();
                return document;
            }
        }
        return document;
    }

    /**
     * Returns the parameterType for the Layer.
     * @param geometryType
     * @return
     */
    private String findParameterTitle(String geometryType, String templateID) {
        String paramType = LAYER_MODEL+"_"+templateID;
        if (!MapfileGenerator.getInstance().templateExists(paramType)){
            // If the template doesn't exist the Defaulttemplates will be used.
            paramType = LAYER_MODEL+"_"+
                        this.determineDefaultTemplateName(geometryType);
        }
        return paramType;
    }

    /**
     * Find the title for a wms layer specified by the user.
     *
     * @param inputData A collection with InputData objects.
     * @return the title.
     */
    protected String getLayerTitle(Collection<InputData> inputData) {
        for (InputData data: inputData) {
            String name = data.getName();
            if (name != null && name.equals("title")) {
                return (String) data.getValue();
            }
        }
        return null;
    }


    /**
     * Turns the geometry type into a form used for the templating mechanism
     * while mapfile generation.
     *
     * @param geometryType The original geometry type.
     * @return a valid geometry type fpr the template mechanism.
     */
    private String determineGeometryType(String geometryType){
        String returnValue = geometryType.toLowerCase();
        if (returnValue.equalsIgnoreCase("linestring")){
            returnValue = "Line";
        }else if (returnValue.equalsIgnoreCase("multilinestring")){
            returnValue ="Line";
        }else if (returnValue.equalsIgnoreCase("multipolygon")){
            returnValue = "Polygon";
        }
        return returnValue;
    }


    /**
     * Determine the default template name if no special template is existing
     * for this layer.
     *
     * @param geometryType The geometry type.
     * @return a default geometry fitting to the original geometry type.
     */
    private String determineDefaultTemplateName(String geometryType){
        String returnValue = geometryType.toLowerCase();
        if (returnValue.equalsIgnoreCase("multilinestring")){
            returnValue ="linestring";
        }else if (returnValue.equalsIgnoreCase("multipolygon")){
            returnValue = "polygon";
        }else if (returnValue.equalsIgnoreCase("multipoint")){
            returnValue = "point";
        }
        return returnValue;
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org