/** * 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.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import org.n52.wps.PropertyDocument.Property; import org.n52.wps.RepositoryDocument.Repository; import org.n52.wps.WPSConfigurationDocument; import org.n52.wps.WPSConfigurationDocument.WPSConfiguration; import org.n52.wps.commons.WPSConfig; import org.n52.wps.server.ExceptionReport; import org.n52.wps.server.r.data.CustomDataTypeManager; import org.n52.wps.server.r.syntax.RAnnotationException; import org.n52.wps.server.r.util.RFileExtensionFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class RPropertyChangeManager implements PropertyChangeListener { private static Logger LOGGER = LoggerFactory.getLogger(RPropertyChangeManager.class); private static RPropertyChangeManager instance; private static R_Config config; private RPropertyChangeManager() { config = R_Config.getInstance(); } public static RPropertyChangeManager getInstance() { if (instance == null) { instance = new RPropertyChangeManager(); WPSConfig.getInstance().addPropertyChangeListener(WPSConfig.WPSCONFIG_PROPERTY_EVENT_NAME, instance); } return instance; } public static RPropertyChangeManager reInitialize() { WPSConfig.getInstance().removePropertyChangeListener(WPSConfig.WPSCONFIG_PROPERTY_EVENT_NAME, instance); instance = new RPropertyChangeManager(); return instance; } @Override public void propertyChange(PropertyChangeEvent evt) { // String repName = LocalRAlgorithmRepository.class.getCanonicalName(); // RepositoryManager manager = RepositoryManager.getInstance(); // LocalRAlgorithmRepository repository = (LocalRAlgorithmRepository) // manager.getRepositoryForClassName(repName); LOGGER.info("received PropertyChangeEvent: " + evt.getPropertyName()); updateRepositoryConfiguration(); CustomDataTypeManager.getInstance().update(); // deleteUnregisteredScripts(); // TODO: How might processes be renamed? } private class PropertyComparator implements Comparator<Property> { @Override public int compare(Property o1, Property o2) { int com1 = o1.getName().compareToIgnoreCase(o2.getName()); if (com1 != 0) return com1; return (o1.getStringValue().compareToIgnoreCase(o2.getStringValue())); } } /** * Reads the current repository properties from the wps config and matches them with registered R scripts. * It will add algorithms and default parameters is necessary * * @throws ExceptionReport * @throws IOException * @throws RAnnotationException * */ public void updateRepositoryConfiguration() { // Retrieve repository document and properties: String localRAlgorithmRepository_className = LocalRAlgorithmRepository.class.getCanonicalName(); Repository[] repositoryDocuments = WPSConfig.getInstance().getRegisterdAlgorithmRepositories(); Repository repositoryDocument = null; for (Repository doc : repositoryDocuments) { if (doc.getClassName().equals(localRAlgorithmRepository_className)) { repositoryDocument = doc; } } if (repositoryDocument == null) { LOGGER.error("Local R Algorithm Repository is not registered"); return; } Property[] oldPropertyArray = repositoryDocument.getPropertyArray(); HashMap<String, Property> algorithmPropertyHash = new HashMap<String, Property>(); boolean propertyChanged = false; ArrayList<Property> newPropertyList = new ArrayList<Property>(); // retrieve set of string representations for all config variables: HashSet<String> configVariableNames = new HashSet<String>(); for (RWPSConfigVariables var : RWPSConfigVariables.values()) { configVariableNames.add(var.toString().toLowerCase()); } for (Property property : oldPropertyArray) { String pname = property.getName().toLowerCase(); // check the name and active state if (pname.equalsIgnoreCase(RWPSConfigVariables.ALGORITHM_PROPERTY_NAME.toString())) { LOGGER.debug("Algorithm property: " + property); // put id into a dictionary to check and add later: algorithmPropertyHash.put(property.getStringValue(), property); } else { LOGGER.debug("NOT-algorithm property: " + property); if (configVariableNames.contains(pname)) { boolean success = handleConfigVariable(property); if ( !success) LOGGER.warn("Invalid config variable was omitted and deleted: " + property); // config variable should occur only once, doubles are omitted: configVariableNames.remove(pname); } else { // valid properties which are not algorithms will be just passed to the new list LOGGER.debug("Unprocessed property: " + property); } newPropertyList.add(property); } } propertyChanged = checkMandatoryParameters(repositoryDocument, propertyChanged, newPropertyList, configVariableNames); propertyChanged = registerRScripts(repositoryDocument, algorithmPropertyHash, propertyChanged, newPropertyList); // there might be registered algorithms, which don't got a script file any more, those will be deleted // here: if ( !algorithmPropertyHash.isEmpty()) propertyChanged = true; //Bugfix #222: Why is the order checked? //This causes a second reload of the wps_config, even if no property was changed //The properties are sorted anyway // propertyChanged = checkPropertyOrder(oldPropertyArray, propertyChanged); if (propertyChanged) { Property[] newPropertyArray = newPropertyList.toArray(new Property[0]); // sort list of properties lexicographically: Arrays.sort(newPropertyArray, new PropertyComparator()); repositoryDocument.setPropertyArray(newPropertyArray); propertyChanged = true; // write new WPSConfig if property had to be changed WPSConfigurationDocument wpsConfigurationDocument = WPSConfigurationDocument.Factory.newInstance(); WPSConfiguration wpsConfig = WPSConfig.getInstance().getWPSConfig(); wpsConfigurationDocument.setWPSConfiguration(wpsConfig); // writes the new WPSConfig to a file try { String configurationPath = WPSConfig.getConfigPath(); File XMLFile = new File(configurationPath); wpsConfigurationDocument.save(XMLFile, new org.apache.xmlbeans.XmlOptions().setUseDefaultNamespace().setSavePrettyPrint()); WPSConfig.forceInitialization(configurationPath); LOGGER.info("WPS Config was changed."); } catch (IOException e) { LOGGER.error("Could not write configuration to file", e); } catch (org.apache.xmlbeans.XmlException e) { LOGGER.error("Could not generate XML File from Data", e); } } LOGGER.info("Updated repository configuration. Batch start R if not running: {}", config.getEnableBatchStart()); } private boolean checkPropertyOrder(Property[] oldPropertyArray, boolean propertyChanged) { boolean pChange = propertyChanged; // check if properties need to be re-ordered: if ( !propertyChanged) { PropertyComparator comp = new PropertyComparator(); for (int i = 0; i < oldPropertyArray.length - 1; i++) { int order = comp.compare(oldPropertyArray[i], oldPropertyArray[i + 1]); if (order > 0) { pChange = true; break; } } } return pChange; } /** * * @param repositoryDocument * @param algorithmPropertyHash * A hashmap wkn -> algorithm property for all algorithms of the wps config * @param propertyChanged * indicates, if a property has been changed previously. If so, the output will be true as well * @param newPropertyList * The property list that possibly replaces the old property list from the wps config * @return true, if any properties were changed or added to the property array */ private boolean registerRScripts(Repository repositoryDocument, HashMap<String, Property> algorithmPropertyHash, boolean propertyChanged, ArrayList<Property> newPropertyList) { boolean pChanged = propertyChanged; // check script dir for R process files Collection<File> scriptDirs = config.getScriptDirFullPath(); config.resetWknFileMapping(); for (File file : scriptDirs) { boolean b = registerRScriptsFromDirectory(file, repositoryDocument, algorithmPropertyHash, newPropertyList, pChanged); if (b) pChanged = b; } return pChanged; } private boolean registerRScriptsFromDirectory(File directory, Repository repositoryDocument, HashMap<String, Property> algorithmPropertyHash, ArrayList<Property> newPropertyList, boolean pChanged) { if ( !directory.isDirectory()) { LOGGER.error("Provided file is not a directory, cannot load scripts: {}", directory); return false; } File[] scripts = directory.listFiles(new RFileExtensionFilter()); LOGGER.debug("Loading {} script files from {}: {}", scripts.length, directory, Arrays.toString(scripts)); for (File scriptf : scripts) { try { boolean registered = config.registerScript(scriptf); if ( !registered) { LOGGER.debug("Could not register script based on file {}", scriptf); continue; } String wkn = config.getWKNForScriptFile(scriptf); Property prop = algorithmPropertyHash.get(wkn); // case: property is missing in wps config if (prop == null) { // Change Property if Algorithm is not inside process // description: prop = repositoryDocument.addNewProperty(); prop.setActive(true); prop.setName(RWPSConfigVariables.ALGORITHM_PROPERTY_NAME.toString()); prop.setStringValue(wkn); newPropertyList.add(prop); LOGGER.debug("Added new algorithm property to repo document: {}", prop); pChanged = true; } else { LOGGER.debug("Algorithm property already repo document: {}", prop); newPropertyList.add(algorithmPropertyHash.remove(wkn)); } } catch (RAnnotationException e) { LOGGER.error(e.getMessage()); } catch (IOException e) { LOGGER.error(e.getMessage()); } catch (ExceptionReport e) { LOGGER.error(e.getMessage()); } /* * if(prop.getActive() && addAlgorithm){ repository.addAlgorithm(wkn); } */ } return pChanged; } /** * This method will insert any config variables (with default values) that should always occur in the wps * config * * @param repositoryDocument * @param propertyChanged * @param newPropertyList * @param unusedConfigVariables * Names of all config variables that do NOT occur in the property array * @return */ private boolean checkMandatoryParameters(Repository repositoryDocument, boolean propertyChanged, ArrayList<Property> newPropertyList, HashSet<String> unusedConfigVariables) { /* * mandatory parameters, the ones from param that have not been covered yet. */ // If there was no required parameters given by WPSconfig, host and port // defaults will be added // (RServe_User and RServe_port won't be added) if (unusedConfigVariables.contains(RWPSConfigVariables.RSERVE_HOST.toString().toLowerCase())) { Property host = repositoryDocument.addNewProperty(); host.setActive(true); host.setName(RWPSConfigVariables.RSERVE_HOST.toString()); host.setStringValue(config.getRServeHost()); newPropertyList.add(host); propertyChanged = true; } if (unusedConfigVariables.contains(RWPSConfigVariables.RSERVE_PORT.toString().toLowerCase())) { Property port = repositoryDocument.addNewProperty(); port.setActive(true); port.setName(RWPSConfigVariables.RSERVE_PORT.toString()); port.setStringValue(Integer.toString(config.getRServePort())); newPropertyList.add(port); propertyChanged = true; } return propertyChanged; } /** * Retrieves configuration parameter from WPS config * * @param property * @return true */ private boolean handleConfigVariable(Property property) { String pname = property.getName(); // RWPSConfigVariables.v boolean success = false; for (RWPSConfigVariables configvariable : RWPSConfigVariables.values()) { if (pname.equalsIgnoreCase(configvariable.toString())) { config.setConfigVariable(configvariable, property.getStringValue()); success = true; break; } } return success; } /** * Deletes *.R file from repository TODO give this method a purpose i.e. implement functionality to delete * process during runtime */ private boolean deleteScript(String processName) { boolean deleted = false; try { File processFile = config.getScriptFileForWKN(processName); deleted = processFile.delete(); if ( !deleted) LOGGER.error("Process file {} could not be deleted, process just removed temporarly", processFile.getName()); else LOGGER.info("Process {} and process file {} successfully deleted!", processName, processFile.getName()); } catch (Exception e) { LOGGER.error("Process file refering to {} could not be deleted", processName, e); } return deleted; } }