Pārlūkot izejas kodu

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

tbeerbower 11 gadi atpakaļ
vecāks
revīzija
dd535bcf81
16 mainītis faili ar 325 papildinājumiem un 159 dzēšanām
  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" +