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.controller.internal;
  
  import java.util.Collections;
  import java.util.EnumSet;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Set;
  
  import org.apache.ambari.server.AmbariException;
  import org.apache.ambari.server.api.services.RootServiceComponentConfiguration;
  import org.apache.ambari.server.api.services.RootServiceComponentConfigurationService;
  import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
  import org.apache.ambari.server.controller.spi.NoSuchResourceException;
  import org.apache.ambari.server.controller.spi.Predicate;
  import org.apache.ambari.server.controller.spi.Request;
  import org.apache.ambari.server.controller.spi.RequestStatus;
  import org.apache.ambari.server.controller.spi.Resource;
  import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
  import org.apache.ambari.server.controller.spi.SystemException;
  import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
  import org.apache.ambari.server.controller.utilities.PredicateHelper;
  import org.apache.ambari.server.controller.utilities.PropertyHelper;
  import org.apache.ambari.server.security.authorization.RoleAuthorization;
  import org.apache.ambari.server.utils.SecretReference;
  import org.apache.commons.collections4.CollectionUtils;
  import org.apache.commons.lang.StringUtils;
  
  import com.google.inject.Inject;
  
  public class RootServiceComponentConfigurationResourceProvider extends AbstractAuthorizedResourceProvider {
  
    static final String RESOURCE_KEY = "Configuration";
  
    public static final String CONFIGURATION_CATEGORY_PROPERTY_ID = PropertyHelper.getPropertyId(RESOURCE_KEY, "category");
    public static final String CONFIGURATION_PROPERTIES_PROPERTY_ID = PropertyHelper.getPropertyId(RESOURCE_KEY, "properties");
    public static final String CONFIGURATION_PROPERTY_TYPES_PROPERTY_ID = PropertyHelper.getPropertyId(RESOURCE_KEY, "property_types");
    public static final String CONFIGURATION_COMPONENT_NAME_PROPERTY_ID = PropertyHelper.getPropertyId(RESOURCE_KEY, "component_name");
    public static final String CONFIGURATION_SERVICE_NAME_PROPERTY_ID = PropertyHelper.getPropertyId(RESOURCE_KEY, "service_name");
  
    private static final Set<String> PROPERTIES;
  
    private static final Map<Resource.Type, String> PK_PROPERTY_MAP;
  
    private static final Set<String> PK_PROPERTY_IDS;
  
    static {
      Set<String> set = new HashSet<>();
      set.add(CONFIGURATION_SERVICE_NAME_PROPERTY_ID);
      set.add(CONFIGURATION_COMPONENT_NAME_PROPERTY_ID);
      set.add(CONFIGURATION_CATEGORY_PROPERTY_ID);
      set.add(CONFIGURATION_PROPERTIES_PROPERTY_ID);
      set.add(CONFIGURATION_PROPERTY_TYPES_PROPERTY_ID);
  
      PROPERTIES = Collections.unmodifiableSet(set);
  
      Map<Resource.Type, String> map = new HashMap<>();
      map.put(Resource.Type.RootService, CONFIGURATION_SERVICE_NAME_PROPERTY_ID);
      map.put(Resource.Type.RootServiceComponent, CONFIGURATION_COMPONENT_NAME_PROPERTY_ID);
      map.put(Resource.Type.RootServiceComponentConfiguration, CONFIGURATION_CATEGORY_PROPERTY_ID);
  
      PK_PROPERTY_MAP = Collections.unmodifiableMap(map);
      PK_PROPERTY_IDS = Collections.unmodifiableSet(new HashSet<>(PK_PROPERTY_MAP.values()));
    }
  
    @Inject
    private RootServiceComponentConfigurationHandlerFactory rootServiceComponentConfigurationHandlerFactory;
  
    public RootServiceComponentConfigurationResourceProvider() {
      super(Resource.Type.RootServiceComponentConfiguration, PROPERTIES, PK_PROPERTY_MAP);
  
      Set<RoleAuthorization> authorizations = EnumSet.of(RoleAuthorization.AMBARI_MANAGE_CONFIGURATION);
      setRequiredCreateAuthorizations(authorizations);
      setRequiredDeleteAuthorizations(authorizations);
      setRequiredUpdateAuthorizations(authorizations);
      setRequiredGetAuthorizations(authorizations);
    }
  
    @Override
    protected Set<String> getPKPropertyIds() {
      return PK_PROPERTY_IDS;
    }
  
    @Override
    public RequestStatus createResourcesAuthorized(Request request)
        throws SystemException, UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException {
  
      OperationStatusMetaData operationStatusMetadata = null;
  
      Map<String, String> requestInfoProperties = request.getRequestInfoProperties();
      if (requestInfoProperties.containsKey(RootServiceComponentConfigurationService.DIRECTIVE_OPERATION)) {
        String operationType = requestInfoProperties.get(RootServiceComponentConfigurationService.DIRECTIVE_OPERATION);
        Map<String, Object> operationParameters = getOperationParameters(requestInfoProperties);
  
        operationStatusMetadata = performOperation(null, null, null, request.getProperties(),
            false, operationType, operationParameters);
      } else {
        createOrAddProperties(null, null, null, request.getProperties(), true);
      }
  
      return getRequestStatus(null, null, operationStatusMetadata);
    }
  
    @Override
    protected Set<Resource> getResourcesAuthorized(Request request, Predicate predicate) throws SystemException,
        UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
  
      return getResources(new Command<Set<Resource>>() {
        @Override
        public Set<Resource> invoke() throws AmbariException {
          Set<Resource> resources = new HashSet<>();
          Set<String> requestedIds = getRequestPropertyIds(request, predicate);
  
          if (CollectionUtils.isEmpty(requestedIds)) {
            requestedIds = PROPERTIES;
          }
  
          if (predicate == null) {
            Set<Resource> _resources;
            try {
              _resources = getConfigurationResources(requestedIds, null);
            } catch (NoSuchResourceException e) {
              throw new AmbariException(e.getMessage(), e);
            }
  
            if (!CollectionUtils.isEmpty(_resources)) {
              resources.addAll(_resources);
            }
          } else {
            for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
              Set<Resource> _resources;
              try {
                _resources = getConfigurationResources(requestedIds, propertyMap);
              } catch (NoSuchResourceException e) {
                throw new AmbariException(e.getMessage(), e);
              }
  
              if (!CollectionUtils.isEmpty(_resources)) {
                resources.addAll(_resources);
              }
            }
          }
  
          return resources;
        }
      });
    }
  
    @Override
    protected RequestStatus deleteResourcesAuthorized(Request request, Predicate predicate) throws SystemException,
        UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
  
      String serviceName = (String) PredicateHelper.getProperties(predicate).get(CONFIGURATION_SERVICE_NAME_PROPERTY_ID);
      String componentName = (String) PredicateHelper.getProperties(predicate).get(CONFIGURATION_COMPONENT_NAME_PROPERTY_ID);
      String categoryName = (String) PredicateHelper.getProperties(predicate).get(CONFIGURATION_CATEGORY_PROPERTY_ID);
  
      RootServiceComponentConfigurationHandler handler = rootServiceComponentConfigurationHandlerFactory.getInstance(serviceName, componentName, categoryName);
      if (handler != null) {
        handler.removeComponentConfiguration(categoryName);
      } else {
        throw new SystemException(String.format("Configurations may not be updated for the %s component of the root service %s", componentName, serviceName));
      }
  
      return getRequestStatus(null);
    }
  
    @Override
    protected RequestStatus updateResourcesAuthorized(Request request, Predicate predicate)
        throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
  
      String serviceName = (String) PredicateHelper.getProperties(predicate).get(CONFIGURATION_SERVICE_NAME_PROPERTY_ID);
      String componentName = (String) PredicateHelper.getProperties(predicate).get(CONFIGURATION_COMPONENT_NAME_PROPERTY_ID);
      String categoryName = (String) PredicateHelper.getProperties(predicate).get(CONFIGURATION_CATEGORY_PROPERTY_ID);
  
      OperationStatusMetaData operationStatusMetadata = null;
  
      Map<String, String> requestInfoProperties = request.getRequestInfoProperties();
      if (requestInfoProperties.containsKey(RootServiceComponentConfigurationService.DIRECTIVE_OPERATION)) {
        String operationType = requestInfoProperties.get(RootServiceComponentConfigurationService.DIRECTIVE_OPERATION);
        Map<String, Object> operationParameters = getOperationParameters(requestInfoProperties);
  
        operationStatusMetadata = performOperation(serviceName, componentName, categoryName, request.getProperties(),
            true, operationType, operationParameters);
      } else {
        createOrAddProperties(serviceName, componentName, categoryName, request.getProperties(), false);
      }
  
      return getRequestStatus(null, null, operationStatusMetadata);
    }
  
    private Resource toResource(String serviceName, String componentName, String categoryName, Map<String, String> properties, Map<String, String> propertyTypes, Set<String> requestedIds) {
      Resource resource = new ResourceImpl(Resource.Type.RootServiceComponentConfiguration);
      setResourceProperty(resource, CONFIGURATION_SERVICE_NAME_PROPERTY_ID, serviceName, requestedIds);
      setResourceProperty(resource, CONFIGURATION_COMPONENT_NAME_PROPERTY_ID, componentName, requestedIds);
      setResourceProperty(resource, CONFIGURATION_CATEGORY_PROPERTY_ID, categoryName, requestedIds);
      setResourceProperty(resource, CONFIGURATION_PROPERTIES_PROPERTY_ID, SecretReference.maskPasswordInPropertyMap(properties), requestedIds);
      setResourceProperty(resource, CONFIGURATION_PROPERTY_TYPES_PROPERTY_ID, propertyTypes, requestedIds);
      return resource;
    }
  
    /**
     * Retrieves groups of properties from the request data and create or updates them as needed.
     * <p>
     * Each group of properties is expected to have a category (<code>AmbariConfiguration/category</code>)
     * value and one or more property (<code>AmbariConfiguration/properties/property.name</code>) values.
     * If a category cannot be determined from the propery set, the default category value (passed in)
     * is used.  If a default category is set, it is assumed that it was parsed from the request predicate
     * (if available).
     *
     * @param defaultServiceName             the default service name to use if needed
     * @param defaultComponentName           the default component name to use if needed
     * @param defaultCategoryName            the default category to use if needed
     * @param requestProperties              a collection of property maps parsed from the request
     * @param removePropertiesIfNotSpecified <code>true</code> to remove existing properties that have not been specifed in the request;
     *                                       <code>false</code> append or update the existing set of properties with values from the request
     * @throws SystemException if an error occurs saving the configuration data
     */
    private void createOrAddProperties(String defaultServiceName, String defaultComponentName, String defaultCategoryName,
                                       Set<Map<String, Object>> requestProperties, boolean removePropertiesIfNotSpecified)
        throws SystemException {
      // set of resource properties (each entry in the set belongs to a different resource)
      if (requestProperties != null) {
        for (Map<String, Object> resourceProperties : requestProperties) {
          RequestDetails requestDetails = parseProperties(defaultServiceName, defaultComponentName, defaultCategoryName, resourceProperties);
  
          RootServiceComponentConfigurationHandler handler = rootServiceComponentConfigurationHandlerFactory.getInstance(requestDetails.serviceName, requestDetails.componentName, requestDetails.categoryName);
          if (handler != null) {
            try {
              handler.updateComponentCategory(requestDetails.categoryName, requestDetails.properties, removePropertiesIfNotSpecified);
            } catch (AmbariException | IllegalArgumentException e) {
              throw new SystemException(e.getMessage(), e.getCause());
            }
          } else {
            throw new SystemException(String.format("Configurations may not be updated for the %s component of the root service, %s", requestDetails.serviceName, requestDetails.componentName));
          }
        }
      }
    }
  
    /**
     * Performs the requested operation on the set of data for the specified configration data.
     *
     * @param defaultServiceName      the default service name to use if needed
     * @param defaultComponentName    the default component name to use if needed
     * @param defaultCategoryName     the default category to use if needed
     * @param requestProperties       a collection of property maps parsed from the request
     * @param mergeExistingProperties <code>true</code> to use the the set of existing properties along with the specified set of properties;
     *                                <code>false</code>  to use set of specified properties only
     * @param operationType           the operation to perform
     * @param operationParameters     parameters to supply the name operation
     * @return an {@link OperationStatusMetaData}
     * @throws SystemException if an error occurs while performing the operation
     */
    private OperationStatusMetaData performOperation(String defaultServiceName, String defaultComponentName, String defaultCategoryName,
                                                     Set<Map<String, Object>> requestProperties, boolean mergeExistingProperties,
                                                     String operationType, Map<String, Object> operationParameters)
        throws SystemException {
  
      OperationStatusMetaData metaData = new OperationStatusMetaData();
  
      // set of resource properties (each entry in the set belongs to a different resource)
      if (requestProperties != null) {
        for (Map<String, Object> resourceProperties : requestProperties) {
          RequestDetails requestDetails = parseProperties(defaultServiceName, defaultComponentName, defaultCategoryName, resourceProperties);
  
          RootServiceComponentConfigurationHandler handler = rootServiceComponentConfigurationHandlerFactory.getInstance(requestDetails.serviceName, requestDetails.componentName, requestDetails.categoryName);
          if (handler != null) {
            RootServiceComponentConfigurationHandler.OperationResult operationResult = handler.performOperation(requestDetails.categoryName, requestDetails.properties, mergeExistingProperties, operationType, operationParameters);
            if (operationResult == null) {
              throw new SystemException(String.format("An unexpected error has occurred while handling an operation for the %s component of the root service, %s", requestDetails.serviceName, requestDetails.componentName));
            }
  
            metaData.addResult(operationResult.getId(), operationResult.isSuccess(), operationResult.getMessage(), operationResult.getResponse());
          } else {
            throw new SystemException(String.format("Operations may not be performed on configurations for the %s component of the root service, %s", requestDetails.serviceName, requestDetails.componentName));
          }
        }
      }
  
      return metaData;
    }
  
    /**
     * Parse the property map from a request into a map of services to components to category names to maps of property names and values.
     *
     * @param defaultServiceName   the default service name to use if one is not found in the map of properties
     * @param defaultComponentName the default component name to use if one is not found in the map of properties
     * @param defaultCategoryName  the default category name to use if one is not found in the map of properties
     * @param resourceProperties   a map of properties from a request item   @return a map of category names to maps of name/value pairs
     * @throws SystemException if an issue with the data is determined
     */
    private RequestDetails parseProperties(String defaultServiceName, String defaultComponentName, String defaultCategoryName, Map<String, Object> resourceProperties) throws SystemException {
      String serviceName = defaultServiceName;
      String componentName = defaultComponentName;
      String categoryName = defaultCategoryName;
      Map<String, String> properties = new HashMap<>();
  
      for (Map.Entry<String, Object> entry : resourceProperties.entrySet()) {
        String propertyName = entry.getKey();
  
        if (CONFIGURATION_CATEGORY_PROPERTY_ID.equals(propertyName)) {
          if (entry.getValue() instanceof String) {
            categoryName = (String) entry.getValue();
          }
        } else if (CONFIGURATION_COMPONENT_NAME_PROPERTY_ID.equals(propertyName)) {
          if (entry.getValue() instanceof String) {
            componentName = (String) entry.getValue();
          }
        } else if (CONFIGURATION_SERVICE_NAME_PROPERTY_ID.equals(propertyName)) {
          if (entry.getValue() instanceof String) {
            serviceName = (String) entry.getValue();
          }
        } else {
          String propertyCategory = PropertyHelper.getPropertyCategory(entry.getKey());
          if ((propertyCategory != null) && propertyCategory.equals(CONFIGURATION_PROPERTIES_PROPERTY_ID)) {
            String name = PropertyHelper.getPropertyName(entry.getKey());
            Object value = entry.getValue();
            properties.put(name, (value == null) ? null : value.toString());
          }
        }
      }
  
      if (StringUtils.isEmpty(serviceName)) {
        throw new SystemException("The service name must be set");
      }
  
      if (StringUtils.isEmpty(componentName)) {
        throw new SystemException("The component name must be set");
      }
  
      if (StringUtils.isEmpty(categoryName)) {
        throw new SystemException("The configuration category must be set");
      }
  
      if (properties.isEmpty()) {
        throw new SystemException("The configuration properties must be set");
      }
  
      return new RequestDetails(serviceName, componentName, categoryName, properties);
    }
  
    /**
     * Creates a map of the operation parameters from the data in the request info map.
     * <p>
     * Operation parmaters are set under the "parameters" category.
     *
     * @param requestInfoProperties a map of request info properties
     * @return a map of operation request parameters
     */
    private Map<String, Object> getOperationParameters(Map<String, String> requestInfoProperties) {
      Map<String, Object> operationParameters = new HashMap<>();
  
      for (Map.Entry<String, String> entry : requestInfoProperties.entrySet()) {
        String propertyCategory = PropertyHelper.getPropertyCategory(entry.getKey());
        if ((propertyCategory != null) && propertyCategory.equals("parameters")) {
          String name = PropertyHelper.getPropertyName(entry.getKey());
          Object value = entry.getValue();
          operationParameters.put(name, (value == null) ? null : value.toString());
        }
      }
  
      return operationParameters;
    }
  
  
    /**
     * Retrieves the requested configuration resources
     *
     * @param requestedIds the requested properties ids
     * @param propertyMap  the request properties
     * @return a set of resources built from the found data
     * @throws NoSuchResourceException if the requested resource was not found
     */
    private Set<Resource> getConfigurationResources(Set<String> requestedIds, Map<String, Object> propertyMap) throws NoSuchResourceException {
      Set<Resource> resources = new HashSet<>();
  
      String serviceName = getStringProperty(propertyMap, CONFIGURATION_SERVICE_NAME_PROPERTY_ID);
      String componentName = getStringProperty(propertyMap, CONFIGURATION_COMPONENT_NAME_PROPERTY_ID);
      String categoryName = getStringProperty(propertyMap, CONFIGURATION_CATEGORY_PROPERTY_ID);
  
      RootServiceComponentConfigurationHandler handler = rootServiceComponentConfigurationHandlerFactory.getInstance(serviceName, componentName, categoryName);
  
      if (handler != null) {
        Map<String, RootServiceComponentConfiguration> configurations = handler.getComponentConfigurations(categoryName);
  
        if (configurations == null) {
          throw new NoSuchResourceException(categoryName);
        } else {
          for (Map.Entry<String, RootServiceComponentConfiguration> entry : configurations.entrySet()) {
            resources.add(toResource(serviceName, componentName, entry.getKey(), entry.getValue().getProperties(), entry.getValue().getPropertyTypes(), requestedIds));
          }
        }
      }
  
      return resources;
    }
  
    private String getStringProperty(Map<String, Object> propertyMap, String propertyId) {
      String value = null;
  
      if (propertyMap != null) {
        Object o = propertyMap.get(propertyId);
        if (o instanceof String) {
          value = (String) o;
        }
      }
  
      return value;
    }
  
    /**
     * RequestDetails is a container for details parsed from the request.
     */
    private class RequestDetails {
      final String serviceName;
      final String componentName;
      final String categoryName;
      final Map<String, String> properties;
  
      private RequestDetails(String serviceName, String componentName, String categoryName, Map<String, String> properties) {
        this.serviceName = serviceName;
        this.componentName = componentName;
        this.categoryName = categoryName;
        this.properties = properties;
      }
    }
  }
  
  