content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.apache.ambari.server.topology;
  
  import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.PROPERTIES_ATTRIBUTES_PROPERTY_ID;
  import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.PROPERTIES_PROPERTY_ID;
  
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  /**
   * Creates a configuration instance from user provided properties.
   * Supports both forms of configuration syntax.
   * todo: document both forms here
  */
  public class ConfigurationFactory {
  
    private static final String SCHEMA_IS_NOT_SUPPORTED_MESSAGE =
        "Provided configuration format is not supported";
  
    public Configuration getConfiguration(Collection<Map<String, String>> configProperties) {
      Map<String, Map<String, String>> properties = new HashMap<>();
      Map<String, Map<String, Map<String, String>>> attributes = new HashMap<>();
      Configuration configuration = new Configuration(properties, attributes);
  
      if (configProperties != null) {
        for (Map<String, String> typeMap : configProperties) {
          //todo: can we have a different strategy for each type?
          ConfigurationStrategy strategy = decidePopulationStrategy(typeMap);
          for (Map.Entry<String, String> entry : typeMap.entrySet()) {
            String[] propertyNameTokens = entry.getKey().split("/");
            strategy.setConfiguration(configuration, propertyNameTokens, entry.getValue());
          }
        }
      }
      return configuration;
    }
  
    private ConfigurationStrategy decidePopulationStrategy(Map<String, String> configuration) {
      if (configuration != null && !configuration.isEmpty()) {
        String keyEntry = configuration.keySet().iterator().next();
        List<String> keyNameTokens = splitConfigurationKey(keyEntry);
  
        if (isKeyInLegacyFormat(keyNameTokens)) {
          return new ConfigurationStrategyV1();
        }
        else if (isKeyInNewFormat(keyNameTokens)) {
          return new ConfigurationStrategyV2();
        }
        else {
          throw new IllegalArgumentException(SCHEMA_IS_NOT_SUPPORTED_MESSAGE);
        }
      }
      else {
        return new ConfigurationStrategyV2();
      }
    }
  
    static List<String> splitConfigurationKey(String configurationKey) {
      return Arrays.asList(configurationKey.split("/"));
    }
  
    static boolean isKeyInLegacyFormat(List<String> configurationKey) {
      return configurationKey.size() == 2;
    }
  
    static boolean isKeyInNewFormat(List<String> configurationKey) {
      String propertiesType = configurationKey.get(1);
      return configurationKey.size() == 3 && PROPERTIES_PROPERTY_ID.equals(propertiesType)
        || configurationKey.size() == 4 && PROPERTIES_ATTRIBUTES_PROPERTY_ID.equals(propertiesType);
    }
  
    /**
     * The structure of blueprints is evolving where multiple resource
     * structures are to be supported. This class abstracts the population
     * of configurations which have changed from a map of key-value strings,
     * to an map containing 'properties' and 'properties_attributes' maps.
     *
     * Extending classes can determine how they want to populate the
     * configuration maps depending on input.
     */
    private static abstract class ConfigurationStrategy {
  
      protected abstract void setConfiguration(Configuration configuration,
                                               String[] propertyNameTokens,
                                               String propertyValue);
  
    }
  
    /**
     * Original blueprint configuration format where configs were a map
     * of strings.
     */
    protected static class ConfigurationStrategyV1 extends ConfigurationStrategy {
  
  
      @Override
      protected void setConfiguration(Configuration configuration, String[] propertyNameTokens, String propertyValue) {
        configuration.setProperty(propertyNameTokens[0], propertyNameTokens[1], propertyValue);
      }
    }
  
    /**
     * New blueprint configuration format where configs are a map from 'properties' and
     * 'properties_attributes' to a map of strings.
     *
     * @since 1.7.0
     */
    protected static class ConfigurationStrategyV2 extends ConfigurationStrategy {
  
      @Override
      protected void setConfiguration(Configuration configuration, String[] propertyNameTokens, String propertyValue) {
        String type = propertyNameTokens[0];
        if (BlueprintFactory.PROPERTIES_PROPERTY_ID.equals(propertyNameTokens[1])) {
          configuration.setProperty(type, propertyNameTokens[2], propertyValue);
        } else if (BlueprintFactory.PROPERTIES_ATTRIBUTES_PROPERTY_ID.equals(propertyNameTokens[1])) {
          configuration.setAttribute(type, propertyNameTokens[3], propertyNameTokens[2], propertyValue);
        }
      }
    }
  }
  