Mercurial > dive4elements > gnv-client
view gnv-artifacts/src/main/java/de/intevation/gnv/state/layer/LayerOutputState.java @ 1033:35c442410738
Added a comment after implementing cache cleaner
gnv-artifacts/trunk@1073 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author | Hans Plum <hans.plum@intevation.de> |
---|---|
date | Fri, 07 May 2010 06:50:47 +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 :