/**
* 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");
}
}
}