Mercurial > dive4elements > gnv-client
diff gnv-artifacts/src/main/java/de/intevation/gnv/state/layer/LayerOutputState.java @ 875:5e9efdda6894
merged gnv-artifacts/1.0
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:13:56 +0200 |
parents | dfd02f8d3602 |
children | 461d4489705c |
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:13:56 2012 +0200 @@ -0,0 +1,774 @@ +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 :