/**
* Copyright (C) 2012 - 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.mc;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.opengis.wps.x100.InputDescriptionType;
import net.opengis.wps.x100.OutputDescriptionType;
import net.opengis.wps.x100.ProcessDescriptionType;
import org.n52.movingcode.runtime.GlobalRepositoryManager;
import org.n52.movingcode.runtime.codepackage.MovingCodePackage;
import org.n52.movingcode.runtime.iodata.IIOParameter;
import org.n52.movingcode.runtime.iodata.IODataType;
import org.n52.movingcode.runtime.iodata.MediaData;
import org.n52.movingcode.runtime.processors.AbstractProcessor;
import org.n52.movingcode.runtime.processors.ProcessorFactory;
import org.n52.wps.io.data.GenericFileData;
import org.n52.wps.io.data.IData;
import org.n52.wps.io.data.binding.complex.GenericFileDataBinding;
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;
public class MCProcessDelegator implements IAlgorithm {
private final String identifier;
private List<String> errors;
private ProcessDescriptionType description = null;
public MCProcessDelegator(final String identifier) {
this.identifier = identifier;
this.errors = new ArrayList<String>();
}
@Override
public Map<String, IData> run(Map<String, List<IData>> inputData) {
MovingCodePackage[] allMCs = GlobalRepositoryManager.getInstance().getPackageByFunction(identifier);
AbstractProcessor processor = null;
// try to isolate a package that is supported by a registered processor
for (MovingCodePackage currentMC : allMCs){
if (ProcessorFactory.getInstance().supportsPackage(currentMC)){
processor = ProcessorFactory.getInstance().newProcessor(currentMC);
break;
}
}
// TODO: exit here if processor == null;
// assign inputs
for (String inputID : inputData.keySet()) {
List<IData> iDataList = inputData.get(inputID);
if (iDataList == null || iDataList.isEmpty() || iDataList.size() == 0) {
// do nothing
}
else {
// here we can be sure that iDataList contains at least one element
for (IData iData : iDataList) {
switch (probeMCDataType(iData)) {
// simple cases
case BOOLEAN:
processor.addData(inputID, iData.getPayload());
break;
case DOUBLE:
processor.addData(inputID, iData.getPayload());
break;
case INTEGER:
processor.addData(inputID, iData.getPayload());
break;
case STRING:
processor.addData(inputID, iData.getPayload());
break;
// media data
case MEDIA:
GenericFileData gfd = (GenericFileData) iData.getPayload();
processor.addData(inputID, new MediaData(gfd.getDataStream(), gfd.getMimeType()));
break;
default:
errors.add("The supplied data is not compatible with the MC library.\n"
+ "Allowed types are BOOLEAN, DOUBLE, INTEGER, STRING, MEDIA.\n"
+ "Offending InputID: " + inputID);
throw new IllegalArgumentException("Invalid data passed to process. Offending InputID: "
+ inputID);
}
}
}
}
// How do we declare MimeTypes for Outputs?
// Do we have enough information to do this?
//
// We could use the ExecuteRequest to do this but the WPS might
// do additional transformation to support more inputs types
// (e.g. gml for shapefiles)
//
// so we shall stick to the defaults until there's a better way
// to do output probing
// MC packages for WPS should therefore have only *one* supported
// type per input or output
OutputDescriptionType[] wpsOutputs = this.getDescription().getProcessOutputs().getOutputArray();
for (IIOParameter param : processor.values()) {
// for all output-only parameters:
if (param.getDirection() == IIOParameter.Direction.OUT) {
// fetch the "default" mimeType
for (OutputDescriptionType wpsOut : wpsOutputs) {
String outputID = param.getMessageOutputIdentifier();
if (wpsOut.getIdentifier().getStringValue().equalsIgnoreCase(outputID)) {
switch (param.getType()) {
// simple cases
case BOOLEAN:
processor.addData(outputID, null);
break;
case DOUBLE:
processor.addData(outputID, null);
break;
case INTEGER:
processor.addData(outputID, null);
break;
case STRING:
processor.addData(outputID, null);
break;
// media data
case MEDIA:
String mimeType = wpsOut.getComplexOutput().getDefault().getFormat().getMimeType();
processor.addData(outputID, new MediaData(null, mimeType));
break;
default:
errors.add("Soemthing went wrong assigning the output data types.\n"
+ "Probably a messed-up process description.\n" + "Offending OutputID: " + outputID);
throw new IllegalArgumentException("Invalid data passed to process. Offending InputID: "
+ outputID);
}
}
}
}
}
// check feasibility
if ( !processor.isFeasible()) {
errors.add("Feasibility == FALSE for process: " + identifier);
throw new IllegalArgumentException("For some reason the parameterisation was wrong for process: "
+ identifier);
}
try {
processor.execute(0); // execute without any timeout
}
catch (IllegalArgumentException e) {
errors.add(e.getMessage());
throw new RuntimeException("Execution terminated with an error.");
}
catch (RuntimeException e) {
errors.add(e.getMessage());
throw new RuntimeException("Execution terminated with an error.");
}
catch (IOException e) {
errors.add(e.getMessage());
throw new RuntimeException("Execution terminated with an error.");
}
// create the output
HashMap<String, IData> result = new HashMap<String, IData>();
// iterate through all values
for (IIOParameter param : processor.values()) {
// select the outputs only
if (param.isMessageOut()) {
// we will only add the first element due to wps output multiplicity restrictions
// actually, there should be exactly one value in the list, but who knows ...
// check: is there more than one value?
if (param.size() <= 0) {
errors.add("Could not retrieve process output for identifier: "
+ param.getMessageOutputIdentifier() + ". Value list was found empty.");
}
switch (param.getType()) {
case BOOLEAN:
result.put(param.getMessageOutputIdentifier(), new LiteralBooleanBinding((Boolean) param.get(0)));
break;
case DOUBLE:
result.put(param.getMessageOutputIdentifier(), new LiteralDoubleBinding((Double) param.get(0)));
break;
case INTEGER:
result.put(param.getMessageOutputIdentifier(), new LiteralIntBinding((Integer) param.get(0)));
break;
case STRING:
result.put(param.getMessageOutputIdentifier(), new LiteralStringBinding((String) param.get(0)));
break;
case MEDIA:
MediaData md = (MediaData) param.get(0);
GenericFileData gfd = new GenericFileData(md.getMediaStream(), md.getMimeType());
result.put(param.getMessageOutputIdentifier(), new GenericFileDataBinding(gfd));
break;
}
}
}
if (errors.size() > 0) {
throw new RuntimeException("Abnormal termination of execution");
}
// TODO: implement return values
return result;
}
@Override
public List<String> getErrors() {
// TODO Auto-generated method stub
return null;
}
@Override
public ProcessDescriptionType getDescription() {
if (description == null) {
ProcessDescriptionType originalDescription = GlobalRepositoryManager.getInstance().getProcessDescription(identifier);
MCProcessRepository.filterProcessDescription(originalDescription);
description = originalDescription;
}
return description;
}
@Override
public String getWellKnownName() {
return getDescription().getIdentifier().getStringValue();
}
@Override
public boolean processDescriptionIsValid() {
return getDescription().validate();
}
@Override
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;
}
if (datatype.equalsIgnoreCase("boolean")) {
return LiteralBooleanBinding.class;
}
if (datatype.equalsIgnoreCase("float")) {
return LiteralFloatBinding.class;
}
if (datatype.equalsIgnoreCase("double")) {
return LiteralDoubleBinding.class;
}
if (datatype.equalsIgnoreCase("int")) {
return LiteralIntBinding.class;
}
if (datatype.equalsIgnoreCase("integer")) {
return LiteralIntBinding.class;
}
}
// Complex Output
if (input.isSetComplexData()) {
return GenericFileDataBinding.class;
}
}
return null;
}
@Override
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;
}
if (datatype.equalsIgnoreCase("boolean")) {
return LiteralBooleanBinding.class;
}
if (datatype.equalsIgnoreCase("float")) {
return LiteralFloatBinding.class;
}
if (datatype.equalsIgnoreCase("double")) {
return LiteralDoubleBinding.class;
}
if (datatype.equalsIgnoreCase("int")) {
return LiteralIntBinding.class;
}
if (datatype.equalsIgnoreCase("integer")) {
return LiteralIntBinding.class;
}
}
// Complex Output
if (output.isSetComplexOutput()) {
return GenericFileDataBinding.class;
}
}
return null;
}
/*
* This class performs a lookup to mediate between the data types used in the 52n WPS framework and the
* types defined in the Moving Code lib.
*/
private static final IODataType probeMCDataType(IData iData) {
Class< ? > clazz = iData.getSupportedClass();
if (clazz == IODataType.BOOLEAN.getSupportedClass()) {
return IODataType.BOOLEAN;
}
if (clazz == IODataType.DOUBLE.getSupportedClass()) {
return IODataType.DOUBLE;
}
if (clazz == IODataType.INTEGER.getSupportedClass()) {
return IODataType.INTEGER;
}
if (clazz == IODataType.STRING.getSupportedClass()) {
return IODataType.STRING;
}
if (clazz == GenericFileData.class) {
return IODataType.MEDIA;
}
// in case we do not find a matching type return null
return null;
}
}