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