content: /**
   * ﻿Copyright (C) 2007 - 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.webadmin;
  
  import java.io.File;
  import java.io.IOException;
  import java.io.UnsupportedEncodingException;
  import java.math.BigInteger;
  import java.net.URLDecoder;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.Map;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.n52.wps.AlgorithmRepositoryListDocument.AlgorithmRepositoryList;
  import org.n52.wps.DatahandlersDocument.Datahandlers;
  import org.n52.wps.FormatDocument.Format;
  import org.n52.wps.GeneratorDocument.Generator;
  import org.n52.wps.GeneratorListDocument.GeneratorList;
  import org.n52.wps.ParserDocument.Parser;
  import org.n52.wps.ParserListDocument.ParserList;
  import org.n52.wps.PropertyDocument.Property;
  import org.n52.wps.RemoteRepositoryDocument.RemoteRepository;
  import org.n52.wps.RemoteRepositoryListDocument.RemoteRepositoryList;
  import org.n52.wps.RepositoryDocument.Repository;
  import org.n52.wps.ServerDocument.Server;
  import org.n52.wps.WPSConfigurationDocument;
  import org.n52.wps.WPSConfigurationDocument.WPSConfiguration;
  import org.n52.wps.commons.WPSConfig;
  
  /**
   * This Bean changes the WPSConfiguration of the Application by processing formdata
   * @author Florian van Keulen, Raphael Rupprecht
   */
  public class ChangeConfigurationBean {
      private static transient Logger LOGGER = LoggerFactory.getLogger(ChangeConfigurationBean.class);
  
      /**
       * Types represents the different types of the Entries to proceed
       */
      private enum Types {Server, Repository, Parser, Generator, Formelement, RemoteRepository};
      
      private WPSConfigurationDocument wpsConfigurationDocument;
      private WPSConfiguration wpsConfiguration;
      
      private AlgorithmRepositoryList repositoryList;
      private RemoteRepositoryList remoteRepositoryList;
      private ParserList parserList;
      private GeneratorList generatorList;
      private Server server;
  
      private String serializedWPSConfiguraton = "";
  
      public void setSerializedWPSConfiguraton(String data){
          serializedWPSConfiguraton = data;
          processFormData(data);
          LOGGER.info("Saved and Activated new configuration!");
      }
  
      public String getSerializedWPSConfiguraton (){
          return serializedWPSConfiguraton;
      }
  
      /**
       * Processes the stringified formdata and updates the WPSConfiguration XML Bean
       * of the Application.
       * it is important, that the form input names are in a specific format.
       * Incorrect form input names will not processed.
       *
       * format:
       *   for a item:
       *   [Repository|Parser|Generator]-[number]_[Name|Class]
       *   for a Property:
       *   [Repository|Parser|Generator]-[number]_Property-[number]_[Name|Value]
       *
       * examples:
       *   items:
       *   Repository-13_Name
       *   Repository-13_Class
       *   properties:
       *   Parser-9_Property-14_Name
       *   Parser-9_Property-14_Value
       *
       *
       * @param formData stringified form data
       */
      private void processFormData(String formData) {
          // get WPSConfiguration XML Bean and create new Lists to hold the data
          wpsConfigurationDocument = WPSConfigurationDocument.Factory.newInstance();
          wpsConfiguration = wpsConfigurationDocument.addNewWPSConfiguration();
          server = wpsConfiguration.addNewServer();
          server.addNewDatabase();
          repositoryList = wpsConfiguration.addNewAlgorithmRepositoryList();
          remoteRepositoryList = wpsConfiguration.addNewRemoteRepositoryList();
          Datahandlers datahandlers = wpsConfiguration.addNewDatahandlers();
          parserList = datahandlers.addNewParserList();
          generatorList = datahandlers.addNewGeneratorList();
  
          //temporary maps to store data in order to proceed
          HashMap<String, String> serverValues = new HashMap<String, String>();
          HashMap<String, String> repositoryValues = new HashMap<String, String>();
          HashMap<String, String> remoteRepositoryValues = new HashMap<String, String>();
          HashMap<String, String> parserValues = new HashMap<String, String>();
          HashMap<String, String> generatorValues = new HashMap<String, String>();
  
          // split the stringified formdata into an array
          String[] dataArr = formData.split("&");
  
          String processingItemName = null;
          Types processingItemType = null;
          String[] formName;
  
          // main loop
          // loops over all entries and add them to the corresponding Map
          // if a item is completely proceed, it adds this to the WPSConfiguration
          for (int i = 0; i < dataArr.length; i++) {
              String dataStr = dataArr[i];
              // splits name and value
              String[] entryArr = dataStr.split("=");
              if(entryArr.length==2){
  	            // extracts the actual processing item type (enum Types)
  	            processingItemType = Types.valueOf(entryArr[0].split("-", 2)[0]);
  	
  	            // which type is the entry
  	            switch (processingItemType) {
  	
  	                case Server:
  	                    serverValues.put(entryArr[0].split("-", 2)[1], entryArr[1]);
  	                    break;
  	
  	                case Repository:
  	                    // splits the formname to the type with number
  	                    // as identifier
  	                    formName = entryArr[0].split("_", 2);
  	                    if(formName.length==2){
  		                    // the first item to proceed
  		                    if (processingItemName == null) {
  		                        processingItemName = formName[0];
  		                        repositoryValues.put(formName[1], entryArr[1]);
  		
  		                    // this item belongs to the same entry as the one before
  		                    } else if (processingItemName.equals(formName[0])) {
  		                        repositoryValues.put(formName[1], entryArr[1]);
  		
  		                    // new item, does not belong to the one before
  		                    } else {
  		                        //checks if the one before was also the same type
  		                        if (processingItemName.startsWith("Repository")) {
  		                            // adds the colected repository values to WPSConfig
  		                            createRepository(repositoryValues);
  		                            repositoryValues.clear();
  		                        }
  		                        processingItemName = formName[0];
  		                        repositoryValues.put(formName[1], entryArr[1]);
  		                    }
  		                    break;
  	                    }
  	
  	                case RemoteRepository:
  	                    // splits the formname to the type with number
  	                    // as identifier
  	                    formName = entryArr[0].split("_", 2);
  	                    if(formName.length==2){
  		                    // the first item to proceed
  		                    if (processingItemName == null) {
  		                        processingItemName = formName[0];
  		                        remoteRepositoryValues.put(formName[1], entryArr[1]);
  		
  		                    // this item belongs to the same entry as the one before
  		                    } else if (processingItemName.equals(formName[0])) {
  		                    	remoteRepositoryValues.put(formName[1], entryArr[1]);
  		
  		                    // new item, does not belong to the one before
  		                    } else {
  		                        //checks if the one before was also the same type
  		                        if (processingItemName.startsWith("RemoteRepository")) {
  		                            // adds the colected repository values to WPSConfig
  		                            createRemoteRepository(remoteRepositoryValues);
  		                            remoteRepositoryValues.clear();
  		                        }
  		                        processingItemName = formName[0];
  		                        remoteRepositoryValues.put(formName[1], entryArr[1]);
  		                    }
  		                    break;
  	                    }
  	
  	                case Parser:
  	                    // splits the formname to the type with number
  	                    // as identifier
  	                    formName = entryArr[0].split("_", 2);
  	                    if(formName.length==2){
  		                    // the first item to proceed
  		                    if (processingItemName == null) {
  		                        processingItemName = formName[0];
  		                        parserValues.put(formName[1], entryArr[1]);
  		
  		                    // this item belongs to the same entry as the one before
  		                    } else if (processingItemName.equals(formName[0])) {
  		                        parserValues.put(formName[1], entryArr[1]);
  		
  		                    // new item, does not belong to the one before
  		                    } else {
  		                        //checks if the one before was also the same type
  		                        if (processingItemName.startsWith("Parser")) {
  		                            // adds the collected parser values to WPSConfig
  		                            createParser(parserValues);
  		                            parserValues.clear();
  		                        }
  		                        processingItemName = formName[0];
  		                        parserValues.put(formName[1], entryArr[1]);
  		                    }
  		                    break;
  	                    }
  	                case Generator:
  	                    // splits the formname to the type with number
  	                    // as identifier
  	                    formName = entryArr[0].split("_", 2);
  	                    if(formName.length==2){
  		                    // the first item to proceed
  		                    if (processingItemName == null) {
  		                        processingItemName = formName[0];
  		                        generatorValues.put(formName[1], entryArr[1]);
  		
  		                    // this item belongs to the same entry as the one before
  		                    } else if (processingItemName.equals(formName[0])) {
  		                        generatorValues.put(formName[1], entryArr[1]);
  		
  		                    // new item, does not belong to the one before
  		                    } else {
  		                        //checks if the one before was also the same type
  		                        if (processingItemName.startsWith("Generator")) {
  		                            // adds the collected generator values to WPSConfig
  		                            createGenerator(generatorValues);
  		                            generatorValues.clear();
  		                        }
  		                        processingItemName = formName[0];
  		                        generatorValues.put(formName[1], entryArr[1]);
  		                    }
  		                    break;
  	                    }
  	                               	
  	            }
              }
  
  
          }
          //adds the server values to the WPSConfig
          createServer(serverValues);
  
          //adds the last not yet added repository to the WPSConfig
          if (!repositoryValues.isEmpty()) {
              createRepository(repositoryValues);
          }
  
          //adds the last not yet added remote repository to the WPSConfig
          if (!remoteRepositoryValues.isEmpty()) {
              createRemoteRepository(remoteRepositoryValues);
          }
          
          //adds the last not yet added parser to the WPSConfig
          if (!parserValues.isEmpty()) {
              createParser(parserValues);
          }
          //adds the last not yet added generator to the WPSConfig
          if (!generatorValues.isEmpty()) {
              createGenerator(generatorValues);
          }
  
          // 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);
          } catch (IOException e) {
              LOGGER.error("Could not write configuration to file: "+ e.getMessage());
          } catch (org.apache.xmlbeans.XmlException e){
              LOGGER.error("Could not generate XML File from Data: " + e.getMessage());
          }
      }
  
      /**
       * adds the name value pairs to the Server instance of the WPSconfig
       * @param serverEntries map with the name value pairs
       */
      private void createServer(HashMap<String, String> serverEntries) {
  
          if (serverEntries.containsKey("protocol")) {
          	server.setProtocol(serverEntries.get("protocol"));
          }
          if (serverEntries.containsKey("hostname")) {
              server.setHostname(serverEntries.get("hostname"));
          }
          if (serverEntries.containsKey("hostport")) {
              server.setHostport(serverEntries.get("hostport"));
          }
          if (serverEntries.containsKey("includeDataInputsInResponse")) {
              server.setIncludeDataInputsInResponse(Boolean.valueOf(serverEntries.get("includeDataInputsInResponse")));
          }
          if (serverEntries.containsKey("computationTimeoutMilliSeconds")) {
              server.setComputationTimeoutMilliSeconds(serverEntries.get("computationTimeoutMilliSeconds"));
          }
          if (serverEntries.containsKey("cacheCapabilites")) {
              server.setCacheCapabilites(Boolean.valueOf(serverEntries.get("cacheCapabilites")));
          }
          if (serverEntries.containsKey("webappPath")) {
              server.setWebappPath(serverEntries.get("webappPath"));
          }
          if (serverEntries.containsKey("repoReloadInterval")) {
              server.setRepoReloadInterval(Double.parseDouble(serverEntries.get("repoReloadInterval")));
          }
          if (serverEntries.containsKey("minPoolSize")) {
          	server.setMinPoolSize(new BigInteger(serverEntries.get("minPoolSize")));
          }
          if (serverEntries.containsKey("maxPoolSize")) {
          	server.setMaxPoolSize(new BigInteger(serverEntries.get("maxPoolSize")));
          }
          if (serverEntries.containsKey("keepAliveSeconds")) {
          	server.setKeepAliveSeconds(new BigInteger(serverEntries.get("keepAliveSeconds")));
          }
          if (serverEntries.containsKey("maxQueuedTasks")) {
          	server.setMaxQueuedTasks(new BigInteger(serverEntries.get("maxQueuedTasks")));
          }
      }
  
      /**
       * adds the name value pairs to the RepositoryList instance of the WPSconfig
       * @param repositoryEntries map with the name value pairs belonging to one Repository
       */
      private void createRepository(HashMap<String, String> repositoryEntries) {
          // if Name or Class does not exist, Repository is not added
          if (repositoryEntries.isEmpty() || !repositoryEntries.containsKey("Name") || !repositoryEntries.containsKey("Class")) {
              return;
          }
          // get new repository and add Name and Class and Active state
          Repository repository = repositoryList.addNewRepository();
          repository.setName(repositoryEntries.remove("Name"));
          repository.setClassName(repositoryEntries.remove("Class"));
          
          String activeString = repositoryEntries.remove("Activator");
          boolean active = true;
          if(activeString == null){	// Activator=off will only be recognized when activeString is null, the jQuery form.serialize() method won't insert "Activator=off"!
          	active = false;
          } else {
          	if(activeString.equals("on")){
          		// everything is fine
          	} else {
          		LOGGER.error("Error - the Activator seems not to be set.");
          	}
          }
          repository.setActive(active);
  
          // if the map has more entries, Properties are present and will be proceed
          if (!repositoryEntries.isEmpty()) {
              repository.setPropertyArray(getPropertyArray(repositoryEntries));
          }
      }
  
      /**
       * adds the name value pairs to the RepositoryList instance of the WPSconfig
       * @param repositoryEntries map with the name value pairs belonging to one Repository
       */
      private void createRemoteRepository(HashMap<String, String> repositoryEntries) {
          // if Name or Class does not exist, Repository is not added
          if (repositoryEntries.isEmpty() || !repositoryEntries.containsKey("Name")) {
              return;
          }
          // get new repository and add Name and Class and Active state
          RemoteRepository repository = remoteRepositoryList.addNewRemoteRepository();
          repository.setName(repositoryEntries.remove("Name"));
          
          String activeString = repositoryEntries.remove("Activator");
          boolean active = true;
          if(activeString == null){	// Activator=off will only be recognized when activeString is null, the jQuery form.serialize() method won't insert "Activator=off"!
          	active = false;
          } else {
          	if(activeString.equals("on")){
          		// everything is fine
          	} else {
          		LOGGER.error("Error - the Activator seems not to be set.");
          	}
          }
          repository.setActive(active);
  
          // if the map has more entries, Properties are present and will be proceed
          if (!repositoryEntries.isEmpty()) {
              repository.setPropertyArray(getPropertyArray(repositoryEntries));
          }
      }    
      
      /**
       * adds the name value pairs to the ParserList instance of the WPSconfig
       * @param parserEntries map with the name value pairs belonging to one Parser
       */
      private void createParser(HashMap<String, String> parserEntries) {
          // if Name or Class does not exist, Parser is not added
          if (parserEntries.isEmpty() || !parserEntries.containsKey("Name") || !parserEntries.containsKey("Class")) {
              return;
          }
  
          // get new parser and add Name and Class
          Parser parser = parserList.addNewParser();
          parser.setName(parserEntries.remove("Name"));
          parser.setClassName(parserEntries.remove("Class"));
          
          String activeString = parserEntries.remove("Activator");
          boolean active = true;
          if(activeString == null){	// Activator=off will only be recognized when activeString is null, the jQuery form.serialize() method won't insert "Activator=off"!
          	active = false;
          } else {
          	if(activeString.equals("on")){
          		// everything is fine
          	} else {
          		LOGGER.error("Error - the Activator seems not to be set.");
          	}
          }
          parser.setActive(active);       
  
          // if the map has more entries, Properties are present and will be proceed
          if (!parserEntries.isEmpty()) {
              try {
  				parser.setFormatArray(getFormatArray(parserEntries));
  			} catch (UnsupportedEncodingException e) {
  				LOGGER.error("Error decoding format attributes in UTF-8.");
  			}
              parser.setPropertyArray(getPropertyArray(parserEntries));
          }
      }
  
      /**
       * adds the name value pairs to the GeneratorList instance of the WPSconfig
       * @param generatorEntries map with the name value pairs belonging to one Generator
       */
      private void createGenerator(HashMap<String, String> generatorEntries) {
          // if Name or Class does not exist, Generator is not added
          if (generatorEntries.isEmpty() || !generatorEntries.containsKey("Name") || !generatorEntries.containsKey("Class")) {
              return;
          }
  
          // get new generator and add Name and Class
          Generator generator = generatorList.addNewGenerator();
          generator.setName(generatorEntries.remove("Name"));
          generator.setClassName(generatorEntries.remove("Class"));
  
          String activeString = generatorEntries.remove("Activator");
          boolean active = true;
          if(activeString == null){	// Activator=off will only be recognized when activeString is null, the jQuery form.serialize() method won't insert "Activator=off"!
          	active = false;
          } else {
          	if(activeString.equals("on")){
          		// everything is fine
          	} else {
          		LOGGER.error("Error - the Activator seems not to be set.");
          	}
          }
          generator.setActive(active);             
          
          // if the map has more entries, Properties are present and will be proceed
          if (!generatorEntries.isEmpty()) {
              try {
  				generator.setFormatArray(getFormatArray(generatorEntries));
  			} catch (UnsupportedEncodingException e) {
  				LOGGER.error("Error decoding format attributes in UTF-8.");
  			}
              generator.setPropertyArray(getPropertyArray(generatorEntries));
          }
      }
  
      /**
       * generates a Property[] of the name value pairs in the map
       * @param properties map with name value pairs belonging to one entry
       * @return Property[]
       */
      private Property[] getPropertyArray(HashMap<String, String> properties) {
          ArrayList<Property> propArr = new ArrayList<Property>();   
          
          Map<Integer, Property> propertyNumberPropertyMap = new HashMap<Integer, Property>();
          
          for (String property : properties.keySet()) {
          	
          	/*
          	 * right now there are only Property and Format elements in the HashMap
          	 * here we are only interested in Property elements
          	 */
          	if(property.contains("Format")){
          		continue;
          	}
          	
          	Integer propertyNumber = Integer.parseInt(property.substring(property.indexOf("-") + 1, property.indexOf("_")));
          	
          	if(propertyNumberPropertyMap.keySet().contains(propertyNumber)){
          		continue;
          	}
          	
          	String processingProperty = property.split("_")[0];
              String propertyName = properties.get(processingProperty + "_Name");
              if(propertyName == null){
              	continue;
              }
              try {
  				propertyName = URLDecoder.decode(propertyName, "UTF-8");
  			} catch (UnsupportedEncodingException e) {
  				LOGGER.error(e.getMessage(), e);
  			}
            
              String propertyValue = properties.get(processingProperty + "_Value");
              if(propertyValue == null){
              	continue;
              }
              try {
  				propertyValue = URLDecoder.decode(propertyValue, "UTF-8");
  			} catch (UnsupportedEncodingException e) {
  				LOGGER.error(e.getMessage(), e);
  			}
              String propertyActiveString = properties.get(processingProperty + "_Activator");
              
              boolean proptertyActive = false;
              if(propertyActiveString != null){
              	if(propertyActiveString.equals("on")){
              		proptertyActive = true;
              	}
              }
              
              if (propertyName != null) {
                  Property prop = Property.Factory.newInstance();
                  prop.setName(propertyName);
                  prop.setActive(proptertyActive);
                  if (propertyValue != null) {
                      prop.setStringValue(propertyValue);
                  }
                  propertyNumberPropertyMap.put(propertyNumber, prop);
                  propArr.add(prop);
              }
  		}
  
          Property[] arr = {};
          arr = propertyNumberPropertyMap.values().toArray(arr);
          
          Arrays.sort(arr, new PropertyComparator());
          
          return arr;
      }
      
      /**
       * generates a Format[] of the name value pairs in the map
       * @param properties map with name value pairs belonging to one entry
       * @return Format[]
       * @throws UnsupportedEncodingException 
       */
      private Format[] getFormatArray(HashMap<String, String> properties) throws UnsupportedEncodingException {
          
          Map<Integer, Format> formatNumberFormatMap = new HashMap<Integer, Format>();
          
          for (String property : properties.keySet()) {
          	
          	/*
          	 * right now there are only Property and Format elements in the HashMap
          	 * here we are only interested in Format elements
          	 */
          	if(property.contains("Property")){
          		continue;
          	}
          	
          	Integer formatNumber = Integer.parseInt(property.substring(property.indexOf("-") + 1, property.indexOf("_")));
          	
          	if(formatNumberFormatMap.keySet().contains(formatNumber)){
          		continue;
          	}
          	
          	String processingProperty = property.split("_")[0];
              String formatMimeType = properties.get(processingProperty + "_Mime");
              if(formatMimeType == null){
              	continue;
              }
              formatMimeType = URLDecoder.decode(formatMimeType, "UTF-8");
            
              String formatEncoding = properties.get(processingProperty + "_Enc");
              
              if(formatEncoding != null){                
                  formatEncoding = URLDecoder.decode(formatEncoding, "UTF-8");
              }
              
              String formatSchema = properties.get(processingProperty + "_Schem");
              
              if(formatSchema != null){                
                  formatSchema = URLDecoder.decode(formatSchema, "UTF-8");
              }
              
              if (formatMimeType != null) {
              	Format format = Format.Factory.newInstance();
              	format.setMimetype(formatMimeType);
                  if (formatEncoding != null && !formatEncoding.equals("default")) {
                  	format.setEncoding(formatEncoding);
                  }
                  if (formatSchema != null) {
                      format.setSchema(formatSchema);
                  }
                  formatNumberFormatMap.put(formatNumber, format);
              }
  		}
  
          Format[] arr = {};
          return formatNumberFormatMap.values().toArray(arr);
      }
      
      /**
       * Comparator class. Used for sorting Properties. Algorithm-Properties will be put at the end,
       * Algorithm names descending.
       *
       */
      class PropertyComparator implements Comparator<Property>{
  
  		@Override
  		public int compare(Property o1, Property o2) {			
  			
  			String o1Name = o1.getName();
  			String o2Name = o2.getName();
  			
  			if(o1Name.equals("Algorithm") && !o2Name.equals("Algorithm")){
  				return 1;
  			}else if(!o1Name.equals("Algorithm") && o2Name.equals("Algorithm")){
  				return -1;
  			}else if(o1Name.equals("Algorithm") && o2Name.equals("Algorithm")){
  				return o1.getStringValue().compareTo(o2.getStringValue());
  			}else{
  				return o1Name.compareTo(o2Name);
  			}
  		}
      	
      }
  }
  