Mercurial > dive4elements > gnv-client
diff gnv-artifacts/src/main/java/de/intevation/gnv/state/layer/LayerOutputState.java @ 1119:7c4f81f74c47
merged gnv-artifacts
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:14:00 +0200 |
parents | dec4257ad570 |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gnv-artifacts/src/main/java/de/intevation/gnv/state/layer/LayerOutputState.java Fri Sep 28 12:14:00 2012 +0200 @@ -0,0 +1,805 @@ +/* + * Copyright (c) 2010 by Intevation GmbH + * + * This program is free software under the LGPL (>=v2.1) + * Read the file LGPL.txt coming with the software for details + * or visit http://www.gnu.org/licenses/ if it does not exist. + */ + +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.Envelope; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.io.ParseException; +import com.vividsolutions.jts.io.WKTReader; + +import de.intevation.artifacts.common.utils.Config; +import de.intevation.artifacts.common.utils.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 layername = resultValue.getString(3); + 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()}; + geometryWKT = 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, layername)); + } + } + return returnValue; + } + + /** + * Fetches the Data from the Databasebackend. + * + * @return the resultdata. + */ + protected Collection<Result> fetchData(LayerMetaData layerMetaData, Envelope mbr){ + log.debug("LayerOutputState.fetchData"); + Collection<Result> data = null; + QueryExecutor queryExecutor = QueryExecutorFactory.getInstance() + .getQueryExecutor(); + try { + data = queryExecutor.executeQuery(dataQueryID, + layerMetaData.getQueryValues()); + if (data != null){ + WKTReader wktReader = new WKTReader(); + Geometry border = null; + if (layerMetaData.getGeometryWKT() != null){ + 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){ + if (border != null){ + currentGeometry = currentGeometry.intersection(border); + current.addColumnValue(0, currentGeometry.toText()); + } + if (mbr.isNull()){ + mbr.init(currentGeometry.getEnvelopeInternal()); + }else{ + mbr.expandToInclude(currentGeometry.getEnvelopeInternal()); + } + } + } + } + } 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; + Envelope mbr = new Envelope(); + while(it.hasNext()){ + LayerMetaData lmd = it.next(); + Collection<Result> data = this.fetchData(lmd, mbr); + 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; + } + } + + + /** + * Set the shapefile path. + */ + public void setShapeFilePath(String shapeFilePath) { + synchronized (shapeFileLock) { + this.shapeFilePath = 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 + { + Document document = XMLUtils.newDocument(); + + File baseDir = shapefileDirectory(callContext); + File shapeDir = new File(baseDir, uuid); + + boolean success = false; + boolean createdDir = false; + + try { + // create shapefile directory or delete content if it already exists + synchronized (shapeFileLock) { + if (shapeDir.exists()) { + FileUtils.deleteContent(shapeDir); + } + else if (!shapeDir.mkdirs()) { + log.error("cannot create directory '" + + shapeDir.getAbsolutePath() + "'"); + return null; + } + setShapeFilePath(shapeDir.getAbsolutePath()); + createdDir = true; + } + + // process data + Iterator<LayerMetaData> it = layerMetaData.iterator(); + + // create meta file + Document meta = MetaWriter.initMeta(); + MetaWriter.insertAbstractMeta(callContext, meta); + + String path = getShapeFilePath(); + String prefix = getLayerPrefix(inputData); + + if (prefix == null) { + prefix = uuid; + } + + int layerNumber = 0; + Envelope mbr = new Envelope(); + 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> results = this.fetchData(lmd,mbr); + if (results != null && writeToShapeFile(uuid, results, + callContext, + geometryType, + layerNumber) != null) { + String name = getLayerName(uuid, layerNumber); + String base = lmd.getLayertitle(); + String title = getLayerTitle(prefix, base); + String data = getLayerData( + uuid, createShapeFileName(layerNumber)); + String type = determineGeometryType(geometryType); + String status = "OFF"; + String model = findParameterTitle( + geometryType, templateId); + + MetaWriter.insertLayer( + callContext, meta, name, title, + data, model, type, status); + } + + success = true; + + if (meta != null && !it.hasNext()) { + MetaWriter.insertMbr(mbr, "EPSG:4326",meta); + MetaWriter.writeMetaFile(path, meta); + MapfileGenerator.getInstance().update(); + return meta; + } + }finally{ + ExclusiveExec.INSTANCE.release(key); + } + } + + return document; + } + finally { + if (!success && createdDir) { + FileUtils.deleteRecursive(shapeDir); + } + } + } + + /** + * 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"; + } + + + protected String getLayerName(String uuid, int idx) { + return "GNV_" + uuid + "_" + idx; + } + + + protected String getLayerTitle(String prefix, String base) { + return "GNV_" + prefix + "_" + base; + } + + + protected String getLayerData(String shapeDir, String filename) { + File path = new File(shapeDir, filename); + + return path.toString(); + } + + + /** + * 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 getLayerPrefix(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 :