/**
* Copyright (C) 2010 - 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.server.r.metadata;
import java.math.BigInteger;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import net.opengis.ows.x11.DomainMetadataType;
import net.opengis.ows.x11.MetadataType;
import net.opengis.wps.x100.ComplexDataCombinationsType;
import net.opengis.wps.x100.ComplexDataDescriptionType;
import net.opengis.wps.x100.InputDescriptionType;
import net.opengis.wps.x100.LiteralInputType;
import net.opengis.wps.x100.LiteralOutputType;
import net.opengis.wps.x100.OutputDescriptionType;
import net.opengis.wps.x100.ProcessDescriptionType;
import net.opengis.wps.x100.ProcessDescriptionType.DataInputs;
import net.opengis.wps.x100.ProcessDescriptionType.ProcessOutputs;
import net.opengis.wps.x100.SupportedComplexDataType;
import org.n52.wps.FormatDocument.Format;
import org.n52.wps.io.GeneratorFactory;
import org.n52.wps.io.IGenerator;
import org.n52.wps.io.IOHandler;
import org.n52.wps.io.IParser;
import org.n52.wps.io.ParserFactory;
import org.n52.wps.io.data.GenericFileDataConstants;
import org.n52.wps.io.data.IData;
import org.n52.wps.io.data.binding.complex.GenericFileDataBinding;
import org.n52.wps.server.ExceptionReport;
import org.n52.wps.server.r.R_Config;
import org.n52.wps.server.r.data.R_Resource;
import org.n52.wps.server.r.syntax.RAnnotation;
import org.n52.wps.server.r.syntax.RAnnotationException;
import org.n52.wps.server.r.syntax.RAttribute;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RProcessDescriptionCreator {
private static Logger log = LoggerFactory.getLogger(RProcessDescriptionCreator.class);
private R_Config config;
public RProcessDescriptionCreator(R_Config config) {
this.config = config;
log.debug("NEW {}", this);
}
/**
* Usually called from GenericRProcess (extends AbstractObservableAlgorithm)
*
* @param annotations
* contain all process description information
* @param identifier
* Process identifier
* @param fileUrl
* @return
* @throws ExceptionReport
* @throws RAnnotationException
*/
public ProcessDescriptionType createDescribeProcessType(List<RAnnotation> annotations,
String identifier,
URL fileUrl,
URL sessionInfoUrl) throws ExceptionReport,
RAnnotationException {
log.debug("Creating Process Description for " + identifier);
try {
ProcessDescriptionType pdt = ProcessDescriptionType.Factory.newInstance();
pdt.setStatusSupported(true);
pdt.setStoreSupported(true);
addMetadataLinks(fileUrl, sessionInfoUrl, pdt);
ProcessOutputs outputs = pdt.addNewProcessOutputs();
DataInputs inputs = pdt.addNewDataInputs();
// iterates over annotations,
// The annotation type (RAnnotationType - enumeration) determines
// next method call
for (RAnnotation annotation : annotations) {
switch (annotation.getType()) {
case INPUT:
addInput(inputs, annotation);
break;
case OUTPUT:
addOutput(outputs, annotation);
break;
case DESCRIPTION:
addProcessDescription(pdt, annotation);
break;
case RESOURCE:
addProcessResources(pdt, annotation);
break;
default:
break;
}
}
// Add SessionInfo-Output
OutputDescriptionType outdes = outputs.addNewOutput();
outdes.addNewIdentifier().setStringValue("sessionInfo");
outdes.addNewTitle().setStringValue("Information about the R session which has been used");
outdes.addNewAbstract().setStringValue("Output of the sessionInfo()-method after R-script execution");
SupportedComplexDataType scdt = outdes.addNewComplexOutput();
ComplexDataDescriptionType datatype = scdt.addNewDefault().addNewFormat();
datatype.setMimeType(GenericFileDataConstants.MIME_TYPE_PLAIN_TEXT);
datatype.setEncoding(IOHandler.DEFAULT_ENCODING);
datatype = scdt.addNewSupported().addNewFormat();
datatype.setMimeType(GenericFileDataConstants.MIME_TYPE_PLAIN_TEXT);
datatype.setEncoding(IOHandler.DEFAULT_ENCODING);
// Add Warnings-Output
outdes = outputs.addNewOutput();
outdes.addNewIdentifier().setStringValue("warnings");
outdes.addNewTitle().setStringValue("Warnings from R");
outdes.addNewAbstract().setStringValue("Output of the warnings()-method after R-script execution");
scdt = outdes.addNewComplexOutput();
datatype = scdt.addNewDefault().addNewFormat();
datatype.setMimeType(GenericFileDataConstants.MIME_TYPE_PLAIN_TEXT);
datatype.setEncoding(IOHandler.DEFAULT_ENCODING);
datatype = scdt.addNewSupported().addNewFormat();
datatype.setMimeType(GenericFileDataConstants.MIME_TYPE_PLAIN_TEXT);
datatype.setEncoding(IOHandler.DEFAULT_ENCODING);
return pdt;
}
catch (Exception e) {
log.error("Error creating process description.", e);
throw new ExceptionReport("Error creating process description.",
"NA",
RProcessDescriptionCreator.class.getName(),
e);
}
}
private void addMetadataLinks(URL fileUrl, URL sessionInfoUrl, ProcessDescriptionType pdt) {
// The "xlin:type"-argument, i.e. mt.setType(TypeType.RESOURCE); was
// not used for the resources
// because validation fails with the cause:
// "cvc-complex-type.3.1: Value 'resource' of attribute 'xlin:type'
// of element 'ows:Metadata' is not valid
// with respect to the corresponding attribute use. Attribute
// 'xlin:type' has a fixed value of 'simple'."
if (fileUrl != null) {
MetadataType mt = pdt.addNewMetadata();
mt.setTitle("R Script");
mt.setHref(fileUrl.toExternalForm());
}
else
log.warn("Cannot add url to script, is null");
if (sessionInfoUrl != null) {
MetadataType mt = pdt.addNewMetadata();
mt.setTitle("R Session Info");
mt.setHref(sessionInfoUrl.toExternalForm());
}
else
log.warn("Cannot add url to session info, is null");
}
private void addProcessResources(ProcessDescriptionType pdt, RAnnotation annotation) {
// Add URL to resource folder > FIXME rather add resources one by one,
// see below.
try {
Object obj = annotation.getObjectValue(RAttribute.NAMED_LIST);
if (obj instanceof List< ? >) {
List< ? > namedList = (List< ? >) obj;
for (Object object : namedList) {
R_Resource resource = null;
if (object instanceof R_Resource)
resource = (R_Resource) object;
else
continue;
MetadataType mt = pdt.addNewMetadata();
mt.setTitle("Resource: " + resource.getResourceValue());
URL url = resource.getFullResourceURL(this.config.getResourceDirURL());
mt.setHref(url.toExternalForm());
}
}
}
catch (RAnnotationException e) {
e.printStackTrace();
log.error(e.getMessage());
}
}
/**
* @param pdt
* @param annotation
* @throws RAnnotationException
*/
private static void addProcessDescription(ProcessDescriptionType pdt, RAnnotation annotation) throws RAnnotationException {
String id = R_Config.WKN_PREFIX + annotation.getStringValue(RAttribute.IDENTIFIER);
pdt.addNewIdentifier().setStringValue(id);
String abstr = annotation.getStringValue(RAttribute.ABSTRACT);
pdt.addNewAbstract().setStringValue("" + abstr);
String title = annotation.getStringValue(RAttribute.TITLE);
pdt.addNewTitle().setStringValue("" + title);
pdt.setProcessVersion(annotation.getStringValue(RAttribute.VERSION));
}
private static void addInput(DataInputs inputs, RAnnotation annotation) throws RAnnotationException {
InputDescriptionType input = inputs.addNewInput();
String identifier = annotation.getStringValue(RAttribute.IDENTIFIER);
input.addNewIdentifier().setStringValue(identifier);
// title is optional, therefore it could be null
String title = annotation.getStringValue(RAttribute.TITLE);
if (title != null)
input.addNewTitle().setStringValue(title);
String abstr = annotation.getStringValue(RAttribute.ABSTRACT);
// abstract is optional, therefore it could be null
if (abstr != null)
input.addNewAbstract().setStringValue(abstr);
String min = annotation.getStringValue(RAttribute.MIN_OCCURS);
BigInteger minOccurs = BigInteger.valueOf(Long.parseLong(min));
input.setMinOccurs(minOccurs);
String max = annotation.getStringValue(RAttribute.MAX_OCCURS);
BigInteger maxOccurs = BigInteger.valueOf(Long.parseLong(max));
input.setMaxOccurs(maxOccurs);
if (annotation.isComplex()) {
addComplexInput(annotation, input);
}
else {
addLiteralInput(annotation, input);
}
}
/**
* @param annotation
* @param input
* @throws RAnnotationException
*/
private static void addLiteralInput(RAnnotation annotation, InputDescriptionType input) throws RAnnotationException {
LiteralInputType literalInput = input.addNewLiteralData();
DomainMetadataType dataType = literalInput.addNewDataType();
dataType.setReference(annotation.getProcessDescriptionType());
literalInput.setDataType(dataType);
literalInput.addNewAnyValue();
String def = annotation.getStringValue(RAttribute.DEFAULT_VALUE);
if (def != null) {
literalInput.setDefaultValue(def);
}
}
/**
* @param annotation
* @param input
* @throws RAnnotationException
*/
private static void addComplexInput(RAnnotation annotation, InputDescriptionType input) throws RAnnotationException {
SupportedComplexDataType complexInput = input.addNewComplexData();
ComplexDataDescriptionType cpldata = complexInput.addNewDefault().addNewFormat();
cpldata.setMimeType(annotation.getProcessDescriptionType());
String encod = annotation.getStringValue(RAttribute.ENCODING);
if (encod != null && encod != "base64")
cpldata.setEncoding(encod);
Class< ? extends IData> iClass = annotation.getDataClass();
if (iClass.equals(GenericFileDataBinding.class)) {
ComplexDataCombinationsType supported = complexInput.addNewSupported();
ComplexDataDescriptionType format = supported.addNewFormat();
format.setMimeType(annotation.getProcessDescriptionType());
encod = annotation.getStringValue(RAttribute.ENCODING);
if (encod != null)
format.setEncoding(encod);
if (encod == "base64") {
// set a format entry such that not encoded data is supported as
// well
ComplexDataDescriptionType format2 = supported.addNewFormat();
format2.setMimeType(annotation.getProcessDescriptionType());
}
}
else {
addSupportedInputFormats(complexInput, iClass);
}
}
private static void addOutput(ProcessOutputs outputs, RAnnotation out) throws RAnnotationException {
OutputDescriptionType output = outputs.addNewOutput();
String identifier = out.getStringValue(RAttribute.IDENTIFIER);
output.addNewIdentifier().setStringValue(identifier);
// title is optional, therefore it could be null
String title = out.getStringValue(RAttribute.TITLE);
if (title != null)
output.addNewTitle().setStringValue(title);
// is optional, therefore it could be null
String abstr = out.getStringValue(RAttribute.ABSTRACT);
if (abstr != null)
output.addNewAbstract().setStringValue(abstr);
if (out.isComplex()) {
addComplexOutput(out, output);
}
else {
addLiteralOutput(out, output);
}
}
/**
* @param out
* @param output
* @throws RAnnotationException
*/
private static void addLiteralOutput(RAnnotation out, OutputDescriptionType output) throws RAnnotationException {
LiteralOutputType literalOutput = output.addNewLiteralOutput();
DomainMetadataType dataType = literalOutput.addNewDataType();
dataType.setReference(out.getProcessDescriptionType());
literalOutput.setDataType(dataType);
}
/**
* @param out
* @param output
* @throws RAnnotationException
*/
private static void addComplexOutput(RAnnotation out, OutputDescriptionType output) throws RAnnotationException {
SupportedComplexDataType complexOutput = output.addNewComplexOutput();
ComplexDataDescriptionType complexData = complexOutput.addNewDefault().addNewFormat();
complexData.setMimeType(out.getProcessDescriptionType());
String encod = out.getStringValue(RAttribute.ENCODING);
if (encod != null && encod != "base64") {
// base64 shall not be default, but occur in the supported formats
complexData.setEncoding(encod);
}
Class< ? extends IData> iClass = out.getDataClass();
if (iClass.equals(GenericFileDataBinding.class)) {
ComplexDataCombinationsType supported = complexOutput.addNewSupported();
ComplexDataDescriptionType format = supported.addNewFormat();
format.setMimeType(out.getProcessDescriptionType());
encod = out.getStringValue(RAttribute.ENCODING);
if (encod != null) {
format.setEncoding(encod);
if (encod == "base64") {
// set a format entry such that not encoded data is supported as well
ComplexDataDescriptionType format2 = supported.addNewFormat();
format2.setMimeType(out.getProcessDescriptionType());
}
}
}
else {
addSupportedOutputFormats(complexOutput, iClass);
}
}
/**
* Searches all available datahandlers for supported encodings / schemas / mime-types and adds them to the
* supported list of an output
*
* @param complex
* IData class for which data handlers are searched
* @param supportedClass
*/
private static void addSupportedOutputFormats(SupportedComplexDataType complex,
Class< ? extends IData> supportedClass) {
// retrieve a list of generators which support the supportedClass-input
List<IGenerator> generators = GeneratorFactory.getInstance().getAllGenerators();
List<IGenerator> foundGenerators = new ArrayList<IGenerator>();
for (IGenerator generator : generators) {
Class< ? >[] supportedClasses = generator.getSupportedDataBindings();
for (Class< ? > clazz : supportedClasses) {
if (clazz.equals(supportedClass)) {
foundGenerators.add(generator);
}
}
}
ComplexDataCombinationsType supported = complex.addNewSupported();
for (int i = 0; i < foundGenerators.size(); i++) {
IGenerator generator = foundGenerators.get(i);
Format[] fullFormats = generator.getSupportedFullFormats();
for (Format format : fullFormats) {
ComplexDataDescriptionType newSupportedFormat = supported.addNewFormat();
String encoding = format.getEncoding();
if (encoding != null)
newSupportedFormat.setEncoding(encoding);
else
newSupportedFormat.setEncoding(IOHandler.DEFAULT_ENCODING);
newSupportedFormat.setMimeType(format.getMimetype());
String schema = format.getSchema();
if (schema != null)
newSupportedFormat.setSchema(schema);
}
}
}
/**
* Searches all available datahandlers for supported encodings / schemas / mime-types and adds them to the
* supported list of an output
*
* @param complex
* IData class for which data handlers are searched
* @param supportedClass
*/
private static void addSupportedInputFormats(SupportedComplexDataType complex,
Class< ? extends IData> supportedClass) {
// retrieve a list of parsers which support the supportedClass-input
List<IParser> parsers = ParserFactory.getInstance().getAllParsers();
List<IParser> foundParsers = new ArrayList<IParser>();
for (IParser parser : parsers) {
Class< ? >[] supportedClasses = parser.getSupportedDataBindings();
for (Class< ? > clazz : supportedClasses) {
if (clazz.equals(supportedClass)) {
foundParsers.add(parser);
}
}
}
// add properties for each parser which is found
ComplexDataCombinationsType supported = complex.addNewSupported();
for (int i = 0; i < foundParsers.size(); i++) {
IParser parser = foundParsers.get(i);
Format[] fullFormats = parser.getSupportedFullFormats();
for (Format format : fullFormats) {
ComplexDataDescriptionType newSupportedFormat = supported.addNewFormat();
String encoding = format.getEncoding();
if (encoding != null)
newSupportedFormat.setEncoding(encoding);
else
newSupportedFormat.setEncoding(IOHandler.DEFAULT_ENCODING);
newSupportedFormat.setMimeType(format.getMimetype());
String schema = format.getSchema();
if (schema != null)
newSupportedFormat.setSchema(schema);
}
}
}
}