/** * Copyright (C) 2009 - 2016 52°North Initiative for Geospatial Open Source * Software GmbH * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. * * If the program is linked with libraries which are licensed under one of * the following licenses, the combination of the program with the linked * library is not considered a "derivative work" of the program: * * • Apache License, version 2.0 * • Apache Software License, version 1.0 * • GNU Lesser General Public License, version 3 * • Mozilla Public License, versions 1.0, 1.1 and 2.0 * • Common Development and Distribution License (CDDL), version 1.0 * * Therefore the distribution of the program linked with libraries licensed * under the aforementioned licenses, is permitted by the copyright holders * if the distribution is compliant with both the GNU General Public * License version 2 and the aforementioned licenses. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. */ package org.n52.wps.ags; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; import net.opengis.wps.x100.InputDescriptionType; import net.opengis.wps.x100.OutputDescriptionType; import net.opengis.wps.x100.ProcessDescriptionType; import org.apache.commons.io.FileUtils; import org.n52.wps.ags.workspace.AGSWorkspace; import org.n52.wps.io.data.GenericFileDataWithGT; import org.n52.wps.io.data.GenericFileDataConstants; import org.n52.wps.io.data.IData; import org.n52.wps.io.data.binding.complex.GenericFileDataWithGTBinding; import org.n52.wps.io.data.binding.literal.LiteralBooleanBinding; import org.n52.wps.io.data.binding.literal.LiteralDoubleBinding; import org.n52.wps.io.data.binding.literal.LiteralFloatBinding; import org.n52.wps.io.data.binding.literal.LiteralIntBinding; import org.n52.wps.io.data.binding.literal.LiteralStringBinding; import org.n52.wps.server.IAlgorithm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Matthias Mueller, TU Dresden * */ public class GenericAGSProcessDelegator implements IAlgorithm{ private static Logger LOGGER = LoggerFactory.getLogger(GenericAGSProcessDelegator.class); private final String processID; private List<String> errors; private AGSWorkspace workspace; protected final ToolParameter[] parameterDescriptions; private String[] toolParameters; private final int parameterCount; private final ProcessDescriptionType processDescription; private final File instanceWorkspace; public GenericAGSProcessDelegator(File workspace, String processID, ToolParameter[] legacyParameters, ProcessDescriptionType processDescription) { errors = new ArrayList<String>(); this.processID = processID; this.parameterDescriptions = legacyParameters; this.parameterCount = legacyParameters.length; this.processDescription = processDescription; this.toolParameters = new String[this.parameterCount]; this.instanceWorkspace = workspace; } public ProcessDescriptionType getDescription() { return processDescription; } public List<String> getErrors() { return errors; } public Class<?> getInputDataType(String id) { InputDescriptionType[] inputs = this.getDescription().getDataInputs().getInputArray(); for(InputDescriptionType input : inputs){ //Literal Input if(input.isSetLiteralData()){ String datatype = input.getLiteralData().getDataType().getStringValue(); if(datatype.equalsIgnoreCase("string")){ return LiteralStringBinding.class; } else if(datatype.equalsIgnoreCase("boolean")){ return LiteralBooleanBinding.class; } else if(datatype.equalsIgnoreCase("float")){ return LiteralFloatBinding.class; } else if(datatype.equalsIgnoreCase("double")){ return LiteralDoubleBinding.class; } else if(datatype.equalsIgnoreCase("int")){ return LiteralIntBinding.class; } else if(datatype.equalsIgnoreCase("integer")){ return LiteralIntBinding.class; } } //Complex Output else if(input.isSetComplexData()){ return GenericFileDataWithGTBinding.class; } } return null; } public Class<?> getOutputDataType(String id) { OutputDescriptionType[] outputs = this.getDescription().getProcessOutputs().getOutputArray(); for(OutputDescriptionType output : outputs){ //Literal Output if(output.isSetLiteralOutput()){ String datatype = output.getLiteralOutput().getDataType().getStringValue(); if(datatype.equalsIgnoreCase("string")){ return LiteralStringBinding.class; } else if(datatype.equalsIgnoreCase("boolean")){ return LiteralBooleanBinding.class; } else if(datatype.equalsIgnoreCase("float")){ return LiteralFloatBinding.class; } else if(datatype.equalsIgnoreCase("double")){ return LiteralDoubleBinding.class; } else if(datatype.equalsIgnoreCase("int")){ return LiteralIntBinding.class; } else if(datatype.equalsIgnoreCase("integer")){ return LiteralIntBinding.class; } } //Complex Output else if(output.isSetComplexOutput()){ return GenericFileDataWithGTBinding.class; } } return null; } public String getWellKnownName() { return processID; } public boolean processDescriptionIsValid() { return this.getDescription().validate(); } public Map<String, IData> run(Map<String, List<IData>> inputData) { //initialize arcObjects AGSProperties.getInstance().bootstrapArcobjectsJar(); //create the workspace this.workspace = new AGSWorkspace(instanceWorkspace); //Assign the parameters for (int i=0; i<this.parameterCount; i++){ ToolParameter currentParam = this.parameterDescriptions[i]; // input parameters if(currentParam.isInput){ if(inputData.containsKey(currentParam.wpsInputID)){ //open the IData list and iterate through it List<IData> dataItemList = inputData.get(currentParam.wpsInputID); Iterator<IData> it = dataItemList.iterator(); ArrayList<String> valueList = new ArrayList<String>(); while (it.hasNext()){ IData currentItem = it.next(); valueList.add(this.loadSingleDataItem(currentItem)); } String[] valueArray = valueList.toArray(new String[0]); //TODO verify: isnt the input separator acutally an " ; "? this.toolParameters[i] = this.calcToolParameterString(" ", valueArray); } else{ if(currentParam.isOptional){ this.toolParameters[i] = null; }else{ errors.add("Error while allocating input parameter " + currentParam.wpsInputID); throw new RuntimeException("Error while allocating input parameter " + currentParam.wpsInputID); } } } //output only parameters else if(currentParam.isOutput && !currentParam.isInput){ if(currentParam.isComplex){ String extension = ""; if(currentParam.schema != null && currentParam.schema.length()>0){ //we have vector data. So use a shp file. extension = "shp"; }else{ extension = GenericFileDataConstants.mimeTypeFileTypeLUT().get(currentParam.mimeType); } String fileName = UUID.randomUUID().toString().substring(0,7) + "." + extension; // geoprocessor can't handle points, dashes etc in output file name fileName = this.addOutputFile(fileName); this.toolParameters[i] = fileName; } } } //execute String toolName = this.processDescription.getTitle().getStringValue(); LOGGER.info("Executing ArcGIS tool " + toolName + " . Parameter array contains " + this.parameterCount + " parameters."); try { workspace.executeGPTool(toolName, null, this.toolParameters); } catch (IOException e1) { LOGGER.error(e1.getMessage()); errors.add(e1.getMessage()); throw new RuntimeException(e1.getMessage()); // otherwise WPS tries to zip and return non-existing files => null pointer } //create the output HashMap<String, IData> result = new HashMap<String, IData>(); for (int i=0; i<this.parameterCount; i++){ ToolParameter currentParam = this.parameterDescriptions[i]; if(currentParam != null // Otherwise, nullpointer exception when trying to process outputs! && currentParam.isOutput){ if(currentParam.isComplex){ String fileName = this.toolParameters[i]; //GenericFileData outputFileData = new GenericFileData(this.workspace.getFileAsStream(fileName), currentParam.mimeType); File currentFile = new File (fileName); GenericFileDataWithGT outputFileData; try { if(currentParam.schema != null && currentParam.schema.length()>0){ //we have vector data. So use a shp file. outputFileData = new GenericFileDataWithGT(currentFile, GenericFileDataConstants.MIME_TYPE_ZIPPED_SHP); }else{ outputFileData = new GenericFileDataWithGT(currentFile, currentParam.mimeType); } result.put(currentParam.wpsOutputID, new GenericFileDataWithGTBinding(outputFileData)); } catch (FileNotFoundException e) { LOGGER.error("Could not read output file: " + fileName); errors.add("Could not read output file: " + fileName); throw new RuntimeException("No files found. Probably the process did not create any output files."); } catch (IOException e) { LOGGER.error("Could not create output file from: " + fileName); errors.add("Could not create output file from: " + fileName); e.printStackTrace(); } } if(currentParam.isLiteral){ // not implemented } if(currentParam.isCRS){ // not implemented } } } //Handle if no result was created if (result.isEmpty()){ String message = ""; for (String error : errors){ message = message.concat(error + " - "); } message = message.concat("ArcGIS Backend Process " + this.processID + " did not return any output" + " data or the output data could not be processed."); throw new RuntimeException(message); } return result; } private String loadSingleDataItem(IData dataItem){ Object payload = dataItem.getPayload(); String value = null; //File if (payload instanceof GenericFileDataWithGT){ GenericFileDataWithGT gfd = (GenericFileDataWithGT)payload; value = gfd.writeData(this.workspace.getWorkspace()); } //String else if (payload instanceof String) value = (String) payload; //Float else if (payload instanceof Float) value = ((Float)payload).toString(); //Integer else if (payload instanceof Integer) value = ((Integer)payload).toString(); //Double else if (payload instanceof Double) value = ((Double)payload).toString(); return value; } private String calcToolParameterString(String valueSeparator, String[] valueArray){ String returnValue = null; boolean firstrun = true; for(String currentValue : valueArray){ if (firstrun){ firstrun = false; returnValue = currentValue; } else { returnValue = returnValue + valueSeparator + currentValue; } } return returnValue; } private final String addOutputFile (String fileName){ String newFileName = this.workspace.getWorkspace().getAbsolutePath() + "\\" + fileName; return newFileName; } //delete the current workspace protected void finalize(){ try { FileUtils.deleteDirectory(instanceWorkspace); } catch (IOException e) { LOGGER.error("Could not delete dead workspace:\n" + instanceWorkspace.getAbsolutePath()); e.printStackTrace(); } } }