tim@616: package de.intevation.gnv.state.layer;
tim@616:
tim@799: import java.io.File;
tim@799: import java.io.IOException;
tim@799: import java.io.OutputStream;
tim@799: import java.util.Collection;
tim@799: import java.util.Iterator;
tim@799:
tim@799: import org.apache.log4j.Logger;
tim@799: import org.w3c.dom.Document;
tim@799: import org.w3c.dom.Element;
tim@799: import org.w3c.dom.Node;
tim@799:
sascha@779: import com.vividsolutions.jts.geom.Geometry;
tim@649: import com.vividsolutions.jts.io.ParseException;
tim@649: import com.vividsolutions.jts.io.WKTReader;
tim@649:
tim@616: import de.intevation.artifactdatabase.Config;
tim@616: import de.intevation.artifactdatabase.XMLUtils;
tim@616: import de.intevation.artifacts.ArtifactNamespaceContext;
tim@616: import de.intevation.artifacts.CallContext;
tim@649: import de.intevation.gnv.artifacts.context.GNVArtifactContext;
tim@616: import de.intevation.gnv.geobackend.base.Result;
tim@649: import de.intevation.gnv.geobackend.base.query.QueryExecutor;
tim@649: import de.intevation.gnv.geobackend.base.query.QueryExecutorFactory;
tim@649: import de.intevation.gnv.geobackend.base.query.exception.QueryException;
tim@616: import de.intevation.gnv.state.InputData;
tim@616: import de.intevation.gnv.state.OutputStateBase;
tim@616: import de.intevation.gnv.state.exception.StateException;
tim@723: import de.intevation.gnv.utils.ArtifactXMLUtilities;
tim@649: import de.intevation.gnv.utils.FileUtils;
tim@649: import de.intevation.gnv.utils.MapfileGenerator;
tim@655: import de.intevation.gnv.utils.MetaWriter;
tim@649: import de.intevation.gnv.utils.ShapeFileWriter;
tim@616:
tim@616: /**
tim@822: * This OutputState
is used for Layer-Products.
sascha@780: * @author Tim Englich
tim@616: *
tim@616: */
tim@616: public class LayerOutputState extends OutputStateBase {
tim@616:
tim@616: /**
tim@616: * the logger, used to log exceptions and additonaly information
tim@616: */
tim@616: private static Logger log = Logger.getLogger(LayerOutputState.class);
sascha@778:
tim@616: /**
tim@616: * The UID of this Class.
tim@616: */
tim@616: private static final long serialVersionUID = 9180957321704424049L;
sascha@778:
tim@822: /**
tim@822: * The Basename of the Templates for the WMS-Exports
tim@822: */
tim@655: public static final String LAYER_MODEL = "layer";
tim@822:
tim@822: /**
tim@822: * The Name of the Shapefile which will be generated.
tim@822: */
tim@822: public static final String SHAPEFILE_NAME = "data.shp";
tim@616:
tim@616: /**
tim@616: * The ID for the Query fetching the Layer from the DB
tim@616: */
sascha@778: private String dataQueryID = null;
sascha@778:
tim@649: /**
tim@649: * The ID for the Query fetching the Geometry from the DB
tim@649: * which should be used to Clip the Layerdata
tim@649: */
tim@649: private String geometryQueryID = null;
sascha@778:
tim@822: /**
tim@822: * The ID of the Query for fetching the Columnnames of the Table which
tim@822: * should put into the Shapefile.
tim@822: */
tim@728: private String columnQueryID = null;
sascha@778:
tim@822: /**
tim@822: * The ID of the Query for fetching the Information which kind of Geometry
tim@822: * should be put into the Shapefile.
tim@822: */
tim@799: private String geometryTypeQueryID = null;
tim@799:
tim@649: /**
tim@822: * The ID for the Value which will hold the Geometry-Value
tim@649: */
tim@649: private String geometryID = null;
sascha@778:
tim@822: /**
tim@822: * Flag for synchronized Access of the Shapefile.
tim@822: */
tim@649: private Boolean shapeFileLock = new Boolean(true);
sascha@778:
tim@822: /**
tim@822: * The Path where the Shapefile is stored.
tim@822: */
tim@649: private String shapeFilePath;
sascha@778:
tim@822: /**
tim@822: * The ID of the Template which should be used to generate the Layerentry
tim@822: * in the Mapfile.
tim@822: */
tim@799: private String templateID = null;
sascha@803:
tim@822: /**
tim@822: * The Kind of Geometry which should be used to generate the Shapefile.
tim@822: */
tim@655: private String geometryType = null;
sascha@778:
tim@616: /**
tim@616: * Constructor
tim@616: */
tim@616: public LayerOutputState() {
tim@616: super();
tim@616: }
tim@616:
tim@616: public void out(Document format, Collection inputData,
tim@616: OutputStream outputStream, String uuid,
tim@616: CallContext callContext) throws StateException {
sascha@778:
tim@616: log.debug("LayerOutputState.out");
tim@616: String outputMode = XMLUtils.xpathString(
tim@616: format, XPATH_OUTPUT_MODE, ArtifactNamespaceContext.INSTANCE);
tim@616: if (outputMode.equalsIgnoreCase("wms")) {
tim@616: Collection data = this.fetchData();
tim@723: if (data != null && !data.isEmpty()){
tim@799: XMLUtils.toStream(this.getWMS(uuid, callContext,
tim@799: data, geometryType),
tim@723: outputStream);
tim@723: }else{
tim@723: this.writeExceptionReport2Stream(outputStream);
tim@723: }
tim@616: }else if (outputMode.equalsIgnoreCase("zip")){
tim@616: Collection data = this.fetchData();
tim@723: if (data != null && !data.isEmpty()){
tim@799: this.writeZip(uuid, callContext, outputStream,
tim@799: data,geometryType);
tim@723: }else{
tim@723: this.writeExceptionReport2Stream(outputStream);
tim@723: }
sascha@778:
tim@616: }
tim@616: }
tim@723:
tim@723: /**
ingo@813: * Writes an exception to an output stream.
ingo@813: *
ingo@813: * @param outputStream The output stream used to write the exception to.
tim@723: */
tim@723: private void writeExceptionReport2Stream(OutputStream outputStream) {
tim@723: Document document = XMLUtils.newDocument();
ingo@813: ArtifactXMLUtilities.
tim@723: createExceptionReport("No Data to Export", document);
tim@723: XMLUtils.toStream(document,outputStream);
tim@723: }
sascha@778:
sascha@778:
tim@649: /**
ingo@813: * Fetches the Data from the Databasebackend.
ingo@813: *
tim@822: * @return the resultdata.
tim@649: */
tim@616: protected Collection fetchData(){
tim@616: log.debug("LayerOutputState.fetchData");
tim@616: Collection result = this.getData(this.queryID);
tim@616: Collection data = null;
tim@649: String geometryWKT = null;
tim@616: if (result != null){
tim@649: QueryExecutor queryExecutor = QueryExecutorFactory.getInstance()
tim@649: .getQueryExecutor();
tim@616: Iterator it = result.iterator();
tim@649: String[] queryValues = null;
tim@616: if (it.hasNext()){
tim@616: Result resultValue = it.next();
tim@649: String table = resultValue.getString(0);
tim@799:
tim@799: this.geometryType = this.getGeometryType(table, queryExecutor);
tim@799:
tim@649: String where = resultValue.getString(1);
tim@728: String columns = this.fetchColumns(table);
sascha@778:
tim@724: templateID = resultValue.getString(2);
tim@649: if (this.geometryID != null){
sascha@778: InputData geometryInputData =
tim@649: this.inputData.get(this.geometryID);
tim@649: if (geometryInputData != null){
sascha@778:
tim@649: try {
tim@649: Collection geometryData = queryExecutor
tim@649: .executeQuery(this.geometryQueryID,
tim@649: new String[]{geometryInputData.getValue()});
tim@649: Iterator git = geometryData.iterator();
tim@649: if (git.hasNext()){
tim@649: Result geometryValue = git.next();
tim@649: geometryWKT = geometryValue.getString(0);
tim@649: }
tim@649: } catch (QueryException e) {
tim@649: log.error(e,e);
tim@649: }
tim@728: queryValues = new String[]{columns,
tim@728: table,
tim@649: where,
tim@649: geometryWKT};
tim@649: }else{
tim@729: //Look into the presetting for an WKT
sascha@778: InputData geometryWKTData = this.preSettings != null ?
tim@742: this.preSettings.get("geometry") :
tim@742: null ;
tim@729: if (geometryWKTData != null){
tim@729: queryValues = new String[]{columns,
tim@729: table,
tim@729: where,
tim@729: geometryWKTData.getValue()};
tim@729: }else{
tim@729: queryValues = new String[]{columns,table,where};
tim@729: }
tim@729: }
tim@729: }else{
tim@729: //Look into the presetting for an WKT
sascha@778: InputData geometryWKTData = this.preSettings != null ?
tim@742: this.preSettings.get("geometry") :
tim@742: null ;
tim@729: if (geometryWKTData != null){
tim@729: queryValues = new String[]{columns,
tim@729: table,
tim@729: where,
tim@729: geometryWKTData.getValue()};
tim@729: }else{
tim@728: queryValues = new String[]{columns,table,where};
tim@649: }
tim@649: }
tim@616: }
sascha@778:
tim@649: try {
tim@649: data = queryExecutor.executeQuery(dataQueryID,
tim@649: queryValues);
tim@649: if (data != null && geometryWKT != null){
tim@649: WKTReader wktReader = new WKTReader();
tim@649: Geometry border = wktReader.read(geometryWKT);
sascha@778:
tim@649: Iterator dataIt = data.iterator();
tim@649: while (dataIt.hasNext()){
tim@649: // Trim the Geometries using the
tim@649: // Geometry if on is available.
tim@649: Result current = dataIt.next();
tim@649: String currentWKT = current.getString(0);
tim@649: Geometry currentGeometry = null;
tim@649: try {
tim@649: currentGeometry = wktReader.read(currentWKT);
tim@649: } catch (Exception e) {
tim@649: log.error("Error parsing Geometry "+ currentWKT);
tim@649: log.error(e,e);
tim@649: }
sascha@778:
tim@649: if (currentGeometry != null){
tim@649: Geometry newGeometry = currentGeometry.intersection(border);
tim@649: current.addColumnValue(0, newGeometry.toText());
tim@649: }
tim@649: }
tim@649: }
tim@649: } catch (QueryException e) {
tim@649: log.error(e,e);
tim@649: } catch (ParseException e){
tim@649: log.error(e,e);
tim@649: }
tim@616: }
tim@616: return data;
tim@616: }
tim@616:
ingo@813:
ingo@813: /**
ingo@813: * This method determines the geometry type on basis of a table name.
ingo@813: *
ingo@813: * @param tableName Name of the table in the database.
ingo@813: * @param queryExecutor The QueryExecutor.
ingo@813: * @return the geometry type as string (e.g. MultiPolygon, Polygon, etc).
ingo@813: */
tim@799: private String getGeometryType(String tableName,
tim@799: QueryExecutor queryExecutor){
tim@799: String returnValue = null;
tim@799: String[] tables = tableName.toUpperCase().split(",");
tim@799: String[] filter = tables[0].split("\\.");
sascha@803:
tim@799: try {
sascha@803: Collection result =
tim@799: queryExecutor.executeQuery(this.geometryTypeQueryID, filter);
tim@799: if (result != null && !result.isEmpty())
tim@799: {
tim@799: int geometryCode = result.iterator().next().getInteger(0);
sascha@803: if (geometryCode == 11 ||
tim@799: geometryCode == 10){
tim@799: returnValue = "MultiPolygon";
sascha@803: }else if (geometryCode == 9 ||
tim@799: geometryCode == 8){
tim@799: returnValue = "MultiLineString";
tim@799: }else if (geometryCode == 7){
tim@799: returnValue = "MultiPoint";
tim@799: }else if (geometryCode == 6){
tim@799: returnValue = "GeometryCollection";
sascha@803: }else if (geometryCode == 5 ||
tim@799: geometryCode == 4){
tim@799: returnValue = "Polygon";
sascha@803: }else if (geometryCode == 3 ||
tim@799: geometryCode == 2){
tim@799: returnValue = "LineString";
tim@799: }else if (geometryCode == 1){
tim@799: returnValue = "Point";
tim@799: }else if (geometryCode == 0){
tim@799: returnValue = "Geometry";
tim@799: }
tim@799: }
tim@799: } catch (QueryException e) {
tim@799: log.error(e,e);
tim@799: }
sascha@803:
tim@799: return returnValue;
tim@799: }
ingo@813:
ingo@813:
ingo@813: /**
ingo@813: * Fetch the columns of a specific table.
ingo@813: *
ingo@813: * @param tableName The name of the table.
ingo@813: * @return the columns as string.
ingo@813: */
tim@728: private String fetchColumns(String tableName){
tim@728: String returnValue = null;
tim@728: try {
tim@756: String[] tables = tableName.toUpperCase().split(",");
tim@756: String[] filter = tables[0].split("\\.");
tim@756: // Only use the first Table the second one will be ignored.
tim@728: QueryExecutor queryExecutor = QueryExecutorFactory.getInstance()
tim@728: .getQueryExecutor();
sascha@778:
tim@728: Collection columnData = queryExecutor.
sascha@778: executeQuery(this.columnQueryID,
tim@728: filter);
tim@728: if (columnData != null && !columnData.isEmpty()){
tim@728: StringBuffer sb = new StringBuffer();
tim@728: synchronized (sb) {
tim@728: Iterator it = columnData.iterator();
tim@728: while(it.hasNext()){
tim@728: Result current = it.next();
tim@728: sb.append(current.getString(0));
tim@728: if (it.hasNext()){
tim@728: sb.append(" , ");
tim@728: }
tim@728: }
tim@728: }
tim@728: returnValue = sb.toString();
tim@728: }
sascha@778:
tim@728: } catch (QueryException e) {
tim@728: log.error(e,e);
tim@728: }
tim@728: return returnValue;
tim@728: }
ingo@813:
ingo@813:
tim@616: @Override
tim@616: public void setup(Node configuration) {
tim@616: log.debug("LayerOutputState.setup");
tim@616: super.setup(configuration);
tim@649: this.dataQueryID = Config.getStringXPath(configuration,
tim@649: "queryID-layerdata");
tim@649: this.geometryID = Config.getStringXPath(configuration,
tim@649: "inputvalue-geometry");
tim@649: this.geometryQueryID = Config.getStringXPath(configuration,
tim@649: "queryID-geometry");
sascha@778:
tim@728: this.columnQueryID = "layer_colums"; //Config.getStringXPath(configuration,
tim@728: // "queryID-columns");
tim@799: this.geometryTypeQueryID = "geometry_type";
tim@616: }
sascha@778:
ingo@813:
ingo@813: /**
tim@822: * Write the resultdata to shapefiles.
ingo@813: *
ingo@813: * @param uuid The UUID of the current artifact.
ingo@813: * @param data The finalized data used for shapefile creation.
ingo@813: * @param callContext The CallContext object.
ingo@813: * @param geometryType The geometry type.
ingo@813: * @return the shapefile path.
ingo@813: */
tim@649: protected String writeToShapeFile(
tim@649: String uuid,
tim@649: Collection data,
tim@799: CallContext callContext,
tim@799: String geometryType
tim@649: ) {
tim@649: File baseDir = shapefileDirectory(callContext);
sascha@778:
tim@649: File shapeDir = new File(baseDir, uuid);
tim@649: boolean success = false;
tim@649: boolean createdDir = false;
tim@649:
tim@649: try {
tim@649: synchronized (shapeFileLock) {
tim@649: int count = 0;
tim@649: while (shapeDir.exists()) {
tim@649: shapeDir = new File(baseDir, uuid + "-" + count);
tim@649: ++count;
tim@649: }
tim@649:
tim@649: if (!shapeDir.mkdirs()) {
sascha@778: log.error("cannot create directory '"
tim@649: + shapeDir.getAbsolutePath() + "'");
tim@649: return null;
tim@649: }
tim@649: createdDir = true;
tim@649: }
tim@649:
tim@649: File shapeFile = new File(shapeDir, SHAPEFILE_NAME);
tim@799: if (!ShapeFileWriter.writeDataToFile(shapeFile, "data", data,geometryType)){
tim@649: log.error("writing data into shapefile failed");
tim@649: return null;
tim@649: }
sascha@778:
tim@649: shapeFilePath = shapeDir.getAbsolutePath();
tim@649: success = true;
tim@649:
tim@649: callContext.afterCall(CallContext.STORE);
tim@649:
tim@649: return shapeFilePath;
tim@649: }
tim@649: finally {
tim@649: if (!success && createdDir) {
tim@649: FileUtils.deleteRecursive(shapeDir);
tim@649: }
tim@649: }
tim@649: }
sascha@778:
ingo@813:
ingo@813: /**
ingo@813: * Create a zip archive with the shapefiles of the given shapefiles path and
ingo@813: * write it to output
.
ingo@813: *
ingo@813: * @param uuid The UUID of the current artifact.
ingo@813: * @param callContext The CallContext object.
ingo@813: * @param output The output stream.
ingo@813: * @param data The data to be written to shapefile.
ingo@813: * @param geometryType The geometry type.
ingo@813: * @throws StateException if an error occured while zipfile creation.
ingo@813: */
tim@649: protected void writeZip(
tim@649: String uuid,
tim@649: CallContext callContext,
tim@649: OutputStream output,
tim@799: Collection data,
tim@799: String geometryType
sascha@778: )
tim@649: throws StateException
tim@649: {
tim@649: try {
tim@649: String p = getShapeFilePath();
tim@649: if (p != null) {
tim@649: File dir = new File(p);
tim@649: if (dir.isDirectory()) {
tim@649: FileUtils.createZipArchive(dir, output);
tim@649: }
tim@649: }
tim@649: else {
sascha@778:
tim@799: if ((p = writeToShapeFile(uuid, data, callContext,geometryType)) != null) {
tim@649: FileUtils.createZipArchive(new File(p), output);
tim@649: }
tim@649: }
tim@649: }
tim@649: catch (IOException ioe) {
tim@649: log.error(ioe.getLocalizedMessage(), ioe);
tim@649: }
tim@649: }
sascha@778:
ingo@813: /**
ingo@813: * Returns the shapefile path.
ingo@813: *
ingo@813: * @return the shapefile path.
ingo@813: */
tim@649: public String getShapeFilePath() {
tim@649: synchronized (shapeFileLock) {
tim@649: return shapeFilePath;
tim@649: }
tim@649: }
sascha@778:
ingo@813:
tim@649: private static File shapefileDirectory(CallContext callContext) {
tim@649: // TODO: Refactoring nessessary it should be used only one Shapefilepath
tim@649: // for alle Modes. Code was taken from HorizontalCrossSectionMeshOutputState
tim@649: GNVArtifactContext context =
tim@649: (GNVArtifactContext)callContext.globalContext();
tim@649: File dir = (File)context.get(
tim@649: GNVArtifactContext.HORIZONTAL_CROSS_SECTION_RESULT_SHAPEFILE_PATH_KEY);
tim@649: return dir != null
tim@649: ? dir
tim@649: : GNVArtifactContext.DEFAULT_HORIZONTAL_CROSS_SECTION_PROFILE_SHAPEFILE_PATH;
tim@649: }
sascha@778:
ingo@813:
tim@649: @Override
tim@649: public void endOfLife(Object globalContext) {
tim@649: super.endOfLife(globalContext);
tim@649:
tim@649: // do it in background
tim@649: new Thread() {
ingo@813: @Override
tim@649: public void run() {
tim@649: String path = resetShapeFilePath();
tim@649:
tim@649: if (path == null) {
tim@649: return;
tim@649: }
tim@649:
tim@649: File dir = new File(path);
tim@649:
tim@649: for (int i = 0; i < 10; ++i) {
tim@649: if (!dir.exists() || FileUtils.deleteRecursive(dir)) {
tim@649: MapfileGenerator.getInstance().update();
tim@649: return;
tim@649: }
tim@649:
tim@649: try {
tim@649: Thread.sleep(10000L);
tim@649: }
tim@649: catch (InterruptedException ie) {
tim@649: }
tim@649: }
tim@649:
tim@649: log.error("failed to remove directory '" + path + "'");
tim@649: } // run
tim@649: }.start();
tim@649: }
sascha@778:
tim@822: /**
tim@822: * Resets the Settings e.g shapeFilePath and templateID
tim@822: * @return
tim@822: */
tim@822: private String resetShapeFilePath() {
tim@649: synchronized (shapeFileLock) {
tim@649: String path = shapeFilePath;
tim@649: shapeFilePath = null;
tim@724: templateID = null;
tim@649: return path;
tim@649: }
tim@649: }
ingo@813:
ingo@813:
ingo@813: /**
ingo@813: * Write data to shapefiles and feed a map service with information about
ingo@813: * these shapefiles. The map service can be queried for displaying
ingo@813: * corresponding layers as WMS.
ingo@813: *
ingo@813: * @param uuid The UUID of the current artifact.
ingo@813: * @param callContext The CallContext object.
ingo@813: * @param data A collection with some input data.
ingo@813: * @param geometryType The geometry type.
ingo@813: * @return a document with some meta information (shapefile path, geometry
ingo@813: * type, time to live of the current artifact, etc).
ingo@813: * @throws StateException if an error occured while shapefile writing.
ingo@813: */
sascha@778: protected Document getWMS(String uuid,
sascha@778: CallContext callContext,
tim@799: Collection data,
tim@799: String geometryType)
tim@655: throws StateException
tim@655: {
tim@655: Document document = XMLUtils.newDocument();
tim@655:
tim@655: Element pathElement = document.createElement("path");
tim@655: document.appendChild(pathElement);
tim@655:
tim@655: String path = getShapeFilePath();
tim@655:
tim@655: if (path != null && new File(path).isDirectory()) {
tim@655: pathElement.setTextContent(path);
tim@655: }
tim@655: else {
sascha@778:
tim@655: if (data != null &&
tim@799: (path = writeToShapeFile(uuid, data, callContext,geometryType)) != null) {
tim@655:
tim@724: String paramType = LAYER_MODEL+"_"+templateID;
sascha@778:
tim@724: if (!MapfileGenerator.getInstance().templateExists(paramType)){
tim@724: // If the template doesn't exist the Defaulttemplates will be used.
sascha@803: paramType = LAYER_MODEL+"_"+
tim@799: this.determineDefaultTemplateName(geometryType);
tim@724: }
sascha@778: Document meta = MetaWriter.writeLayerMeta(callContext, uuid,
sascha@778: path, paramType,
tim@799: this.determineGeometryType(geometryType));
tim@655: if (meta != null) {
tim@655: MapfileGenerator.getInstance().update();
tim@655: return meta;
tim@655: }
tim@655:
tim@655: pathElement.setTextContent(path);
tim@655: }
tim@655: }
tim@655:
tim@655: return document;
tim@655: }
sascha@778:
ingo@813:
ingo@813: /**
ingo@813: * Turns the geometry type into a form used for the templating mechanism
ingo@813: * while mapfile generation.
ingo@813: *
ingo@813: * @param geometryType The original geometry type.
ingo@813: * @return a valid geometry type fpr the template mechanism.
ingo@813: */
tim@799: private String determineGeometryType(String geometryType){
sascha@778:
tim@799: String returnValue = geometryType.toLowerCase();
sascha@778:
tim@655: if (returnValue.equalsIgnoreCase("linestring")){
tim@655: returnValue = "Line";
tim@799: }else if (returnValue.equalsIgnoreCase("multilinestring")){
tim@799: returnValue ="Line";
tim@799: }else if (returnValue.equalsIgnoreCase("multipolygon")){
tim@799: returnValue = "Polygon";
tim@799: }
tim@799: return returnValue;
tim@799: }
sascha@803:
ingo@813:
ingo@813: /**
ingo@813: * Determine the default template name if no special template is existing
ingo@813: * for this layer.
ingo@813: *
ingo@813: * @param geometryType The geometry type.
ingo@813: * @return a default geometry fitting to the original geometry type.
ingo@813: */
tim@799: private String determineDefaultTemplateName(String geometryType){
tim@799:
tim@799: String returnValue = geometryType.toLowerCase();
tim@799:
tim@799: if (returnValue.equalsIgnoreCase("multilinestring")){
tim@799: returnValue ="linestring";
tim@799: }else if (returnValue.equalsIgnoreCase("multipolygon")){
tim@799: returnValue = "polygon";
tim@799: }else if (returnValue.equalsIgnoreCase("multipoint")){
tim@799: returnValue = "point";
tim@655: }
tim@655: return returnValue;
tim@655: }
tim@616: }
ingo@813: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :