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