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.api.query.render;
  
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.Set;
  
  import org.apache.ambari.server.api.query.QueryInfo;
  import org.apache.ambari.server.api.services.Request;
  import org.apache.ambari.server.api.services.Result;
  import org.apache.ambari.server.api.services.ResultPostProcessor;
  import org.apache.ambari.server.api.services.ResultPostProcessorImpl;
  import org.apache.ambari.server.api.util.TreeNode;
  import org.apache.ambari.server.api.util.TreeNodeImpl;
  import org.apache.ambari.server.controller.spi.Resource;
  import org.apache.ambari.server.controller.utilities.PropertyHelper;
  
  /**
   * Minimal Renderer.
   *
   * All href properties are stripped from the result.
   *
   * For the root resource, this renderer behaves identically to the
   * default renderer wrt resource properties and sub-resources. If
   * no root properties or sub-resources are specified all top level
   * properties and all sub-resources are included in the result. If
   * any root properties or any sub-resources are requested, then only
   * those will be included in the result.
   *
   * For sub-resource, only primary keys and any requested properties
   * are included in the result.
   *
   * This renderer can be specified for any resource using
   * 'format=minimal' or the older syntax 'minimal_response=true'.
   */
  public class MinimalRenderer extends BaseRenderer implements Renderer {
  
    /**
     * Type of root resource.
     */
    private Resource.Type m_rootType;
  
    /**
     * Whether the request is for a collection.
     */
    private boolean m_isCollection;
  
    /**
     * Map of requested properties.
     */
    private Map<Resource.Type, Set<String>> m_originalProperties =
      new HashMap<>();
  
    // ----- Renderer ----------------------------------------------------------
  
    @Override
    public TreeNode<Set<String>> finalizeProperties(
        TreeNode<QueryInfo> queryTree, boolean isCollection) {
  
      QueryInfo queryInfo = queryTree.getObject();
      TreeNode<Set<String>> resultTree = new TreeNodeImpl<>(
        null, queryInfo.getProperties(), queryTree.getName());
  
      copyPropertiesToResult(queryTree, resultTree);
  
      m_rootType     = queryTree.getObject().getResource().getType();
      m_isCollection = isCollection;
  
      boolean addKeysToEmptyResource = true;
      if (! isCollection && isRequestWithNoProperties(queryTree)) {
        addSubResources(queryTree, resultTree);
        addKeysToEmptyResource = false;
      }
      processRequestedProperties(queryTree);
      ensureRequiredProperties(resultTree, addKeysToEmptyResource);
  
      return resultTree;
    }
  
    @Override
    public Result finalizeResult(Result queryResult) {
      // can't just return result, need to strip added properties.
      processResultNode(queryResult.getResultTree());
      return queryResult;
    }
  
    @Override
    public ResultPostProcessor getResultPostProcessor(Request request) {
      return new MinimalPostProcessor(request);
    }
  
    // ----- private instance methods ------------------------------------------
  
    /**
     * Recursively save all requested properties to check the result
     * properties against.
     *
     * @param queryTree  query tree to process
     */
    private void processRequestedProperties(TreeNode<QueryInfo> queryTree) {
      QueryInfo queryInfo = queryTree.getObject();
      if (queryInfo != null) {
        Resource.Type type = queryInfo.getResource().getType();
        Set<String> properties = m_originalProperties.get(type);
        if (properties == null) {
          properties = new HashSet<>();
          m_originalProperties.put(type, properties);
        }
        properties.addAll(queryInfo.getProperties());
        for (TreeNode<QueryInfo> child : queryTree.getChildren()) {
          processRequestedProperties(child);
        }
      }
    }
  
    /**
     * Recursively strip all unwanted properties from the result nodes.
     * During normal processing, foreign keys are always added to the request which need
     * to be stripped unless they were requested.
     *
     * @param node  node to process for extra properties
     */
    private void processResultNode(TreeNode<Resource> node) {
      Resource resource = node.getObject();
      if (resource != null && ( resource.getType() != m_rootType || m_isCollection)) {
        Resource.Type type = resource.getType();
        Set<String> requestedProperties = m_originalProperties.get(type);
        Map<String, Map<String, Object>> properties = resource.getPropertiesMap();
  
        Iterator<Map.Entry<String, Map<String, Object>>> iter;
        for(iter = properties.entrySet().iterator(); iter.hasNext(); ) {
          Map.Entry<String, Map<String, Object>> entry = iter.next();
          String categoryName = entry.getKey();
          Iterator<String> valueIter;
  
          for(valueIter = entry.getValue().keySet().iterator(); valueIter.hasNext(); ) {
            String propName = valueIter.next();
            // if property was not requested and it is not a pk, remove
            String absPropertyName = PropertyHelper.getPropertyId(categoryName, propName);
            if ((requestedProperties == null ||
                (! requestedProperties.contains(absPropertyName) &&
                ! isSubCategory(requestedProperties, categoryName))) &&
                ! getPrimaryKeys(type).contains(absPropertyName)) {
              valueIter.remove();
            }
          }
          if (entry.getValue().isEmpty()) {
            iter.remove();
          }
        }
      }
      for (TreeNode<Resource> child : node.getChildren()) {
        processResultNode(child);
      }
    }
  
    /**
     * Checks if properties contains a category for the subCategoryName
     * Example: metrics/yarn/Queue/root/ActiveApplications and metrics/yarn/Queue
     *
     * @param properties categories
     * @param subCategoryName subcategory
     * @return
     */
    private boolean isSubCategory(Set<String> properties, String subCategoryName) {
      for (String property : properties) {
        if (subCategoryName.startsWith(property)) {
          return true;
        }
      }
      return false;
    }
  
    /**
     * Obtain the primary keys for the specified type.
     * This method is necessary because some resource types, specifically
     * the configuration type, don't have a proper pk even though one is
     * registered.  Instead, multiple properties are used as a 'composite'
     * key even though this is not supported by the framework.
     *
     * @param type  resource type
     *
     * @return set of pk's for a type
     */
    private Set<String> getPrimaryKeys(Resource.Type type) {
      Set<String> primaryKeys = new HashSet<>();
  
      if (type == Resource.Type.Configuration) {
        primaryKeys.add("type");
        primaryKeys.add("tag");
      } else {
        Map<Resource.Type, String> keys = PropertyHelper.getKeyPropertyIds(type);
        if (keys != null) {
          String pk = PropertyHelper.getKeyPropertyIds(type).get(type);
          if (pk != null) {
            primaryKeys = Collections.singleton(pk);
          }
        }
      }
      return primaryKeys;
    }
  
    // ----- inner classes -----------------------------------------------------
  
    /**
     * Post processor which doesn't generate href properties in the result tree.
     */
    private static class MinimalPostProcessor extends ResultPostProcessorImpl {
      private MinimalPostProcessor(Request request) {
        super(request);
      }
  
      @Override
      protected void finalizeNode(TreeNode<Resource> node) {
        node.removeProperty("href");
      }
    }
  }
  