content: package alien4cloud.orchestrators.services;
  
  import java.io.IOException;
  import java.util.Map;
  
  import javax.annotation.Resource;
  import javax.inject.Inject;
  
  import com.google.common.collect.Sets;
  
  import org.springframework.context.ApplicationEventPublisher;
  import org.springframework.stereotype.Service;
  
  import alien4cloud.dao.IGenericSearchDAO;
  import alien4cloud.exception.NotFoundException;
  import alien4cloud.model.orchestrators.Orchestrator;
  import alien4cloud.model.orchestrators.OrchestratorConfiguration;
  import alien4cloud.orchestrators.events.OnOrchestratorConfigurationChanged;
  import alien4cloud.orchestrators.plugin.IOrchestratorPlugin;
  import alien4cloud.orchestrators.plugin.IOrchestratorPluginFactory;
  import alien4cloud.paas.OrchestratorPluginService;
  import alien4cloud.paas.exception.PluginConfigurationException;
  import alien4cloud.rest.utils.JsonUtil;
  import alien4cloud.utils.ReflectionUtil;
  
  /**
   * Manages orchestrator configuration
   */
  @Service
  public class OrchestratorConfigurationService {
      @Inject
      private OrchestratorService orchestratorService;
      @Resource(name = "alien-es-dao")
      private IGenericSearchDAO alienDAO;
      @Inject
      private OrchestratorPluginService orchestratorPluginService;
  
      @Inject
      private ApplicationEventPublisher publisher;
  
      /**
       * Return the type of configuration for a given orchestrator.
       *
       * @param id Id of the orchestrator for which to get configuration.
       * @return The type of the orchestrator.
       */
      public Class<?> getConfigurationType(String id) {
          Orchestrator orchestrator = orchestratorService.getOrFail(id);
          return getConfigurationType(orchestrator);
      }
  
      /**
       * Return the type of configuration for a given orchestrator.
       *
       * @param orchestrator Orchestrator for which to get configuration.
       * @return The type of the orchestrator.
       */
      private Class<?> getConfigurationType(Orchestrator orchestrator) {
          IOrchestratorPluginFactory orchestratorFactory = orchestratorService.getPluginFactory(orchestrator);
          return orchestratorFactory.getConfigurationType();
      }
  
      /**
       * Ensure that the configuration object parsed from json without typing is valid based on the orchestrator configuration type and return a valid typed
       * object.
       *
       * @param id if of the orchestrator.
       * @param configurationAsMap Configuration object (that may be a map parsed from json).
       * @return A typed configuration object.
       */
      public Object configurationAsValidObject(String id, Object configurationAsMap) throws IOException, PluginConfigurationException {
          Orchestrator orchestrator = orchestratorService.getOrFail(id);
          return configurationAsValidObject(orchestrator, configurationAsMap);
      }
  
      /**
       * Get the configuration for a given orchestrator.
       *
       * @param id Id of the orchestrator for which to get the configuration.
       * @return the instance of configuration for the given orchestrator
       */
      public OrchestratorConfiguration getConfigurationOrFail(String id) {
          OrchestratorConfiguration configuration = alienDAO.findById(OrchestratorConfiguration.class, id);
          if (configuration == null) {
              throw new NotFoundException("Orchestrator Configuration for id [" + id + "] doesn't exists.");
          }
          return configuration;
      }
  
      /**
       * Ensure that the configuration object parsed from json without typing is valid based on the orchestrator configuration type and return a valid typed
       * object.
       *
       * @param orchestrator Orchestrator for which to validated and compute a type configuration object.
       * @param configurationAsMap Configuration object (that may be a map parsed from json).
       * @return A typed configuration object.
       */
      private Object configurationAsValidObject(Orchestrator orchestrator, Object configurationAsMap) throws PluginConfigurationException, IOException {
          Class<?> configurationType = getConfigurationType(orchestrator);
          if (configurationType == null) {
              String message = "Orchestrator <" + orchestrator.getId() + "> using plugin <" + orchestrator.getPluginId() + "> <" + orchestrator.getPluginBean()
                      + "> cannot have configuration set (plugin has no configuration type).";
              throw new PluginConfigurationException(message);
          }
  
          return JsonUtil.readObject(JsonUtil.toString(configurationAsMap), configurationType);
      }
  
      /**
       * Update the configuration for the given cloud.
       *
       * @param id Id of the orchestrator for which to update the configuration.
       * @param newConfiguration The new configuration.
       */
      public synchronized void updateConfiguration(String id, Object newConfiguration) throws PluginConfigurationException, IOException {
          OrchestratorConfiguration configuration = alienDAO.findById(OrchestratorConfiguration.class, id);
          if (configuration == null) {
              throw new NotFoundException("No configuration exists for cloud [" + id + "].");
          }
  
          Object newConfigurationObj = configurationAsValidObject(id, newConfiguration);
  
          Object oldConfiguration = configuration.getConfiguration();
          Object oldConfigurationObj = configurationAsValidObject(id, oldConfiguration);
  
          Map<String, Object> oldConfAsMap = JsonUtil.toMap(JsonUtil.toString(oldConfigurationObj));
          Map<String, Object> newConfAsMap = (Map<String, Object>) newConfiguration;
  
          // merge the config so that old values are preserved
          ReflectionUtil.mergeObject(newConfigurationObj, oldConfigurationObj, false, Sets.difference(oldConfAsMap.keySet(), newConfAsMap.keySet()));
          configuration.setConfiguration(oldConfigurationObj);
  
          // Trigger update of the orchestrator's configuration if enabled.
          IOrchestratorPlugin orchestratorInstance = orchestratorPluginService.get(id);
          if (orchestratorInstance != null) {
              orchestratorInstance.setConfiguration(id, oldConfigurationObj);
          }
  
          alienDAO.save(configuration);
  
          publisher.publishEvent(new OnOrchestratorConfigurationChanged(this, id, configuration));
      }
  }
  