content: /**
   * ﻿Copyright (C) 2010 - 2019 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;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import net.opengis.wps.x100.ProcessDescriptionType;
  
  import org.apache.xmlbeans.XmlError;
  import org.apache.xmlbeans.XmlOptions;
  import org.n52.wps.PropertyDocument.Property;
  import org.n52.wps.commons.WPSConfig;
  import org.n52.wps.server.IAlgorithm;
  import org.n52.wps.server.ITransactionalAlgorithmRepository;
  import org.n52.wps.server.r.data.CustomDataTypeManager;
  import org.n52.wps.server.r.info.RProcessInfo;
  import org.rosuda.REngine.Rserve.RConnection;
  import org.rosuda.REngine.Rserve.RserveException;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * A static repository to retrieve the available algorithms.
   * 
   * @author Matthias Hinz
   * 
   */
  public class LocalRAlgorithmRepository implements ITransactionalAlgorithmRepository {
  
      private static Logger LOGGER = LoggerFactory.getLogger(LocalRAlgorithmRepository.class);
  
      // registered processes
      private Map<String, IAlgorithm> algorithms;
  
      private R_Config rConfig;
  
      public LocalRAlgorithmRepository() {
          LOGGER.info("Initializing LocalRAlgorithmRepository");
          this.algorithms = new HashMap<String, IAlgorithm>();
          this.rConfig = R_Config.getInstance();
  
          // Check WPS Config properties:
          RPropertyChangeManager changeManager = RPropertyChangeManager.getInstance();
          // unregistered scripts from repository folder will be added as
          // Algorithm to WPSconfig
          changeManager.updateRepositoryConfiguration();
  
          CustomDataTypeManager.getInstance().update();
          checkStartUpConditions();
  
          // finally add all available algorithms from the R config
          addAllAlgorithms();
      }
  
      /**
       * Check if repository is active and Rserve can be found
       * 
       * @return
       */
      private boolean checkStartUpConditions() {
          // check if the repository is active:
          String className = this.getClass().getCanonicalName();
          if ( !WPSConfig.getInstance().isRepositoryActive(className)) {
              LOGGER.debug("Local R Algorithm Repository is inactive.");
              return false;
          }
  
          // Try to build up a connection to Rserve. If it is refused, a new instance of Rserve will be opened
          LOGGER.debug("Trying to connect to Rserve.");
          try {
              RConnection testcon = rConfig.openRConnection();
              LOGGER.info("WPS successfully connected to Rserve.");
              testcon.close();
          }
          catch (RserveException e) {
              // try to start Rserve via batchfile if enabled
              LOGGER.error("[Rserve] Could not connect to Rserve. Rserve may not be available or may not be ready at the current time.",
                           e);
              return false;
          }
          return true;
      }
  
      private void addAllAlgorithms() {
          // add algorithms from config file to repository
          List<RProcessInfo> processInfoList = new ArrayList<RProcessInfo>();
          Property[] propertyArray = WPSConfig.getInstance().getPropertiesForRepositoryClass(this.getClass().getCanonicalName());
  
          for (Property property : propertyArray) {
              RProcessInfo processInfo = null;
              String algorithm_wkn = property.getStringValue();
  
              if (property.getName().equalsIgnoreCase(RWPSConfigVariables.ALGORITHM_PROPERTY_NAME.toString())) {
                  processInfo = new RProcessInfo(algorithm_wkn, this.rConfig);
                  processInfoList.add(processInfo);
              }
              else
                  continue;
  
              if (property.getActive()) {
                  if ( !processInfo.isAvailable()) {
                      // property.setActive(false);
                      // propertyChanged=true;
                      LOGGER.error("Missing R script for process '{}'. Process ignored - check WPS configuration.",
                                   algorithm_wkn);
                      continue;
                  }
  
                  if ( !processInfo.isValid()) {
                      // property.setActive(false);
                      // propertyChanged=true;
                      LOGGER.error("Invalid R script for process '{}'. You may enable/disable it manually from the Web Admin console. Check logs for details.",
                                   algorithm_wkn);
                  }
  
                  addAlgorithm(algorithm_wkn);
  
                  // //unavailable algorithms get an unavailable suffix in the
                  // properties and will be deactivated
                  // String unavailable_suffix = " (unavailable)";
                  //
                  // if(!rConfig.isScriptAvailable(algorithm_wkn)){
                  // if(!algorithm_wkn.endsWith(unavailable_suffix)){
                  // property.setName(algorithm_wkn+ unavailable_suffix);
                  // }
                  // property.setActive(false);
                  // LOGGER.error("[WPS4R] Missing R script for process "+algorithm_wkn+". Property has been set inactive. Check WPS config.");
                  //
                  // }else{
                  // if(algorithm_wkn.endsWith(unavailable_suffix)){
                  // algorithm_wkn = algorithm_wkn.replace(unavailable_suffix,
                  // "");
                  // property.setName(algorithm_wkn);
                  // }
                  // addAlgorithm(algorithm_wkn);
                  // }
  
              }
          }
  
          RProcessInfo.setRProcessInfoList(processInfoList);
      }
  
      public boolean addAlgorithms(String[] algorithms) {
          for (String algorithmClassName : algorithms) {
              addAlgorithm(algorithmClassName);
          }
          LOGGER.info("Algorithms registered!");
          return true;
  
      }
  
      @Override
      public IAlgorithm getAlgorithm(String algorithmName) {
          if ( !this.rConfig.getCacheProcesses()) {
              LOGGER.debug("Process cache disabled, creating new process for id '{}'", algorithmName);
              boolean b = addAlgorithm(algorithmName);
              if ( !b)
                  LOGGER.warn("Problem adding algorithm for deactivated cache.");
          }
  
          if ( !this.algorithms.containsKey(algorithmName))
              throw new RuntimeException("This repository does not contain an algorithm '" + algorithmName + "'");
          else
              return this.algorithms.get(algorithmName);
      }
  
      public Collection<String> getAlgorithmNames() {
          return new ArrayList<String>(this.algorithms.keySet());
      }
  
      @Override
      public boolean containsAlgorithm(String className) {
          return this.algorithms.containsKey(className);
      }
  
      private IAlgorithm loadAlgorithmAndValidate(String wellKnownName) throws Exception {
          LOGGER.debug("Loading algorithm '{}'", wellKnownName);
  
          IAlgorithm algorithm = new GenericRProcess(wellKnownName);
  
          if ( !algorithm.processDescriptionIsValid()) {
              // collect the errors
              ProcessDescriptionType description = algorithm.getDescription();
              XmlOptions validateOptions = new XmlOptions();
              ArrayList<XmlError> errorList = new ArrayList<XmlError>();
              validateOptions.setErrorListener(errorList);
              // run validation again
              description.validate(validateOptions);
              StringBuilder validationMessages = new StringBuilder();
              validationMessages.append("\n");
  
              for (XmlError e : errorList) {
                  validationMessages.append("[");
                  validationMessages.append(e.getLine());
                  validationMessages.append(" | ");
                  validationMessages.append(e.getErrorCode());
                  validationMessages.append("] ");
                  validationMessages.append(e.getMessage());
                  validationMessages.append("\n");
              }
              LOGGER.warn("Algorithm description is not valid {}. Errors: {}",
                          wellKnownName,
                          validationMessages.toString());
  
              throw new Exception("Could not load algorithm " + wellKnownName + ". ProcessDescription not valid: "
                      + validationMessages.toString());
          }
  
          return algorithm;
      }
  
      @Override
      public boolean addAlgorithm(Object processID) {
          if (processID instanceof String) {
              String algorithmName = (String) processID;
  
              try {
                  IAlgorithm a = loadAlgorithmAndValidate(algorithmName);
                  this.algorithms.put(algorithmName, a);
                  LOGGER.info("Algorithm under name '{}' added: {}", algorithmName, a);
  
                  return true;
              }
              catch (Exception e) {
                  String message = "Could not load algorithm for class name '" + algorithmName + "'";
                  LOGGER.error(message, e);
                  throw new RuntimeException(message + ": " + e.getMessage(), e);
              }
          }
          else
              return false;
      }
  
      @Override
      public boolean removeAlgorithm(Object processID) {
          if ( ! (processID instanceof String)) {
              LOGGER.debug("Could not remove algorithm with processID {}", processID);
              return false;
          }
  
          String id = (String) processID;
          if (this.algorithms.containsKey(id))
              this.algorithms.remove(id);
  
          LOGGER.info("Removed algorithm: {}", id);
          return true;
      }
  
      @Override
      public ProcessDescriptionType getProcessDescription(String processID) {
          return getAlgorithm(processID).getDescription();
      }
  
      @Override
      public void shutdown() {
          LOGGER.info("Shutting down ...");
          this.algorithms.clear();
      }
  
  }
  