content: /**
   * ﻿Copyright (C) 2007 - 2019 52°North Initiative for Geospatial Open Source
   * Software GmbH
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *    http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.n52.wps.server;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Iterator;
  import java.util.List;
  
  import net.opengis.ows.x11.AllowedValuesDocument.AllowedValues;
  import net.opengis.wps.x100.ComplexDataCombinationType;
  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.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.ProcessDescriptionsDocument;
  import net.opengis.wps.x100.ProcessDescriptionsDocument.ProcessDescriptions;
  import net.opengis.wps.x100.SupportedComplexDataInputType;
  import net.opengis.wps.x100.SupportedComplexDataType;
  
  import org.apache.xmlbeans.XmlOptions;
  import org.apache.xmlbeans.XmlValidationError;
  import org.n52.wps.FormatDocument.Format;
  import org.n52.wps.algorithm.descriptor.AlgorithmDescriptor;
  import org.n52.wps.algorithm.descriptor.ComplexDataInputDescriptor;
  import org.n52.wps.algorithm.descriptor.ComplexDataOutputDescriptor;
  import org.n52.wps.algorithm.descriptor.InputDescriptor;
  import org.n52.wps.algorithm.descriptor.LiteralDataInputDescriptor;
  import org.n52.wps.algorithm.descriptor.LiteralDataOutputDescriptor;
  import org.n52.wps.algorithm.descriptor.OutputDescriptor;
  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.IData;
  import org.n52.wps.server.observerpattern.IObserver;
  import org.n52.wps.server.observerpattern.ISubject;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import com.google.common.base.Strings;
  
  public abstract class AbstractDescriptorAlgorithm implements IAlgorithm, ISubject {
  
      private final static Logger LOGGER = LoggerFactory.getLogger(AbstractDescriptorAlgorithm.class);
      
      private AlgorithmDescriptor descriptor;
      private ProcessDescriptionType description;
      
      public AbstractDescriptorAlgorithm() {
          super();
      }
  
      @Override
      public synchronized ProcessDescriptionType getDescription() {
          if (description == null) {
              description = createProcessDescription();
          }
          return description;
      }
  
      @Override
      public String getWellKnownName() {
          return getAlgorithmDescriptor().getIdentifier();
      }
  
      private ProcessDescriptionType createProcessDescription() {
  
          AlgorithmDescriptor algorithmDescriptor = getAlgorithmDescriptor();
  
          ProcessDescriptionsDocument document = ProcessDescriptionsDocument.Factory.newInstance();
          ProcessDescriptions processDescriptions = document.addNewProcessDescriptions();
          ProcessDescriptionType processDescription = processDescriptions.addNewProcessDescription();
  
          if (algorithmDescriptor == null) {
              throw new IllegalStateException("Instance must have an algorithm descriptor");
          } else {
  
              // 1. Identifier
              processDescription.setStatusSupported(algorithmDescriptor.getStatusSupported());
              processDescription.setStoreSupported(algorithmDescriptor.getStoreSupported());
              processDescription.setProcessVersion(algorithmDescriptor.getVersion());
              processDescription.addNewIdentifier().setStringValue(algorithmDescriptor.getIdentifier());
              processDescription.addNewTitle().setStringValue( algorithmDescriptor.hasTitle() ?
                      algorithmDescriptor.getTitle() :
                      algorithmDescriptor.getIdentifier());
              if (algorithmDescriptor.hasAbstract()) {
                  processDescription.addNewAbstract().setStringValue(algorithmDescriptor.getAbstract());
              }
  
              // 2. Inputs
              Collection<InputDescriptor> inputDescriptors = algorithmDescriptor.getInputDescriptors();
              DataInputs dataInputs = null;
              if (inputDescriptors.size() > 0) {
                  dataInputs = processDescription.addNewDataInputs();
              }
              for (InputDescriptor inputDescriptor : inputDescriptors) {
  
                  InputDescriptionType dataInput = dataInputs.addNewInput();
                  dataInput.setMinOccurs(inputDescriptor.getMinOccurs());
                  dataInput.setMaxOccurs(inputDescriptor.getMaxOccurs());
  
                  dataInput.addNewIdentifier().setStringValue(inputDescriptor.getIdentifier());
                  dataInput.addNewTitle().setStringValue( inputDescriptor.hasTitle() ?
                          inputDescriptor.getTitle() :
                          inputDescriptor.getIdentifier());
                  if (inputDescriptor.hasAbstract()) {
                      dataInput.addNewAbstract().setStringValue(inputDescriptor.getAbstract());
                  }
  
                  if (inputDescriptor instanceof LiteralDataInputDescriptor) {
                      LiteralDataInputDescriptor<?> literalDescriptor = (LiteralDataInputDescriptor)inputDescriptor;
  
                      LiteralInputType literalData = dataInput.addNewLiteralData();
                      literalData.addNewDataType().setReference(literalDescriptor.getDataType());
  
                      if (literalDescriptor.hasDefaultValue()) {
                          literalData.setDefaultValue(literalDescriptor.getDefaultValue());
                      }
                      if (literalDescriptor.hasAllowedValues()) {
                          AllowedValues allowed = literalData.addNewAllowedValues();
                          for (String allowedValue : literalDescriptor.getAllowedValues()) {
                              allowed.addNewValue().setStringValue(allowedValue);
                          }
                      } else {
                          literalData.addNewAnyValue();
                      }
  
                  } else if (inputDescriptor instanceof ComplexDataInputDescriptor) {
                      SupportedComplexDataInputType complexDataType = dataInput.addNewComplexData();
                      ComplexDataInputDescriptor complexInputDescriptor =
                              (ComplexDataInputDescriptor)inputDescriptor;
                      if (complexInputDescriptor.hasMaximumMegaBytes()) {
                          complexDataType.setMaximumMegabytes(complexInputDescriptor.getMaximumMegaBytes());
                      }
                      describeComplexDataInputType(complexDataType, inputDescriptor.getBinding());
                  }
              }
  
              // 3. Outputs
              ProcessOutputs dataOutputs = processDescription.addNewProcessOutputs();
              Collection<OutputDescriptor> outputDescriptors = algorithmDescriptor.getOutputDescriptors();
              if (outputDescriptors.size() < 1) {
                 LOGGER.error("No outputs found for algorithm {}", algorithmDescriptor.getIdentifier());
              }
              for (OutputDescriptor outputDescriptor : outputDescriptors) {
  
                  OutputDescriptionType dataOutput = dataOutputs.addNewOutput();
                  dataOutput.addNewIdentifier().setStringValue(outputDescriptor.getIdentifier());
                  dataOutput.addNewTitle().setStringValue( outputDescriptor.hasTitle() ?
                          outputDescriptor.getTitle() :
                          outputDescriptor.getIdentifier());
                  if (outputDescriptor.hasAbstract()) {
                      dataOutput.addNewAbstract().setStringValue(outputDescriptor.getAbstract());
                  }
  
                  if (outputDescriptor instanceof LiteralDataOutputDescriptor) {
                      LiteralDataOutputDescriptor<?> literalDescriptor = (LiteralDataOutputDescriptor)outputDescriptor;
                      dataOutput.addNewLiteralOutput().addNewDataType().
                              setReference(literalDescriptor.getDataType());
                  } else if (outputDescriptor instanceof ComplexDataOutputDescriptor) {
                      describeComplexDataOutputType(dataOutput.addNewComplexOutput(), outputDescriptor.getBinding());
                 }
              }
          }
          return document.getProcessDescriptions().getProcessDescriptionArray(0);
      }
  
      private void describeComplexDataInputType(SupportedComplexDataType complexData, Class dataTypeClass) {
          List<IParser> parsers = ParserFactory.getInstance().getAllParsers();
          List<IParser> foundParsers = new ArrayList<IParser>();
          for (IParser parser : parsers) {
  // /*2.0*/    Class[] supportedClasses = parser.getSupportedInternalOutputDataType();
   /*3.0*/    Class[] supportedClasses = parser.getSupportedDataBindings();
              for (Class clazz : supportedClasses) {
                  if (dataTypeClass.isAssignableFrom(clazz)) {
                      foundParsers.add(parser);
                  }
              }
          }
          describeComplexDataType(complexData, foundParsers);
      }
  
      private void describeComplexDataOutputType(SupportedComplexDataType complexData, Class dataTypeClass) {
  
          List<IGenerator> generators = GeneratorFactory.getInstance().getAllGenerators();
          List<IGenerator> foundGenerators = new ArrayList<IGenerator>();
          for (IGenerator generator : generators) {
  // /*2.0*/    Class[] supportedClasses = generator.getSupportedInternalInputDataType(); // appears to have been removed in 52n WPS 3.0
   /*3.0*/    Class[] supportedClasses = generator.getSupportedDataBindings();
              for (Class clazz : supportedClasses) {
                  if (clazz.isAssignableFrom(dataTypeClass)) {
                      foundGenerators.add(generator);
                  }
              }
          }
          describeComplexDataType(complexData, foundGenerators);
      }
  
      private void describeComplexDataType(
              SupportedComplexDataType complexData,
              List<? extends IOHandler> handlers)
      {
          ComplexDataCombinationType defaultFormatType = complexData.addNewDefault();
          ComplexDataCombinationsType supportedFormatType = complexData.addNewSupported();
  
          boolean needDefault = true;
          for (IOHandler handler : handlers) {
              
              Format[] fullFormats = handler.getSupportedFullFormats();
              if (fullFormats != null && fullFormats.length > 0) {
                  if (needDefault) {
                      needDefault = false;
                      describeComplexDataFormat(
                              defaultFormatType.addNewFormat(),
                              fullFormats[0]);
                  }
                  for (int formatIndex = 0, formatCount = fullFormats.length; formatIndex < formatCount; ++formatIndex) {
                      describeComplexDataFormat(
                              supportedFormatType.addNewFormat(),
                              fullFormats[formatIndex]);
                  }
              } else {
                  
                  String[] formats = handler.getSupportedFormats();
                  
                  if (formats == null || formats.length == 0) {
                      LOGGER.warn("Skipping IOHandler {} in ProcessDescription generation for {}, no formats specified",
                              handler.getClass().getSimpleName(),
                              getWellKnownName());
                  } else {
                      // if formats, encodings or schemas arrays are 'null' or empty, create
                      // new array with single 'null' element.  We do this so we can utilize
                      // a single set of nested loops to process all permutations.  'null'
                      // values will not be output...
                      String[] encodings = handler.getSupportedEncodings();
                      if (encodings == null || encodings.length == 0) {
                          encodings = new String[] { null };
                      }
                      String[] schemas = handler.getSupportedSchemas();
                      if (schemas == null || schemas.length == 0) {
                          schemas = new String[] { null };
                      }
  
                      for (String format : formats) {
                          for (String encoding : encodings) {
                              for (String schema : schemas) {
                                  if (needDefault) {
                                      needDefault = false;
                                      describeComplexDataFormat(
                                              defaultFormatType.addNewFormat(),
                                              format, encoding, schema);
                                  }
                                  describeComplexDataFormat(
                                          supportedFormatType.addNewFormat(),
                                          format, encoding, schema);
                              }
                          }
                      }
                  }
              }
          }
      }
  
      private void describeComplexDataFormat(
              ComplexDataDescriptionType description,
              Format format)
      {
          describeComplexDataFormat(description,
                  format.getMimetype(),
                  format.getEncoding(),
                  format.getSchema());
      }
      
      private void describeComplexDataFormat(ComplexDataDescriptionType description,
                                             String format,
                                             String encoding,
                                             String schema) {
          if ( !Strings.isNullOrEmpty(format)) {
              description.setMimeType(format);
          }
          if ( !Strings.isNullOrEmpty(encoding)) {
              description.setEncoding(encoding);
          }
          if ( !Strings.isNullOrEmpty(schema)) {
              description.setSchema(schema);
          }
      }
  
      @Override
      public boolean processDescriptionIsValid() {
          XmlOptions xmlOptions = new XmlOptions();
          List<XmlValidationError> xmlValidationErrorList = new ArrayList<XmlValidationError>();
              xmlOptions.setErrorListener(xmlValidationErrorList);
          boolean valid = getDescription().validate(xmlOptions);
          if (!valid) {
              LOGGER.error("Error validating process description for " + getClass().getCanonicalName());
              for (XmlValidationError xmlValidationError : xmlValidationErrorList) {
                  LOGGER.error("\tMessage: {}", xmlValidationError.getMessage());
                  LOGGER.error("\tLocation of invalid XML: {}",
                       xmlValidationError.getCursorLocation().xmlText());
              }
          }
          return valid;
      }
  
      protected final synchronized AlgorithmDescriptor getAlgorithmDescriptor() {
          if (descriptor == null) {
              descriptor = createAlgorithmDescriptor();
          }
          return descriptor;
      }
      
      protected abstract AlgorithmDescriptor createAlgorithmDescriptor();
  
      @Override
      public Class<? extends IData> getInputDataType(String identifier) {
          AlgorithmDescriptor algorithmDescriptor = getAlgorithmDescriptor();
          if (algorithmDescriptor != null) {
              return getAlgorithmDescriptor().getInputDescriptor(identifier).getBinding();
          } else {
              throw new IllegalStateException("Instance must have an algorithm descriptor");
          }
      }
  
      @Override
      public Class<? extends IData> getOutputDataType(String identifier) {
          AlgorithmDescriptor algorithmDescriptor = getAlgorithmDescriptor();
          if (algorithmDescriptor != null) {
              return getAlgorithmDescriptor().getOutputDescriptor(identifier).getBinding();
          } else {
              throw new IllegalStateException("Instance must have an algorithm descriptor");
          }
      }
  
      private List observers = new ArrayList();
      private Object state = null;
  
      @Override
      public Object getState() {
          return state;
      }
  
      @Override
      public void update(Object state) {
          this.state = state;
          notifyObservers();
      }
  
      @Override
      public void addObserver(IObserver o) {
          observers.add(o);
      }
  
      @Override
      public void removeObserver(IObserver o) {
          observers.remove(o);
      }
  
      public void notifyObservers() {
          Iterator i = observers.iterator();
          while (i.hasNext()) {
              IObserver o = (IObserver) i.next();
              o.update(this);
          }
      }
  
      List<String> errorList = new ArrayList();
      protected List<String> addError(String error) {
          errorList.add(error);
          return errorList;
      }
  
      @Override
      public List<String> getErrors() {
          return errorList;
      }
  }
  