Browse Source

AMBARI-3849 Need ability to filter out href field in requests

tbeerbower 11 năm trước cách đây
mục cha
commit
dd535bcf81
16 tập tin đã thay đổi với 325 bổ sung159 xóa
  1. 4 3
      ambari-server/src/main/java/org/apache/ambari/server/api/handlers/ReadHandler.java
  2. 15 4
      ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java
  3. 8 3
      ambari-server/src/main/java/org/apache/ambari/server/api/query/Query.java
  4. 83 67
      ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryImpl.java
  5. 11 6
      ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseRequest.java
  6. 6 3
      ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseService.java
  7. 7 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/Request.java
  8. 8 5
      ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/JsonSerializer.java
  9. 3 2
      ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/ResultSerializer.java
  10. 23 39
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterControllerImpl.java
  11. 21 7
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ResourceImpl.java
  12. 26 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/PropertyHelper.java
  13. 25 11
      ambari-server/src/test/java/org/apache/ambari/server/api/handlers/ReadHandlerTest.java
  14. 3 3
      ambari-server/src/test/java/org/apache/ambari/server/api/query/QueryImplTest.java
  15. 27 3
      ambari-server/src/test/java/org/apache/ambari/server/api/services/BaseServiceTest.java
  16. 55 3
      ambari-server/src/test/java/org/apache/ambari/server/api/services/serializers/JsonSerializerTest.java

+ 4 - 3
ambari-server/src/main/java/org/apache/ambari/server/api/handlers/ReadHandler.java

@@ -45,9 +45,11 @@ public class ReadHandler implements RequestHandler {
   public Result handleRequest(Request request) {
     Query query = request.getResource().getQuery();
 
+    query.setPageRequest(request.getPageRequest());
+    query.setMinimal(request.isMinimal());
+
     try {
       addFieldsToQuery(request, query);
-      query.setPageRequest(request.getPageRequest());
     } catch (IllegalArgumentException e) {
       return new ResultImpl(new ResultStatus(ResultStatus.STATUS.BAD_REQUEST, e.getMessage()));
     }
@@ -100,8 +102,7 @@ public class ReadHandler implements RequestHandler {
     for (Map.Entry<String, TemporalInfo> entry : request.getFields().entrySet()) {
       // Iterate over map and add props/temporalInfo
       String propertyId = entry.getKey();
-      query.addProperty(PropertyHelper.getPropertyCategory(propertyId),
-          PropertyHelper.getPropertyName(propertyId), entry.getValue());
+      query.addProperty(propertyId, entry.getValue());
     }
   }
 }

+ 15 - 4
ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java

@@ -33,6 +33,16 @@ import java.util.regex.Pattern;
  * Second, each string token is converted into a Token with type and value information.
  */
 public class QueryLexer {
+
+  /**
+   * Query string constants.
+   */
+  public static final String QUERY_FIELDS    = "fields";
+  public static final String QUERY_PAGE_SIZE = "page_size";
+  public static final String QUERY_TO        = "to";
+  public static final String QUERY_FROM      = "from";
+  public static final String QUERY_MINIMAL   = "minimal_response";
+
   /**
    * All valid deliminators.
    */
@@ -167,10 +177,11 @@ public class QueryLexer {
    */
   static {
     // ignore values
-    SET_IGNORE.add("fields");
-    SET_IGNORE.add("page_size");
-    SET_IGNORE.add("to");
-    SET_IGNORE.add("from");
+    SET_IGNORE.add(QUERY_FIELDS);
+    SET_IGNORE.add(QUERY_PAGE_SIZE);
+    SET_IGNORE.add(QUERY_TO);
+    SET_IGNORE.add(QUERY_FROM);
+    SET_IGNORE.add(QUERY_MINIMAL);
     SET_IGNORE.add("_");
   }
 

+ 8 - 3
ambari-server/src/main/java/org/apache/ambari/server/api/query/Query.java

@@ -33,11 +33,10 @@ public interface Query {
    * Add a property to the query.
    * This is the select portion of the query.
    *
-   * @param group         the group name that contains the property
-   * @param property      the property name
+   * @param propertyId    the property id
    * @param temporalInfo  temporal information for the property
    */
-  public void addProperty(String group, String property, TemporalInfo temporalInfo);
+  public void addProperty(String propertyId, TemporalInfo temporalInfo);
 
   /**
    * Add a local (not sub-resource) property to the query.
@@ -93,4 +92,10 @@ public interface Query {
    */
   public void setPageRequest(PageRequest pageRequest);
 
+  /**
+   * Set a flag to indicate whether or not the response should be minimal.
+   *
+   * @param minimal  minimal flag; true indicates that the response should be minimal
+   */
+  public void setMinimal(boolean minimal);
 }

+ 83 - 67
ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryImpl.java

@@ -23,6 +23,7 @@ import org.apache.ambari.server.api.resources.ResourceInstance;
 import org.apache.ambari.server.api.resources.ResourceInstanceFactoryImpl;
 import org.apache.ambari.server.api.resources.SubResourceDefinition;
 import org.apache.ambari.server.api.services.ResultImpl;
+import org.apache.ambari.server.controller.internal.ResourceImpl;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.apache.ambari.server.controller.predicate.AndPredicate;
 import org.apache.ambari.server.controller.predicate.EqualsPredicate;
@@ -96,6 +97,11 @@ public class QueryImpl implements Query, ResourceInstance {
    */
   private PageRequest pageRequest;
 
+  /**
+   * Indicates whether or not the response should be minimal.
+   */
+  private boolean minimal;
+
   /**
    * The logger.
    */
@@ -124,21 +130,17 @@ public class QueryImpl implements Query, ResourceInstance {
   // ----- Query -------------------------------------------------------------
 
   @Override
-  public void addProperty(String category, String name, TemporalInfo temporalInfo) {
-    if (category == null && name.equals("*")) {
+  public void addProperty(String propertyId, TemporalInfo temporalInfo) {
+    if (propertyId.equals("*")) {
       // wildcard
       addAllProperties(temporalInfo);
     } else{
-      if (addPropertyToSubResource(category, name, temporalInfo)){
-        // add pk/fk properties of the resource to this query
-        Resource.Type resourceType = getResourceDefinition().getType();
-        Schema        schema       = clusterController.getSchema(resourceType);
-
-        for (Resource.Type type : getKeyValueMap().keySet()) {
-          addLocalProperty(schema.getKeyPropertyId(type));
-        }
+      if (addPropertyToSubResource(propertyId, temporalInfo)){
+        addKeyProperties(getResourceDefinition().getType(), !minimal);
       } else {
-        String propertyId = PropertyHelper.getPropertyId(category, name.equals("*") ? null : name);
+        if (propertyId.endsWith("/*")) {
+          propertyId = propertyId.substring(0, propertyId.length() - 2);
+        }
         addLocalProperty(propertyId);
         if (temporalInfo != null) {
           temporalInfoMap.put(propertyId, temporalInfo);
@@ -183,8 +185,13 @@ public class QueryImpl implements Query, ResourceInstance {
     this.pageRequest = pageRequest;
   }
 
+  @Override
+  public void setMinimal(boolean minimal) {
+    this.minimal = minimal;
+  }
+
 
-  // ----- ResourceInstance --------------------------------------------------
+// ----- ResourceInstance --------------------------------------------------
 
   @Override
   public void setKeyValueMap(Map<Resource.Type, String> keyValueMap) {
@@ -263,19 +270,25 @@ public class QueryImpl implements Query, ResourceInstance {
           getResourceDefinition().getSubResourceDefinitions();
 
       ClusterController controller = clusterController;
+
       for (SubResourceDefinition subResDef : setSubResourceDefs) {
         Resource.Type type = subResDef.getType();
         Map<Resource.Type, String> valueMap = getKeyValueMap();
         QueryImpl resource =  new QueryImpl(valueMap,
             ResourceInstanceFactoryImpl.getResourceDefinition(type, valueMap),
             controller);
+        resource.setMinimal(minimal);
+
+        Schema schema = controller.getSchema(type);
 
         // ensure pk is returned
-        resource.addLocalProperty(controller.getSchema(
-            type).getKeyPropertyId(type));
-        // add additionally required fk properties
-        for (Resource.Type fkType : subResDef.getAdditionalForeignKeys()) {
-          resource.addLocalProperty(controller.getSchema(type).getKeyPropertyId(fkType));
+        resource.addLocalProperty(schema.getKeyPropertyId(type));
+
+        if (!minimal) {
+          // add additionally required fk properties
+          for (Resource.Type fkType : subResDef.getAdditionalForeignKeys()) {
+            resource.addLocalProperty(schema.getKeyPropertyId(fkType));
+          }
         }
 
         String subResourceName = subResDef.isCollection() ?
@@ -300,18 +313,19 @@ public class QueryImpl implements Query, ResourceInstance {
     Set<Resource> providerResourceSet = new HashSet<Resource>();
 
     Resource.Type resourceType = getResourceDefinition().getType();
-    Request       request      = createRequest();
+    Request       request      = createRequest(!minimal);
+    Request       qRequest     = createRequest(true);
     Predicate     predicate    = getPredicate();
 
     Set<Resource> resourceSet = new LinkedHashSet<Resource>();
 
-    for (Resource queryResource : doQuery(resourceType, request, predicate)) {
+    for (Resource queryResource : doQuery(resourceType, qRequest, predicate)) {
       providerResourceSet.add(queryResource);
       resourceSet.add(queryResource);
     }
     queryResults.put(null, new QueryResult(request, predicate, getKeyValueMap(), resourceSet));
 
-    clusterController.populateResources(resourceType, providerResourceSet, request, predicate);
+    clusterController.populateResources(resourceType, providerResourceSet, qRequest, predicate);
     queryForSubResources();
   }
 
@@ -330,7 +344,8 @@ public class QueryImpl implements Query, ResourceInstance {
 
       QueryImpl     subResource  = entry.getValue();
       Resource.Type resourceType = subResource.getResourceDefinition().getType();
-      Request       request      = subResource.createRequest();
+      Request       request      = subResource.createRequest(!minimal);
+      Request       qRequest     = subResource.createRequest(true);
 
       Set<Resource> providerResourceSet = new HashSet<Resource>();
 
@@ -343,14 +358,14 @@ public class QueryImpl implements Query, ResourceInstance {
 
           Set<Resource> resourceSet = new LinkedHashSet<Resource>();
 
-          for (Resource queryResource : subResource.doQuery(resourceType, request, predicate)) {
+          for (Resource queryResource : subResource.doQuery(resourceType, qRequest, predicate)) {
             providerResourceSet.add(queryResource);
             resourceSet.add(queryResource);
           }
           subResource.queryResults.put(resource, new QueryResult(request, predicate, map, resourceSet));
         }
       }
-      clusterController.populateResources(resourceType, providerResourceSet, request, null);
+      clusterController.populateResources(resourceType, providerResourceSet, qRequest, null);
       subResource.queryForSubResources();
     }
   }
@@ -408,10 +423,16 @@ public class QueryImpl implements Query, ResourceInstance {
         iterResource = pageResponse.getIterable();
       }
 
+      Set<String> propertyIds = request.getPropertyIds();
+
       int count = 1;
       for (Resource resource : iterResource) {
+
         // add a child node for the resource and provide a unique name.  The name is never used.
-        TreeNode<Resource> node = tree.addChild(resource, resource.getType() + ":" + count++);
+        TreeNode<Resource> node = tree.addChild(
+            minimal ? new ResourceImpl(resource, propertyIds) : resource,
+            resource.getType() + ":" + count++);
+
         for (Map.Entry<String, QueryImpl> entry : querySubResourceSet.entrySet()) {
           String    subResCategory = entry.getKey();
           QueryImpl subResource    = entry.getValue();
@@ -426,21 +447,21 @@ public class QueryImpl implements Query, ResourceInstance {
     return result;
   }
 
-  private void addCollectionProperties(Resource.Type resourceType) {
+  private void addKeyProperties(Resource.Type resourceType, boolean includeFKs) {
     Schema schema = clusterController.getSchema(resourceType);
 
-    // add pk
-    String property = schema.getKeyPropertyId(resourceType);
-    addProperty(PropertyHelper.getPropertyCategory(property),
-        PropertyHelper.getPropertyName(property), null);
-
-    for (Resource.Type type : getKeyValueMap().keySet()) {
-      // add fk's
-      String keyPropertyId = schema.getKeyPropertyId(type);
-      if (keyPropertyId != null) {
-        addProperty(PropertyHelper.getPropertyCategory(keyPropertyId),
-            PropertyHelper.getPropertyName(keyPropertyId), null);
+    if (includeFKs) {
+      for (Resource.Type type : Resource.Type.values()) {
+        // add fk's
+        String propertyId = schema.getKeyPropertyId(type);
+        if (propertyId != null) {
+          addProperty(propertyId, null);
+        }
       }
+    } else {
+      // add pk only
+      String propertyId = schema.getKeyPropertyId(resourceType);
+      addProperty(propertyId, null);
     }
   }
 
@@ -458,33 +479,23 @@ public class QueryImpl implements Query, ResourceInstance {
     }
   }
 
-  private boolean addPropertyToSubResource(String path, String property, TemporalInfo temporalInfo) {
-    // cases:
-    // - path is null, property is path (all sub-resource props will have a path)
-    // - path is single token and prop in non null
-    //      (path only will presented as above case with property only)
-    // - path is multi level and prop is non null
-
-    boolean resourceAdded = false;
-    if (path == null) {
-      path = property;
-      property = null;
-    }
+  private boolean addPropertyToSubResource(String propertyId, TemporalInfo temporalInfo) {
+    int    index    = propertyId.indexOf("/");
+    String category = index == -1 ? propertyId : propertyId.substring(0, index);
 
-    int i = path.indexOf("/");
-    String p = i == -1 ? path : path.substring(0, i);
+    Map<String, QueryImpl> subResources = ensureSubResources();
 
-    QueryImpl subResource = ensureSubResources().get(p);
+    QueryImpl subResource = subResources.get(category);
     if (subResource != null) {
-      querySubResourceSet.put(p, subResource);
+      querySubResourceSet.put(category, subResource);
 
-      if (property != null || !path.equals(p)) {
-        //only add if a sub property is set or if a sub category is specified
-        subResource.getQuery().addProperty(i == -1 ? null : path.substring(i + 1), property, temporalInfo);
+      //only add if a sub property is set or if a sub category is specified
+      if (index != -1) {
+        subResource.addProperty(propertyId.substring(index + 1), temporalInfo);
       }
-      resourceAdded = true;
+      return true;
     }
-    return resourceAdded;
+    return false;
   }
 
   private Predicate createInternalPredicate(Map<Resource.Type, String> mapResourceIds) {
@@ -528,7 +539,12 @@ public class QueryImpl implements Query, ResourceInstance {
     return predicate;
   }
 
-  private Request createRequest() {
+  private Request createRequest(boolean includeFKs) {
+    
+    if (allProperties) {
+      return PropertyHelper.getReadRequest(Collections.<String>emptySet());
+    }
+    
     Set<String> setProperties = new HashSet<String>();
 
     Map<String, TemporalInfo> mapTemporalInfo    = new HashMap<String, TemporalInfo>();
@@ -536,23 +552,23 @@ public class QueryImpl implements Query, ResourceInstance {
     Resource.Type             resourceType       = getResourceDefinition().getType();
 
     if (getKeyValueMap().get(resourceType) == null) {
-      addCollectionProperties(resourceType);
+      addKeyProperties(resourceType, includeFKs);
     }
 
-    for (String group : queryPropertySet) {
-      TemporalInfo temporalInfo = temporalInfoMap.get(group);
+    setProperties.addAll(queryPropertySet);
+    
+    for (String propertyId : setProperties) {
+      TemporalInfo temporalInfo = temporalInfoMap.get(propertyId);
       if (temporalInfo != null) {
-        mapTemporalInfo.put(group, temporalInfo);
+        mapTemporalInfo.put(propertyId, temporalInfo);
       } else if (globalTemporalInfo != null) {
-        mapTemporalInfo.put(group, globalTemporalInfo);
+        mapTemporalInfo.put(propertyId, globalTemporalInfo);
       }
-      setProperties.add(group);
     }
-
-    return PropertyHelper.getReadRequest(allProperties ?
-        Collections.<String>emptySet() : setProperties, mapTemporalInfo);
+    return PropertyHelper.getReadRequest(setProperties, mapTemporalInfo);
   }
 
+
   // Get a key value map based on the given resource and an existing key value map
   private Map<Resource.Type, String> getKeyValueMap(Resource resource,
                                                     Map<Resource.Type, String> keyValueMap) {

+ 11 - 6
ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseRequest.java

@@ -21,6 +21,7 @@ package org.apache.ambari.server.api.services;
 import org.apache.ambari.server.api.handlers.RequestHandler;
 import org.apache.ambari.server.api.predicate.InvalidQueryException;
 import org.apache.ambari.server.api.predicate.PredicateCompiler;
+import org.apache.ambari.server.api.predicate.QueryLexer;
 import org.apache.ambari.server.api.resources.*;
 import org.apache.ambari.server.controller.internal.PageRequestImpl;
 import org.apache.ambari.server.controller.internal.TemporalInfoImpl;
@@ -144,7 +145,7 @@ public abstract class BaseRequest implements Request {
   @Override
   public Map<String, TemporalInfo> getFields() {
     Map<String, TemporalInfo> mapProperties;
-    String partialResponseFields = m_uriInfo.getQueryParameters().getFirst("fields");
+    String partialResponseFields = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_FIELDS);
     if (partialResponseFields == null) {
       mapProperties = Collections.emptyMap();
     } else {
@@ -192,9 +193,9 @@ public abstract class BaseRequest implements Request {
   @Override
   public PageRequest getPageRequest() {
 
-    String pageSize = m_uriInfo.getQueryParameters().getFirst("page_size");
-    String from     = m_uriInfo.getQueryParameters().getFirst("from");
-    String to       = m_uriInfo.getQueryParameters().getFirst("to");
+    String pageSize = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_PAGE_SIZE);
+    String from     = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_FROM);
+    String to       = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_TO);
 
     if (pageSize == null && from == null && to == null) {
       return null;
@@ -227,12 +228,17 @@ public abstract class BaseRequest implements Request {
         pageSize == null ? DEFAULT_PAGE_SIZE : Integer.valueOf(pageSize), offset, null, null);
   }
 
+  @Override
+  public boolean isMinimal() {
+    String minimal = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_MINIMAL);
+    return minimal != null && minimal.equalsIgnoreCase("true");
+  }
+
   @Override
   public RequestBody getBody() {
     return m_body;
   }
 
-
   /**
    * Obtain the result post processor for the request.
    *
@@ -253,7 +259,6 @@ public abstract class BaseRequest implements Request {
     return new PredicateCompiler();
   }
 
-
   /**
    * Parse the query string and compile it into a predicate.
    * The query string may have already been extracted from the http body.

+ 6 - 3
ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseService.java

@@ -66,7 +66,9 @@ public abstract class BaseService {
   protected Response handleRequest(HttpHeaders headers, String body, UriInfo uriInfo,
                                    Request.Type requestType, ResourceInstance resource) {
 
-    Result result = new ResultImpl(new ResultStatus(ResultStatus.STATUS.OK));
+    Result  result  = new ResultImpl(new ResultStatus(ResultStatus.STATUS.OK));
+    boolean minimal = false;
+
     try {
       Set<RequestBody> requestBodySet = getBodyParser().parse(body);
 
@@ -77,14 +79,15 @@ public abstract class BaseService {
         Request request = getRequestFactory().createRequest(
             headers, requestBody, uriInfo, requestType, resource);
 
-        result = request.process();
+        minimal = request.isMinimal();
+        result  = request.process();
       }
     } catch (BodyParseException e) {
       result =  new ResultImpl(new ResultStatus(ResultStatus.STATUS.BAD_REQUEST, e.getMessage()));
     }
 
     return Response.status(result.getStatus().getStatusCode()).entity(
-        getResultSerializer().serialize(result)).build();
+        getResultSerializer().serialize(result, minimal)).build();
   }
 
   /**

+ 7 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/Request.java

@@ -114,4 +114,11 @@ public interface Request {
    * @return the page request
    */
   public PageRequest getPageRequest();
+
+  /**
+   * Is the minimal response parameter specified as true.
+   *
+   * @return true if the minimal response parameter is specified as true
+   */
+  public boolean isMinimal();
 }

+ 8 - 5
ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/JsonSerializer.java

@@ -53,7 +53,7 @@ public class JsonSerializer implements ResultSerializer {
 
 
   @Override
-  public Object serialize(Result result) {
+  public Object serialize(Result result, boolean minimal) {
     try {
       ByteArrayOutputStream bytesOut = init();
 
@@ -61,7 +61,7 @@ public class JsonSerializer implements ResultSerializer {
         return serializeError(result.getStatus());
       }
 
-      processNode(result.getResultTree());
+      processNode(result.getResultTree(), minimal);
 
       m_generator.close();
       return bytesOut.toString("UTF-8");
@@ -100,10 +100,13 @@ public class JsonSerializer implements ResultSerializer {
     return bytesOut;
   }
 
-  private void processNode(TreeNode<Resource> node) throws IOException {
+  private void processNode(TreeNode<Resource> node, boolean minimal) throws IOException {
     if (isObject(node)) {
       m_generator.writeStartObject();
-      writeHref(node);
+
+      if (!minimal) {
+        writeHref(node);
+      }
 
       Resource r = node.getObject();
       if (r != null) {
@@ -115,7 +118,7 @@ public class JsonSerializer implements ResultSerializer {
     }
 
     for (TreeNode<Resource> child : node.getChildren()) {
-      processNode(child);
+      processNode(child, minimal);
     }
 
     if (isArray(node)) {

+ 3 - 2
ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/ResultSerializer.java

@@ -29,11 +29,12 @@ public interface ResultSerializer {
   /**
    * Serialize the given result to a format expected by client.
    *
-   *
    * @param result  internal result
+   * @param minimal flag to indicate minimal results
+   *
    * @return the serialized result
    */
-  Object serialize(Result result);
+  Object serialize(Result result, boolean minimal);
 
   /**
    * Serialize an error result to the format expected by the client.

+ 23 - 39
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterControllerImpl.java

@@ -164,16 +164,16 @@ public class ClusterControllerImpl implements ClusterController {
         switch (pageRequest.getStartingPoint()) {
           case Beginning:
             return getPageFromOffset(pageRequest.getPageSize(), 0,
-              sortedResources, request, predicate, evaluator);
+              sortedResources, predicate, evaluator);
           case End:
             return getPageToOffset(pageRequest.getPageSize(), -1,
-                sortedResources, request, predicate, evaluator);
+                sortedResources, predicate, evaluator);
           case OffsetStart:
             return getPageFromOffset(pageRequest.getPageSize(),
-              pageRequest.getOffset(), sortedResources, request, predicate, evaluator);
+              pageRequest.getOffset(), sortedResources, predicate, evaluator);
           case OffsetEnd:
             return getPageToOffset(pageRequest.getPageSize(),
-              pageRequest.getOffset(), sortedResources, request, predicate, evaluator);
+              pageRequest.getOffset(), sortedResources, predicate, evaluator);
           // TODO : need to support the following cases for pagination
 //          case PredicateStart:
 //          case PredicateEnd:
@@ -184,8 +184,9 @@ public class ClusterControllerImpl implements ClusterController {
       resources = providerResources;
     }
 
-    return new PageResponseImpl(new ResourceIterable(resources, request, predicate, evaluator),
-      0, null, null);
+    return new PageResponseImpl(
+        new ResourceIterable(resources, predicate, evaluator),
+        0, null, null);
   }
 
   @Override
@@ -279,23 +280,23 @@ public class ClusterControllerImpl implements ClusterController {
    * @throws NoSuchResourceException no matching resource(s) found
    * @throws NoSuchParentResourceException a specified parent resource doesn't exist
    */
-  protected Iterable<Resource> getResourceIterable(Resource.Type type, Request request, Predicate predicate)
+  protected Iterable<Resource> getResourceIterable(Resource.Type type, Request request,
+                                                   Predicate predicate)
       throws UnsupportedPropertyException,
       SystemException,
       NoSuchParentResourceException,
       NoSuchResourceException {
-    PageResponse resources = getResources(type, request, predicate, null);
-    return resources.getIterable();
+    return getResources(type, request, predicate, null).getIterable();
   }
 
   /**
    * Get a page of resources filtered by the given request, predicate objects and
    * page request.
    *
-   * @param type               type of resources
-   * @param request            the request
-   * @param predicate          the predicate object which filters which resources are returned
-   * @param pageRequest        the page request for a paginated response
+   * @param type         type of resources
+   * @param request      the request
+   * @param predicate    the predicate object which filters which resources are returned
+   * @param pageRequest  the page request for a paginated response
    *
    * @return a page response representing the requested page of resources
    *
@@ -305,7 +306,8 @@ public class ClusterControllerImpl implements ClusterController {
    * @throws NoSuchResourceException no matching resource(s) found
    * @throws NoSuchParentResourceException a specified parent resource doesn't exist
    */
-  protected  PageResponse getResources(Resource.Type type, Request request, Predicate predicate, PageRequest pageRequest)
+  protected  PageResponse getResources(Resource.Type type, Request request, Predicate predicate,
+                                       PageRequest pageRequest)
       throws UnsupportedPropertyException,
       SystemException,
       NoSuchResourceException,
@@ -480,14 +482,13 @@ public class ClusterControllerImpl implements ClusterController {
    * @param pageSize   the page size
    * @param offset     the offset
    * @param resources  the set of resources
-   * @param request    the request
    * @param predicate  the predicate
    *
    * @return a page response containing a page of resources
    */
   private PageResponse getPageFromOffset(int pageSize, int offset,
                                          NavigableSet<Resource> resources,
-                                         Request request, Predicate predicate,
+                                         Predicate predicate,
                                          ResourcePredicateEvaluator evaluator) {
 
     int                currentOffset = 0;
@@ -507,7 +508,7 @@ public class ClusterControllerImpl implements ClusterController {
     }
 
     return new PageResponseImpl(new ResourceIterable(pageResources,
-        request, predicate, evaluator),
+        predicate, evaluator),
         currentOffset,
         previous,
         iterator.hasNext() ? iterator.next() : null);
@@ -519,14 +520,13 @@ public class ClusterControllerImpl implements ClusterController {
    * @param pageSize   the page size
    * @param offset     the offset; -1 indicates the end of the resource set
    * @param resources  the set of resources
-   * @param request    the request
    * @param predicate  the predicate
    *
    * @return a page response containing a page of resources
    */
   private PageResponse getPageToOffset(int pageSize, int offset,
                                        NavigableSet<Resource> resources,
-                                       Request request, Predicate predicate,
+                                       Predicate predicate,
                                        ResourcePredicateEvaluator evaluator) {
 
     int                currentOffset = resources.size() - 1;
@@ -549,7 +549,7 @@ public class ClusterControllerImpl implements ClusterController {
     }
 
     return new PageResponseImpl(new ResourceIterable(new
-        LinkedHashSet<Resource>(pageResources), request, predicate, evaluator),
+        LinkedHashSet<Resource>(pageResources), predicate, evaluator),
         currentOffset + 1,
         iterator.hasNext() ? iterator.next() : null,
         next);
@@ -574,11 +574,6 @@ public class ClusterControllerImpl implements ClusterController {
      */
     private final Set<Resource> resources;
 
-    /**
-     * The associated request.
-     */
-    private final Request request;
-
     /**
      * The predicate used to filter the set.
      */
@@ -596,14 +591,12 @@ public class ClusterControllerImpl implements ClusterController {
      * Create a ResourceIterable.
      *
      * @param resources  the set of resources to iterate over
-     * @param request    the request
      * @param predicate  the predicate used to filter the set of resources
      * @param evaluator  the evaluator used to evaluate with the given predicate
      */
-    private ResourceIterable(Set<Resource> resources, Request request, Predicate predicate,
+    private ResourceIterable(Set<Resource> resources, Predicate predicate,
                              ResourcePredicateEvaluator evaluator) {
       this.resources = resources;
-      this.request   = request;
       this.predicate = predicate;
       this.evaluator = evaluator;
     }
@@ -612,7 +605,7 @@ public class ClusterControllerImpl implements ClusterController {
 
     @Override
     public Iterator<Resource> iterator() {
-      return new ResourceIterator(resources, request, predicate, evaluator);
+      return new ResourceIterator(resources, predicate, evaluator);
     }
   }
 
@@ -626,11 +619,6 @@ public class ClusterControllerImpl implements ClusterController {
      */
     private final Iterator<Resource> iterator;
 
-    /**
-     * The associated request.
-     */
-    private final Request request;
-
     /**
      * The predicate used to filter the resource being iterated over.
      */
@@ -652,14 +640,12 @@ public class ClusterControllerImpl implements ClusterController {
      * Create a new ResourceIterator.
      *
      * @param resources  the set of resources to iterate over
-     * @param request    the request
      * @param predicate  the predicate used to filter the set of resources
      * @param evaluator  the evaluator used to evaluate with the given predicate
      */
-    private ResourceIterator(Set<Resource> resources, Request request, Predicate predicate,
+    private ResourceIterator(Set<Resource> resources, Predicate predicate,
                              ResourcePredicateEvaluator evaluator) {
       this.iterator     = resources.iterator();
-      this.request      = request;
       this.predicate    = predicate;
       this.evaluator    = evaluator;
       this.nextResource = getNextResource();
@@ -699,9 +685,7 @@ public class ClusterControllerImpl implements ClusterController {
     private Resource getNextResource() {
       while (iterator.hasNext()) {
         Resource next = iterator.next();
-
         if (predicate == null || evaluator.evaluate(predicate, next)) {
-          // TODO : copy the resource and only include the requested properties.
           return next;
         }
       }

+ 21 - 7
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ResourceImpl.java

@@ -23,6 +23,7 @@ import org.apache.ambari.server.controller.utilities.PropertyHelper;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 
 /**
@@ -57,16 +58,30 @@ public class ResourceImpl implements Resource {
    * @param resource  the resource to copy
    */
   public ResourceImpl(Resource resource) {
+    this(resource, null);
+  }
+
+  /**
+   * Construct a resource from the given resource, setting only the properties
+   * that are found in the given set of property and category ids.
+   *
+   * @param resource     the resource to copy
+   * @param propertyIds  the set of requested property and category ids
+   */
+  public ResourceImpl(Resource resource, Set<String> propertyIds) {
     this.type = resource.getType();
 
-    for (Map.Entry<String, Map<String, Object>> categoryEntry : resource.getPropertiesMap().entrySet()) {
+    for (Map.Entry<String, Map<String, Object>> categoryEntry :
+        resource.getPropertiesMap().entrySet()) {
       String category = categoryEntry.getKey();
       Map<String, Object> propertyMap = categoryEntry.getValue();
       if (propertyMap != null) {
         for (Map.Entry<String, Object> propertyEntry : propertyMap.entrySet()) {
-          String propertyId    = PropertyHelper.getPropertyId(category, propertyEntry.getKey());
-          Object propertyValue = propertyEntry.getValue();
-          setProperty(propertyId, propertyValue);
+          String propertyId = PropertyHelper.getPropertyId(category, propertyEntry.getKey());
+          if (propertyIds == null || propertyIds.isEmpty() || PropertyHelper.containsProperty(propertyIds, propertyId)) {
+            Object propertyValue = propertyEntry.getValue();
+            setProperty(propertyId, propertyValue);
+          }
         }
       }
     }
@@ -143,11 +158,10 @@ public class ResourceImpl implements Resource {
 
   @Override
   public int hashCode() {
-    int result =  31 * type.hashCode() + (propertiesMap != null ? propertiesMap.hashCode() : 0);
-    return result;
+    return 31 * type.hashCode() + (propertiesMap != null ? propertiesMap.hashCode() : 0);
   }
 
-// ----- utility methods ---------------------------------------------------
+  // ----- utility methods ---------------------------------------------------
 
   private String getCategoryKey(String category) {
     return category == null ? "" : category;

+ 26 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/PropertyHelper.java

@@ -183,6 +183,32 @@ public class PropertyHelper {
     return categories;
   }
 
+  /**
+   * Check if the given property id or one of its parent category ids is contained
+   * in the given set of property ids.
+   *
+   * @param propertyIds  the set of property ids
+   * @param propertyId   the property id
+   *
+   * @return true if the given property id of one of its parent category ids is
+   *         contained in the given set of property ids
+   */
+  public static boolean containsProperty(Set<String> propertyIds, String propertyId) {
+
+    if (propertyIds.contains(propertyId)){
+      return true;
+    }
+
+    String category = PropertyHelper.getPropertyCategory(propertyId);
+    while (category != null) {
+      if ( propertyIds.contains(category)) {
+        return true;
+      }
+      category = PropertyHelper.getPropertyCategory(category);
+    }
+    return false;
+  }
+
   /**
    * Check to see if the given property id contains replacement arguments (e.g. $1)
    *

+ 25 - 11
ambari-server/src/test/java/org/apache/ambari/server/api/handlers/ReadHandlerTest.java

@@ -54,7 +54,9 @@ public class ReadHandlerTest {
     expect(request.getFields()).andReturn(mapPartialResponseFields);
     expect(resource.getQuery()).andReturn(query);
 
-    query.addProperty("foo", "bar", null);
+    query.setPageRequest(null);
+    query.setMinimal(false);
+    query.addProperty("foo/bar", null);
     expectLastCall().andThrow(new IllegalArgumentException("testMsg"));
 
     replay(request, resource, query);
@@ -86,16 +88,18 @@ public class ReadHandlerTest {
     expect(request.getResource()).andReturn(resource);
     expect(resource.getQuery()).andReturn(query);
 
-    expect(request.getFields()).andReturn(mapPartialResponseFields);
     expect(request.getPageRequest()).andReturn(null);
-    query.addProperty(null, "foo", null);
-    query.addProperty("bar", "c", null);
-    query.addProperty("bar/d", "e", null);
-    query.addProperty("category", "", null);
+    expect(request.isMinimal()).andReturn(false);
+    expect(request.getFields()).andReturn(mapPartialResponseFields);
+    query.addProperty("foo", null);
+    query.addProperty("bar/c", null);
+    query.addProperty("bar/d/e", null);
+    query.addProperty("category/", null);
 
     expect(request.getQueryPredicate()).andReturn(predicate);
     query.setUserPredicate(predicate);
     query.setPageRequest(null);
+    query.setMinimal(false);
     expect(query.execute()).andReturn(result);
     result.setResultStatus(capture(resultStatusCapture));
 
@@ -118,12 +122,14 @@ public class ReadHandlerTest {
     expect(request.getResource()).andReturn(resource);
     expect(resource.getQuery()).andReturn(query);
 
-    expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
     expect(request.getPageRequest()).andReturn(null);
+    expect(request.isMinimal()).andReturn(false);
+    expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
 
     expect(request.getQueryPredicate()).andReturn(predicate);
     query.setUserPredicate(predicate);
     query.setPageRequest(null);
+    query.setMinimal(false);
     SystemException systemException = new SystemException("testMsg", new RuntimeException());
     expect(query.execute()).andThrow(systemException);
 
@@ -148,12 +154,14 @@ public class ReadHandlerTest {
     expect(request.getResource()).andReturn(resource);
     expect(resource.getQuery()).andReturn(query);
 
-    expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
     expect(request.getPageRequest()).andReturn(null);
+    expect(request.isMinimal()).andReturn(false);
+    expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
 
     expect(request.getQueryPredicate()).andReturn(predicate);
     query.setUserPredicate(predicate);
     query.setPageRequest(null);
+    query.setMinimal(false);
 
     expect(query.execute()).andThrow(exception);
 
@@ -179,12 +187,14 @@ public class ReadHandlerTest {
     expect(request.getResource()).andReturn(resource);
     expect(resource.getQuery()).andReturn(query);
 
-    expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
     expect(request.getPageRequest()).andReturn(null);
+    expect(request.isMinimal()).andReturn(false);
+    expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
 
     expect(request.getQueryPredicate()).andReturn(predicate);
     query.setUserPredicate(predicate);
     query.setPageRequest(null);
+    query.setMinimal(false);
 
     expect(query.execute()).andThrow(exception);
 
@@ -209,12 +219,14 @@ public class ReadHandlerTest {
     expect(request.getResource()).andReturn(resource);
     expect(resource.getQuery()).andReturn(query);
 
-    expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
     expect(request.getPageRequest()).andReturn(null);
+    expect(request.isMinimal()).andReturn(false);
+    expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
 
     expect(request.getQueryPredicate()).andReturn(predicate).anyTimes();
     query.setUserPredicate(predicate);
     query.setPageRequest(null);
+    query.setMinimal(false);
 
     expect(query.execute()).andThrow(exception);
 
@@ -238,12 +250,14 @@ public class ReadHandlerTest {
     expect(request.getResource()).andReturn(resource);
     expect(resource.getQuery()).andReturn(query);
 
-    expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
     expect(request.getPageRequest()).andReturn(null);
+    expect(request.isMinimal()).andReturn(false);
+    expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
 
     expect(request.getQueryPredicate()).andReturn(null).anyTimes();
     query.setUserPredicate(null);
     query.setPageRequest(null);
+    query.setMinimal(false);
 
     expect(query.execute()).andThrow(exception);
 

+ 3 - 3
ambari-server/src/test/java/org/apache/ambari/server/api/query/QueryImplTest.java

@@ -160,9 +160,9 @@ public class QueryImplTest {
     //test
     QueryImpl instance = new TestQuery(mapIds, resourceDefinition);
 
-    instance.addProperty("versions", "*", null);
-    instance.addProperty("versions/operatingSystems", "*", null);
-    instance.addProperty("versions/operatingSystems/repositories", "*", null);
+    instance.addProperty("versions/*", null);
+    instance.addProperty("versions/operatingSystems/*", null);
+    instance.addProperty("versions/operatingSystems/repositories/*", null);
 
     Result result = instance.execute();
 

+ 27 - 3
ambari-server/src/test/java/org/apache/ambari/server/api/services/BaseServiceTest.java

@@ -93,6 +93,7 @@ public abstract class BaseServiceTest {
     List<ServiceTestInvocation> listTestInvocations = getTestInvocations();
     for (ServiceTestInvocation testInvocation : listTestInvocations) {
       testMethod(testInvocation);
+      testMethodMinimal(testInvocation);
       testMethod_bodyParseException(testInvocation);
       testMethod_resultInErrorState(testInvocation);
     }
@@ -109,7 +110,30 @@ public abstract class BaseServiceTest {
     expect(request.process()).andReturn(result);
     expect(result.getStatus()).andReturn(status).atLeastOnce();
     expect(status.getStatusCode()).andReturn(testMethod.getStatusCode()).atLeastOnce();
-    expect(serializer.serialize(result)).andReturn(serializedResult);
+    expect(serializer.serialize(result, false)).andReturn(serializedResult);
+
+    replayMocks();
+
+    Response r = testMethod.invoke();
+
+    assertEquals(serializedResult, r.getEntity());
+    assertEquals(testMethod.getStatusCode(), r.getStatus());
+    verifyAndResetMocks();
+  }
+
+  private void testMethodMinimal(ServiceTestInvocation testMethod) throws InvocationTargetException, IllegalAccessException {
+    try {
+      expect(bodyParser.parse(testMethod.getBody())).andReturn(Collections.singleton(requestBody));
+    } catch (BodyParseException e) {
+      // needed for compiler
+    }
+
+    expect(requestFactory.createRequest(httpHeaders, requestBody, uriInfo, testMethod.getRequestType(), resourceInstance)).andReturn(request);
+    expect(request.isMinimal()).andReturn(true);
+    expect(request.process()).andReturn(result);
+    expect(result.getStatus()).andReturn(status).atLeastOnce();
+    expect(status.getStatusCode()).andReturn(testMethod.getStatusCode()).atLeastOnce();
+    expect(serializer.serialize(result, true)).andReturn(serializedResult);
 
     replayMocks();
 
@@ -124,7 +148,7 @@ public abstract class BaseServiceTest {
     Capture<Result> resultCapture = new Capture<Result>();
     BodyParseException e = new BodyParseException("TEST MSG");
     expect(bodyParser.parse(testMethod.getBody())).andThrow(e);
-    expect(serializer.serialize(capture(resultCapture))).andReturn(serializedResult);
+    expect(serializer.serialize(capture(resultCapture), eq(false))).andReturn(serializedResult);
 
     replayMocks();
 
@@ -146,7 +170,7 @@ public abstract class BaseServiceTest {
     expect(request.process()).andReturn(result);
     expect(result.getStatus()).andReturn(status).atLeastOnce();
     expect(status.getStatusCode()).andReturn(400).atLeastOnce();
-    expect(serializer.serialize(result)).andReturn(serializedResult);
+    expect(serializer.serialize(result, false)).andReturn(serializedResult);
 
     replayMocks();
 

+ 55 - 3
ambari-server/src/test/java/org/apache/ambari/server/api/services/serializers/JsonSerializerTest.java

@@ -47,7 +47,8 @@ public class JsonSerializerTest {
     result.setResultStatus(new ResultStatus(ResultStatus.STATUS.OK));
     TreeNode<Resource> tree = result.getResultTree();
     //tree.setName("items");
-    tree.addChild(resource, "resource1");
+    TreeNode<Resource> child = tree.addChild(resource, "resource1");
+    child.setProperty("href", "this is an href");
     //child.addChild(resource2, "sub-resource");
 
     // resource properties
@@ -71,9 +72,10 @@ public class JsonSerializerTest {
     replay(uriInfo, resource/*, resource2*/);
 
     //execute test
-    Object o = new JsonSerializer().serialize(result);
+    Object o = new JsonSerializer().serialize(result, false);
 
     String expected = "{\n" +
+        "  \"href\" : \"this is an href\",\n" +
         "  \"prop2\" : \"value2\",\n" +
         "  \"prop1\" : \"value1\",\n" +
         "  \"category\" : {\n" +
@@ -87,6 +89,56 @@ public class JsonSerializerTest {
     verify(uriInfo, resource/*, resource2*/);
   }
 
+  @Test
+  public void testSerializeMinimal() throws Exception {
+    UriInfo uriInfo = createMock(UriInfo.class);
+    Resource resource = createMock(Resource.class);
+    //Resource resource2 = createMock(Resource.class);
+
+    Result result = new ResultImpl(true);
+    result.setResultStatus(new ResultStatus(ResultStatus.STATUS.OK));
+    TreeNode<Resource> tree = result.getResultTree();
+    //tree.setName("items");
+    TreeNode<Resource> child = tree.addChild(resource, "resource1");
+    child.setProperty("href", "this is an href");
+    //child.addChild(resource2, "sub-resource");
+
+    // resource properties
+    HashMap<String, Object> mapRootProps = new HashMap<String, Object>();
+    mapRootProps.put("prop1", "value1");
+    mapRootProps.put("prop2", "value2");
+
+    HashMap<String, Object> mapCategoryProps = new HashMap<String, Object>();
+    mapCategoryProps.put("catProp1", "catValue1");
+    mapCategoryProps.put("catProp2", "catValue2");
+
+    Map<String, Map<String, Object>> propertyMap = new HashMap<String, Map<String, Object>>();
+
+    propertyMap.put(null, mapRootProps);
+    propertyMap.put("category", mapCategoryProps);
+
+    //expectations
+    expect(resource.getPropertiesMap()).andReturn(propertyMap).anyTimes();
+    expect(resource.getType()).andReturn(Resource.Type.Cluster).anyTimes();
+
+    replay(uriInfo, resource/*, resource2*/);
+
+    //execute test
+    Object o = new JsonSerializer().serialize(result, true);
+
+    String expected = "{\n" +
+        "  \"prop2\" : \"value2\",\n" +
+        "  \"prop1\" : \"value1\",\n" +
+        "  \"category\" : {\n" +
+        "    \"catProp1\" : \"catValue1\",\n" +
+        "    \"catProp2\" : \"catValue2\"\n" +
+        "  }\n" +
+        "}";
+
+    assertEquals(expected, o);
+
+    verify(uriInfo, resource/*, resource2*/);
+  }
 
   @Test
   public void testSerializeResources() throws Exception {
@@ -125,7 +177,7 @@ public class JsonSerializerTest {
     replay(uriInfo, resource);
 
     //execute test
-    Object o = new JsonSerializer().serialize(result);
+    Object o = new JsonSerializer().serialize(result, false);
 
     String expected = "{\n" +
         "  \"resources\" : [\n" +