/**
* 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();
}
}
}