You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by js...@apache.org on 2014/02/27 16:58:18 UTC

[3/3] git commit: AMBARI-4786. Add ability to export a blueprint from a running cluster. This patch also includes new functionality for alternate renderings

AMBARI-4786.  Add ability to export a blueprint from a running cluster.
This patch also includes new functionality for alternate renderings


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/7eb4bc6b
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/7eb4bc6b
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/7eb4bc6b

Branch: refs/heads/trunk
Commit: 7eb4bc6b9905cc6df6aee2cfce662e7b53fe6d85
Parents: 62aa47b
Author: John Speidel <js...@hortonworks.com>
Authored: Fri Feb 21 12:57:02 2014 -0500
Committer: John Speidel <js...@hortonworks.com>
Committed: Thu Feb 27 10:58:00 2014 -0500

----------------------------------------------------------------------
 .../api/handlers/BaseManagementHandler.java     |   5 +-
 .../ambari/server/api/handlers/ReadHandler.java |  12 +-
 .../server/api/handlers/RequestHandler.java     |   1 -
 .../ambari/server/api/predicate/QueryLexer.java |   2 +
 .../apache/ambari/server/api/query/Query.java   |  17 +-
 .../ambari/server/api/query/QueryImpl.java      | 295 ++++++----
 .../ambari/server/api/query/QueryInfo.java      |  75 +++
 .../server/api/query/render/BaseRenderer.java   | 172 ++++++
 .../query/render/ClusterBlueprintRenderer.java  | 302 ++++++++++
 .../api/query/render/DefaultRenderer.java       |  68 +++
 .../api/query/render/MinimalRenderer.java       | 229 ++++++++
 .../server/api/query/render/Renderer.java       |  83 +++
 .../api/resources/BaseResourceDefinition.java   |  15 +
 .../resources/ClusterResourceDefinition.java    |  14 +-
 .../api/resources/ResourceDefinition.java       |  11 +
 .../ambari/server/api/services/BaseRequest.java |  46 +-
 .../ambari/server/api/services/BaseService.java |   7 +-
 .../ambari/server/api/services/Request.java     |   8 +-
 .../api/services/ResultPostProcessorImpl.java   |  12 +-
 .../services/serializers/JsonSerializer.java    |  12 +-
 .../services/serializers/ResultSerializer.java  |   3 +-
 .../apache/ambari/server/api/util/TreeNode.java |   7 +
 .../ambari/server/api/util/TreeNodeImpl.java    |   7 +
 .../controller/spi/ClusterController.java       |   2 +-
 .../server/controller/spi/SchemaFactory.java    |  32 ++
 .../server/api/handlers/CreateHandlerTest.java  |  36 +-
 .../server/api/handlers/DeleteHandlerTest.java  |  21 +-
 .../server/api/handlers/ReadHandlerTest.java    |  67 +--
 .../server/api/handlers/UpdateHandlerTest.java  |  12 +-
 .../server/api/predicate/QueryLexerTest.java    |  36 ++
 .../ambari/server/api/query/QueryImplTest.java  |   3 +-
 .../ambari/server/api/query/QueryInfoTest.java  |  50 ++
 .../render/ClusterBlueprintRendererTest.java    | 225 ++++++++
 .../api/query/render/DefaultRendererTest.java   | 342 ++++++++++++
 .../api/query/render/MinimalRendererTest.java   | 559 +++++++++++++++++++
 .../resources/BaseResourceDefinitionTest.java   |  21 +-
 .../ClusterResourceDefinitionTest.java          |  88 +++
 .../server/api/services/BaseRequestTest.java    | 212 ++++++-
 .../server/api/services/BaseServiceTest.java    |  28 +-
 .../serializers/JsonSerializerTest.java         |  55 +-
 40 files changed, 2882 insertions(+), 310 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/BaseManagementHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/BaseManagementHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/BaseManagementHandler.java
index a31a46e..c34f0d7 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/BaseManagementHandler.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/BaseManagementHandler.java
@@ -18,6 +18,7 @@
 
 package org.apache.ambari.server.api.handlers;
 
+import org.apache.ambari.server.api.query.Query;
 import org.apache.ambari.server.api.resources.*;
 import org.apache.ambari.server.api.services.*;
 import org.apache.ambari.server.api.services.persistence.PersistenceManager;
@@ -61,10 +62,12 @@ public abstract class BaseManagementHandler implements RequestHandler {
 
   @Override
   public Result handleRequest(Request request) {
+    Query query = request.getResource().getQuery();
     Predicate queryPredicate = request.getQueryPredicate();
 
+    query.setRenderer(request.getRenderer());
     if (queryPredicate != null) {
-      request.getResource().getQuery().setUserPredicate(queryPredicate);
+      query.setUserPredicate(queryPredicate);
     }
     return persist(request.getResource(), request.getBody());
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/ReadHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/ReadHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/ReadHandler.java
index 3b4cda1..d5717a6 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/ReadHandler.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/ReadHandler.java
@@ -23,8 +23,12 @@ import org.apache.ambari.server.api.services.ResultImpl;
 import org.apache.ambari.server.api.services.ResultStatus;
 import org.apache.ambari.server.api.services.Result;
 import org.apache.ambari.server.api.query.Query;
-import org.apache.ambari.server.controller.spi.*;
-import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.NoSuchResourceException;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.TemporalInfo;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -46,7 +50,7 @@ public class ReadHandler implements RequestHandler {
     Query query = request.getResource().getQuery();
 
     query.setPageRequest(request.getPageRequest());
-    query.setMinimal(request.isMinimal());
+    query.setRenderer(request.getRenderer());
 
     try {
       addFieldsToQuery(request, query);
@@ -95,7 +99,7 @@ public class ReadHandler implements RequestHandler {
    * Add partial response fields to the provided query.
    *
    * @param request  the current request
-   * @param query    the associated query   *
+   * @param query    the associated query
    */
   private void addFieldsToQuery(Request request, Query query) {
     //Partial response

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/RequestHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/RequestHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/RequestHandler.java
index 381dedb..9e2d923 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/RequestHandler.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/RequestHandler.java
@@ -18,7 +18,6 @@
 
 package org.apache.ambari.server.api.handlers;
 
-import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.api.services.Request;
 import org.apache.ambari.server.api.services.Result;
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java b/ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java
index 5aa04c4..4ab75aa 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java
@@ -38,6 +38,7 @@ public class QueryLexer {
    * Query string constants.
    */
   public static final String QUERY_FIELDS    = "fields";
+  public static final String QUERY_FORMAT    = "format";
   public static final String QUERY_PAGE_SIZE = "page_size";
   public static final String QUERY_TO        = "to";
   public static final String QUERY_FROM      = "from";
@@ -178,6 +179,7 @@ public class QueryLexer {
   static {
     // ignore values
     SET_IGNORE.add(QUERY_FIELDS);
+    SET_IGNORE.add(QUERY_FORMAT);
     SET_IGNORE.add(QUERY_PAGE_SIZE);
     SET_IGNORE.add(QUERY_TO);
     SET_IGNORE.add(QUERY_FROM);

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/query/Query.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/query/Query.java b/ambari-server/src/main/java/org/apache/ambari/server/api/query/Query.java
index 58c947a..b1dfa3d 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/query/Query.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/query/Query.java
@@ -18,8 +18,15 @@
 
 package org.apache.ambari.server.api.query;
 
+import org.apache.ambari.server.api.query.render.Renderer;
 import org.apache.ambari.server.api.services.Result;
-import org.apache.ambari.server.controller.spi.*;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.NoSuchResourceException;
+import org.apache.ambari.server.controller.spi.PageRequest;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.TemporalInfo;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 
 import java.util.Set;
 
@@ -93,9 +100,11 @@ public interface Query {
   public void setPageRequest(PageRequest pageRequest);
 
   /**
-   * Set a flag to indicate whether or not the response should be minimal.
+   * Set the corresponding renderer.
+   * The renderer is responsible for the rendering of the query result, including which
+   * properties are contained and the format of the result.
    *
-   * @param minimal  minimal flag; true indicates that the response should be minimal
+   * @param renderer  renderer for the query
    */
-  public void setMinimal(boolean minimal);
+  public void setRenderer(Renderer renderer);
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryImpl.java
index a7ac498..fc7f7a2 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryImpl.java
@@ -18,19 +18,21 @@
 
 package org.apache.ambari.server.api.query;
 
+import org.apache.ambari.server.api.query.render.DefaultRenderer;
+import org.apache.ambari.server.api.query.render.Renderer;
 import org.apache.ambari.server.api.resources.ResourceDefinition;
 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.api.util.TreeNode;
+import org.apache.ambari.server.api.util.TreeNodeImpl;
 import org.apache.ambari.server.controller.utilities.PredicateHelper;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.apache.ambari.server.controller.predicate.AndPredicate;
 import org.apache.ambari.server.controller.predicate.EqualsPredicate;
 import org.apache.ambari.server.api.services.Result;
 import org.apache.ambari.server.controller.spi.*;
-import org.apache.ambari.server.api.util.TreeNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -55,7 +57,7 @@ public class QueryImpl implements Query, ResourceInstance {
   /**
    * Properties of the query which make up the select portion of the query.
    */
-  private final Set<String> queryPropertySet = new HashSet<String>();
+  private final Set<String> requestedProperties = new HashSet<String>();
 
   /**
    * Map that associates categories with temporal data.
@@ -74,14 +76,15 @@ public class QueryImpl implements Query, ResourceInstance {
 
   /**
    * Sub-resources of the resource which is being operated on.
+   * Should only be added via {@link #addSubResource(String, QueryImpl)}
    */
-  private final Map<String, QueryImpl> querySubResourceSet = new HashMap<String, QueryImpl>();
+  private final Map<String, QueryImpl> requestedSubResources = new HashMap<String, QueryImpl>();
 
   /**
    * Sub-resource instances of this resource.
    * Map of resource name to resource instance.
    */
-  private Map<String, QueryImpl> subResourceSet;
+  private Map<String, QueryImpl> availableSubResources;
 
   /**
    * Indicates that the query should include all available properties.
@@ -99,14 +102,27 @@ public class QueryImpl implements Query, ResourceInstance {
   private PageRequest pageRequest;
 
   /**
-   * Indicates whether or not the response should be minimal.
+   * The sub resource properties referenced in the user predicate.
    */
-  private boolean minimal;
+  private final Set<String> subResourcePredicateProperties = new HashSet<String>();
 
   /**
-   * The sub resource properties referenced in the user predicate.
+   * Associated renderer. The default renderer is used unless
+   * an alternate renderer is specified for the request. The renderer
+   * is responsible for determining which properties are selected
+   * as well as the overall structure of the result.
    */
-  private final Set<String> subResourcePredicateProperties = new HashSet<String>();
+  private Renderer renderer;
+
+  /**
+   * Sub-resource predicate.
+   */
+  private Predicate subResourcePredicate;
+
+  /**
+   * Processed predicate.
+   */
+  private Predicate processedPredicate;
 
   /**
    * The logger.
@@ -141,9 +157,7 @@ public class QueryImpl implements Query, ResourceInstance {
       // wildcard
       addAllProperties(temporalInfo);
     } else{
-      if (addPropertyToSubResource(propertyId, temporalInfo)){
-        addKeyProperties(getResourceDefinition().getType(), !minimal);
-      } else {
+      if (! addPropertyToSubResource(propertyId, temporalInfo)) {
         if (propertyId.endsWith("/*")) {
           propertyId = propertyId.substring(0, propertyId.length() - 2);
         }
@@ -157,7 +171,7 @@ public class QueryImpl implements Query, ResourceInstance {
 
   @Override
   public void addLocalProperty(String property) {
-    queryPropertySet.add(property);
+    requestedProperties.add(property);
   }
 
   @Override
@@ -178,7 +192,7 @@ public class QueryImpl implements Query, ResourceInstance {
 
   @Override
   public Set<String> getProperties() {
-    return Collections.unmodifiableSet(queryPropertySet);
+    return Collections.unmodifiableSet(requestedProperties);
   }
 
   @Override
@@ -192,8 +206,9 @@ public class QueryImpl implements Query, ResourceInstance {
   }
 
   @Override
-  public void setMinimal(boolean minimal) {
-    this.minimal = minimal;
+  public void setRenderer(Renderer renderer) {
+    this.renderer = renderer;
+    renderer.init(clusterController);
   }
 
 
@@ -245,7 +260,7 @@ public class QueryImpl implements Query, ResourceInstance {
 
     return clusterController.equals(query.clusterController) && !(pageRequest != null ?
         !pageRequest.equals(query.pageRequest) :
-        query.pageRequest != null) && queryPropertySet.equals(query.queryPropertySet) &&
+        query.pageRequest != null) && requestedProperties.equals(query.requestedProperties) &&
         resourceDefinition.equals(query.resourceDefinition) &&
         keyValueMap.equals(query.keyValueMap) && !(userPredicate != null ?
         !userPredicate.equals(query.userPredicate) :
@@ -256,7 +271,7 @@ public class QueryImpl implements Query, ResourceInstance {
   public int hashCode() {
     int result = resourceDefinition.hashCode();
     result = 31 * result + clusterController.hashCode();
-    result = 31 * result + queryPropertySet.hashCode();
+    result = 31 * result + requestedProperties.hashCode();
     result = 31 * result + keyValueMap.hashCode();
     result = 31 * result + (userPredicate != null ? userPredicate.hashCode() : 0);
     result = 31 * result + (pageRequest != null ? pageRequest.hashCode() : 0);
@@ -270,8 +285,8 @@ public class QueryImpl implements Query, ResourceInstance {
    * Get the map of sub-resources.  Lazily create the map if required.  
    */
   protected Map<String, QueryImpl> ensureSubResources() {
-    if (subResourceSet == null) {
-      subResourceSet = new HashMap<String, QueryImpl>();
+    if (availableSubResources == null) {
+      availableSubResources = new HashMap<String, QueryImpl>();
       Set<SubResourceDefinition> setSubResourceDefs =
           getResourceDefinition().getSubResourceDefinitions();
 
@@ -283,28 +298,12 @@ public class QueryImpl implements Query, ResourceInstance {
         QueryImpl resource =  new QueryImpl(valueMap,
             ResourceInstanceFactoryImpl.getResourceDefinition(type, valueMap),
             controller);
-        resource.setMinimal(minimal);
-
-        Schema schema = controller.getSchema(type);
 
-        // ensure pk is returned
-        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() ?
-            resource.getResourceDefinition().getPluralName() :
-            resource.getResourceDefinition().getSingularName();
-
-        subResourceSet.put(subResourceName, resource);
+        String subResourceName = getSubResourceName(resource.getResourceDefinition(), subResDef);
+        availableSubResources.put(subResourceName, resource);
       }
     }
-    return subResourceSet;
+    return availableSubResources;
   }
 
   /**
@@ -317,59 +316,52 @@ public class QueryImpl implements Query, ResourceInstance {
       NoSuchParentResourceException {
 
     Set<Resource> providerResourceSet = new HashSet<Resource>();
-
     Resource.Type resourceType    = getResourceDefinition().getType();
-    Request       request         = createRequest(!minimal);
-    Request       qRequest        = createRequest(true);
     Predicate     queryPredicate  = createPredicate(getKeyValueMap(), processUserPredicate(userPredicate));
-    Set<Resource> resourceSet     = new LinkedHashSet<Resource>();
 
-    for (Resource queryResource : doQuery(resourceType, qRequest, queryPredicate)) {
+    // must occur after processing user predicate and prior to creating request
+    finalizeProperties();
+
+    Request       request     = createRequest();
+    Set<Resource> resourceSet = new LinkedHashSet<Resource>();
+
+    for (Resource queryResource : doQuery(resourceType, request, queryPredicate)) {
       providerResourceSet.add(queryResource);
       resourceSet.add(queryResource);
     }
-    queryResults.put(null,
-        new QueryResult(request, queryPredicate, userPredicate, getKeyValueMap(), resourceSet));
 
-    clusterController.populateResources(resourceType, providerResourceSet, qRequest, queryPredicate);
-    queryForSubResources(userPredicate, hasSubResourcePredicate());
+    queryResults.put(null, new QueryResult(
+        request, queryPredicate, userPredicate, getKeyValueMap(), resourceSet));
+
+    clusterController.populateResources(resourceType, providerResourceSet, request, queryPredicate);
+    queryForSubResources();
   }
 
   /**
    * Query the cluster controller for the sub-resources associated with 
    * this query object.
    */
-  private void queryForSubResources(Predicate predicate, boolean hasSubResourcePredicate)
+  private void queryForSubResources()
       throws UnsupportedPropertyException,
       SystemException,
       NoSuchResourceException,
       NoSuchParentResourceException {
 
-    for (Map.Entry<String, QueryImpl> entry : querySubResourceSet.entrySet()) {
-
-      QueryImpl     subResource  = entry.getValue();
-      Resource.Type resourceType = subResource.getResourceDefinition().getType();
-      Request       request      = subResource.createRequest(!minimal);
-      Request       qRequest     = subResource.createRequest(true);
-
-      Predicate subResourcePredicate = hasSubResourcePredicate ?
-          getSubResourcePredicate(predicate, entry.getKey()) : null;
-
-      Predicate processedPredicate = hasSubResourcePredicate ? subResource.processUserPredicate(subResourcePredicate) : null;
-
+    for (Map.Entry<String, QueryImpl> entry : requestedSubResources.entrySet()) {
+      QueryImpl     subResource         = entry.getValue();
+      Resource.Type resourceType        = subResource.getResourceDefinition().getType();
+      Request       request             = subResource.createRequest();
       Set<Resource> providerResourceSet = new HashSet<Resource>();
 
       for (QueryResult queryResult : queryResults.values()) {
         for (Resource resource : queryResult.getProviderResourceSet()) {
-
           Map<Resource.Type, String> map = getKeyValueMap(resource, queryResult.getKeyValueMap());
 
-          Predicate queryPredicate = subResource.createPredicate(map, processedPredicate);
-
-          Set<Resource> resourceSet = new LinkedHashSet<Resource>();
+          Predicate     queryPredicate = subResource.createPredicate(map, subResource.processedPredicate);
+          Set<Resource> resourceSet    = new LinkedHashSet<Resource>();
 
           try {
-            for (Resource queryResource : subResource.doQuery(resourceType, qRequest, queryPredicate)) {
+            for (Resource queryResource : subResource.doQuery(resourceType, request, queryPredicate)) {
               providerResourceSet.add(queryResource);
               resourceSet.add(queryResource);
             }
@@ -380,8 +372,8 @@ public class QueryImpl implements Query, ResourceInstance {
               new QueryResult(request, queryPredicate, subResourcePredicate, map, resourceSet));
         }
       }
-      clusterController.populateResources(resourceType, providerResourceSet, qRequest, null);
-      subResource.queryForSubResources(subResourcePredicate, hasSubResourcePredicate);
+      clusterController.populateResources(resourceType, providerResourceSet, request, null);
+      subResource.queryForSubResources();
     }
   }
 
@@ -394,14 +386,10 @@ public class QueryImpl implements Query, ResourceInstance {
       NoSuchResourceException,
       NoSuchParentResourceException {
 
-    if (queryPropertySet.isEmpty() && querySubResourceSet.isEmpty()) {
-      //Add sub resource properties for default case where no fields are specified.
-      querySubResourceSet.putAll(ensureSubResources());
-    }
-
     if (LOG.isDebugEnabled()) {
       LOG.debug("Executing resource query: " + request + " where " + predicate);
     }
+
     return clusterController.getResources(type, request, predicate);
   }
 
@@ -499,7 +487,7 @@ public class QueryImpl implements Query, ResourceInstance {
           Set<Map<String, Object>> propertyMaps = new HashSet<Map<String, Object>>();
 
           // For each sub category get the property maps for the sub resources
-          for (Map.Entry<String, QueryImpl> entry : querySubResourceSet.entrySet()) {
+          for (Map.Entry<String, QueryImpl> entry : requestedSubResources.entrySet()) {
             String subResourceCategory = category == null ? entry.getKey() : category + "/" + entry.getKey();
 
             QueryImpl subResource = entry.getValue();
@@ -530,6 +518,77 @@ public class QueryImpl implements Query, ResourceInstance {
     return resourcePropertyMaps;
   }
 
+  /**
+   * Finalize properties for entire query tree before executing query.
+   */
+  private void finalizeProperties() {
+    ResourceDefinition rootDefinition = this.resourceDefinition;
+
+    QueryInfo rootQueryInfo = new QueryInfo(rootDefinition, this.requestedProperties);
+    TreeNode<QueryInfo> rootNode = new TreeNodeImpl<QueryInfo>(
+        null, rootQueryInfo, rootDefinition.getType().name());
+
+    TreeNode<QueryInfo> requestedPropertyTree = buildQueryPropertyTree(this, rootNode);
+
+    mergeFinalizedProperties(renderer.finalizeProperties(
+        requestedPropertyTree, isCollectionResource()), this);
+  }
+
+  /**
+   * Recursively build a tree of query information.
+   *
+   * @param query  query to process
+   * @param node   tree node associated with the query
+   *
+   * @return query info tree
+   */
+  private TreeNode<QueryInfo> buildQueryPropertyTree(QueryImpl query, TreeNode<QueryInfo> node) {
+    for (QueryImpl subQuery : query.requestedSubResources.values()) {
+      ResourceDefinition childResource = subQuery.resourceDefinition;
+
+      QueryInfo queryInfo = new QueryInfo(childResource, subQuery.requestedProperties);
+      TreeNode<QueryInfo> childNode = node.addChild(queryInfo, childResource.getType().name());
+      buildQueryPropertyTree(subQuery, childNode);
+    }
+    return node;
+  }
+
+  /**
+   * Merge the tree of query properties returned by the renderer with properties in
+   * the query tree.
+   *
+   * @param node   property tree node
+   * @param query  query associated with the property tree node
+   */
+  private void mergeFinalizedProperties(TreeNode<Set<String>> node, QueryImpl query) {
+
+    Set<String> finalizedProperties = node.getObject();
+    query.requestedProperties.clear();
+    // currently not exposing temporal information to renderer
+    query.requestedProperties.addAll(finalizedProperties);
+
+    for (TreeNode<Set<String>> child : node.getChildren()) {
+      Resource.Type childType = Resource.Type.valueOf(child.getName());
+      ResourceDefinition parentResource = query.resourceDefinition;
+      Set<SubResourceDefinition> subResources = parentResource.getSubResourceDefinitions();
+      String subResourceName = null;
+      for (SubResourceDefinition subResource : subResources) {
+        if (subResource.getType() == childType) {
+          ResourceDefinition resource = ResourceInstanceFactoryImpl.getResourceDefinition(
+              subResource.getType(), query.keyValueMap);
+          subResourceName = getSubResourceName(resource, subResource);
+          break;
+        }
+      }
+      QueryImpl subQuery = query.requestedSubResources.get(subResourceName);
+      if (subQuery == null) {
+        query.addProperty(subResourceName, null);
+        subQuery = query.requestedSubResources.get(subResourceName);
+      }
+      mergeFinalizedProperties(child, subQuery);
+    }
+  }
+
   // Map the given set of property ids to corresponding property ids in the
   // given sub-resource category.
   private Map<String, String> getPropertyIdsForCategory(Set<String> propertyIds, String category) {
@@ -601,17 +660,14 @@ public class QueryImpl implements Query, ResourceInstance {
         iterResource = pageResponse.getIterable();
       }
 
-      Set<String> propertyIds = queryRequest.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(
-            minimal ? new ResourceImpl(resource, propertyIds) : resource,
-            resource.getType() + ":" + count++);
+            resource, resource.getType() + ":" + count++);
 
-        for (Map.Entry<String, QueryImpl> entry : querySubResourceSet.entrySet()) {
+        for (Map.Entry<String, QueryImpl> entry : requestedSubResources.entrySet()) {
           String    subResCategory = entry.getKey();
           QueryImpl subResource    = entry.getValue();
 
@@ -622,7 +678,7 @@ public class QueryImpl implements Query, ResourceInstance {
         }
       }
     }
-    return result;
+    return renderer.finalizeResult(result);
   }
 
   // Indicates whether or not this query has sub-resource elements
@@ -651,24 +707,6 @@ public class QueryImpl implements Query, ResourceInstance {
     return visitor.getExtendedPredicate();
   }
 
-  private void addKeyProperties(Resource.Type resourceType, boolean includeFKs) {
-    Schema schema = clusterController.getSchema(resourceType);
-
-    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);
-    }
-  }
-
   private void addAllProperties(TemporalInfo temporalInfo) {
     allProperties = true;
     if (temporalInfo != null) {
@@ -677,8 +715,8 @@ public class QueryImpl implements Query, ResourceInstance {
 
     for (Map.Entry<String, QueryImpl> entry : ensureSubResources().entrySet()) {
       String name = entry.getKey();
-      if (! querySubResourceSet.containsKey(name)) {
-        querySubResourceSet.put(name, entry.getValue());
+      if (! requestedSubResources.containsKey(name)) {
+        addSubResource(name, entry.getValue());
       }
     }
   }
@@ -691,7 +729,7 @@ public class QueryImpl implements Query, ResourceInstance {
 
     QueryImpl subResource = subResources.get(category);
     if (subResource != null) {
-      querySubResourceSet.put(category, subResource);
+      addSubResource(category, subResource);
 
       //only add if a sub property is set or if a sub category is specified
       if (index != -1) {
@@ -766,27 +804,28 @@ public class QueryImpl implements Query, ResourceInstance {
     // record the sub-resource properties on this query
     subResourcePredicateProperties.addAll(visitor.getSubResourceProperties());
 
-    return visitor.getProcessedPredicate();
+    if (hasSubResourcePredicate()) {
+      for (Map.Entry<String, QueryImpl> entry : requestedSubResources.entrySet()) {
+        subResourcePredicate = getSubResourcePredicate(predicate, entry.getKey());
+        entry.getValue().processUserPredicate(subResourcePredicate);
+      }
+    }
+
+    processedPredicate = visitor.getProcessedPredicate();
+    return processedPredicate;
   }
 
-  private Request createRequest(boolean includeFKs) {
+  private Request createRequest() {
     
     if (allProperties) {
       return PropertyHelper.getReadRequest(Collections.<String>emptySet());
     }
-    
-    Set<String> setProperties = new HashSet<String>();
 
     Map<String, TemporalInfo> mapTemporalInfo    = new HashMap<String, TemporalInfo>();
     TemporalInfo              globalTemporalInfo = temporalInfoMap.get(null);
-    Resource.Type             resourceType       = getResourceDefinition().getType();
 
-    if (getKeyValueMap().get(resourceType) == null) {
-      addKeyProperties(resourceType, includeFKs);
-    }
-
-    setProperties.addAll(queryPropertySet);
-    
+    Set<String> setProperties = new HashSet<String>();
+    setProperties.addAll(requestedProperties);
     for (String propertyId : setProperties) {
       TemporalInfo temporalInfo = temporalInfoMap.get(propertyId);
       if (temporalInfo != null) {
@@ -822,6 +861,33 @@ public class QueryImpl implements Query, ResourceInstance {
     return resourceKeyValueMap;
   }
 
+  /**
+   * Add a sub query with the renderer set.
+   *
+   * @param name   name of sub resource
+   * @param query  sub resource
+   */
+  private void addSubResource(String name, QueryImpl query) {
+    // renderer specified for request only applies to top level query
+    query.setRenderer(new DefaultRenderer());
+    requestedSubResources.put(name, query);
+  }
+
+  /**
+   * Obtain the name of a sub-resource.
+   *
+   * @param resource     parent resource
+   * @param subResource  sub-resource
+   *
+   * @return either the plural or singular sub-resource name based on whether the sub-resource is
+   *         included as a collection
+   */
+  private String getSubResourceName(ResourceDefinition resource, SubResourceDefinition subResource) {
+    return subResource.isCollection() ?
+        resource.getPluralName() :
+        resource.getSingularName();
+  }
+
   // ----- inner class : QueryResult -----------------------------------------
 
   /**
@@ -836,9 +902,8 @@ public class QueryImpl implements Query, ResourceInstance {
 
     // ----- Constructor -----------------------------------------------------
 
-    private QueryResult(Request request, Predicate predicate,
-                        Predicate userPredicate, Map<Resource.Type, String> keyValueMap,
-                        Set<Resource> providerResourceSet) {
+    private  QueryResult(Request request, Predicate predicate, Predicate userPredicate,
+                         Map<Resource.Type, String> keyValueMap, Set<Resource> providerResourceSet) {
       this.request             = request;
       this.predicate           = predicate;
       this.userPredicate       = userPredicate;

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryInfo.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryInfo.java
new file mode 100644
index 0000000..80b79f0
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryInfo.java
@@ -0,0 +1,75 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.api.query;
+
+import org.apache.ambari.server.api.resources.ResourceDefinition;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Query related information.
+ */
+public class QueryInfo {
+  /**
+   * Resource definition
+   */
+  private ResourceDefinition m_resource;
+
+  /**
+   * Requested properties for the query.
+   * These properties comprise the select portion of a query.
+   */
+  private Set<String> m_properties;
+
+  // ----- Constructors ------------------------------------------------------
+
+  /**
+   * Constructor
+   *
+   * @param resource    resource definition
+   * @param properties  query properties
+   */
+  public QueryInfo(ResourceDefinition resource, Set<String> properties) {
+    m_resource   = resource;
+    m_properties = new HashSet<String>(properties);
+  }
+
+  // ----- QueryInfo ---------------------------------------------------------
+
+  /**
+   * Obtain the resource definition associated with the query.
+   *
+   * @return associated resource definition
+   */
+  public ResourceDefinition getResource() {
+    return m_resource;
+  }
+
+  /**
+   * Obtain the properties associated with the query.
+   * These are the requested properties which comprise
+   * the select portion of the query.
+   *
+   * @return requested properties
+   */
+  public Set<String> getProperties() {
+    return m_properties;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/BaseRenderer.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/BaseRenderer.java b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/BaseRenderer.java
new file mode 100644
index 0000000..7866aa4
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/BaseRenderer.java
@@ -0,0 +1,172 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.api.query.render;
+
+import org.apache.ambari.server.api.query.QueryInfo;
+import org.apache.ambari.server.api.resources.ResourceDefinition;
+import org.apache.ambari.server.api.resources.SubResourceDefinition;
+import org.apache.ambari.server.api.util.TreeNode;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.Schema;
+import org.apache.ambari.server.controller.spi.SchemaFactory;
+
+import java.util.HashSet;
+import java.util.Set;
+
+
+/**
+ * Base renderer.
+ * Contains functionality which may be common across implementations.
+ */
+public abstract class BaseRenderer implements Renderer {
+
+  /**
+   * Factory for creating schema instances.
+   */
+  private SchemaFactory m_schemaFactory;
+
+  @Override
+  public void init(SchemaFactory schemaFactory) {
+    m_schemaFactory = schemaFactory;
+  }
+
+  /**
+   * Obtain a schema instance based on resource type.
+   *
+   * @param type  resource type
+   *
+   * @return schema instance for the provided resource type
+   */
+  protected Schema getSchema(Resource.Type type) {
+    return m_schemaFactory.getSchema(type);
+  }
+
+  /**
+   * Copies a tree of QueryInfo to a tree of Set<String>.
+   * This is useful in {@link Renderer#finalizeProperties(TreeNode, boolean)} for converting
+   * the passed in tree of query info to the return type which is a set of property names.
+   *
+   * @param queryTree     source tree
+   * @param propertyTree  target tree
+   */
+  protected void copyPropertiesToResult(TreeNode<QueryInfo> queryTree, TreeNode<Set<String>> propertyTree) {
+    for (TreeNode<QueryInfo> node : queryTree.getChildren()) {
+      TreeNode<Set<String>> child = propertyTree.addChild(
+          node.getObject().getProperties(), node.getName());
+      copyPropertiesToResult(node, child);
+    }
+  }
+
+  /**
+   * Add primary key for the specified resource type to the provided set.
+   *
+   * @param resourceType  resource type
+   * @param properties    set of properties which pk will be added to
+   */
+  protected void addPrimaryKey(Resource.Type resourceType, Set<String> properties) {
+    properties.add(getSchema(resourceType).getKeyPropertyId(resourceType));
+  }
+
+  /**
+   * Add primary and all foreign keys for the specified resource type to the provided set.
+   *
+   * @param resourceType  resource type
+   * @param properties    set of properties which keys will be added to
+   */
+  protected void addKeys(Resource.Type resourceType, Set<String> properties) {
+    Schema schema = getSchema(resourceType);
+
+    for (Resource.Type type : Resource.Type.values()) {
+      String propertyId = schema.getKeyPropertyId(type);
+      if (propertyId != null) {
+        properties.add(propertyId);
+      }
+    }
+  }
+
+  /**
+   * Determine if the query node contains no properties and no children.
+   *
+   * @param queryNode  the query node to check
+   *
+   * @return true if the node contains no properties or children; false otherwise
+   */
+  protected boolean isRequestWithNoProperties(TreeNode<QueryInfo> queryNode) {
+    return queryNode.getChildren().isEmpty() &&
+        queryNode.getObject().getProperties().size() == 0;
+  }
+
+  /**
+   * Add available sub resources to property node.
+   *
+   * @param queryTree     query tree
+   * @param propertyTree  property tree
+   */
+  protected void addSubResources(TreeNode<QueryInfo> queryTree,
+                                 TreeNode<Set<String>> propertyTree) {
+
+    QueryInfo queryInfo = queryTree.getObject();
+    ResourceDefinition resource = queryInfo.getResource();
+    Set<SubResourceDefinition> subResources = resource.getSubResourceDefinitions();
+    for (SubResourceDefinition subResource : subResources) {
+      Set<String> resourceProperties = new HashSet<String>();
+      populateSubResourceDefaults(subResource, resourceProperties);
+      propertyTree.addChild(resourceProperties, subResource.getType().name());
+    }
+  }
+
+  /**
+   * Populate sub-resource properties.
+   *
+   * @param subResource  definition of sub-resource
+   * @param properties   property set to update
+   */
+  protected void populateSubResourceDefaults(
+      SubResourceDefinition subResource, Set<String> properties) {
+
+    Schema schema = getSchema(subResource.getType());
+    Set<Resource.Type> foreignKeys = subResource.getAdditionalForeignKeys();
+    for (Resource.Type fk : foreignKeys) {
+      properties.add(schema.getKeyPropertyId(fk));
+    }
+    addPrimaryKey(subResource.getType(), properties);
+    addKeys(subResource.getType(), properties);
+  }
+
+  /**
+   * Add required primary and foreign keys properties based on request type.
+   *
+   * @param propertyTree  tree of properties
+   * @param addIfEmpty    whether keys should be added to node with no properties
+   */
+  protected void ensureRequiredProperties(
+      TreeNode<Set<String>> propertyTree, boolean addIfEmpty) {
+
+    Resource.Type type = Resource.Type.valueOf(propertyTree.getName());
+    Set<String> properties = propertyTree.getObject();
+
+    if (!properties.isEmpty() || addIfEmpty) {
+      addKeys(type, properties);
+    }
+
+    for (TreeNode<Set<String>> child : propertyTree.getChildren()) {
+      ensureRequiredProperties(child, addIfEmpty);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/ClusterBlueprintRenderer.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/ClusterBlueprintRenderer.java b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/ClusterBlueprintRenderer.java
new file mode 100644
index 0000000..db3dff8
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/ClusterBlueprintRenderer.java
@@ -0,0 +1,302 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.api.query.render;
+
+import org.apache.ambari.server.api.query.QueryInfo;
+import org.apache.ambari.server.api.services.Request;
+import org.apache.ambari.server.api.services.Result;
+import org.apache.ambari.server.api.services.ResultImpl;
+import org.apache.ambari.server.api.services.ResultPostProcessor;
+import org.apache.ambari.server.api.services.ResultPostProcessorImpl;
+import org.apache.ambari.server.api.util.TreeNode;
+import org.apache.ambari.server.api.util.TreeNodeImpl;
+import org.apache.ambari.server.controller.internal.ResourceImpl;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Renderer which renders a cluster resource as a blueprint.
+ */
+public class ClusterBlueprintRenderer extends BaseRenderer implements Renderer {
+
+  // ----- Renderer ----------------------------------------------------------
+
+  @Override
+  public TreeNode<Set<String>> finalizeProperties(
+      TreeNode<QueryInfo> queryProperties, boolean isCollection) {
+
+    Set<String> properties = new HashSet<String>(queryProperties.getObject().getProperties());
+    TreeNode<Set<String>> resultTree = new TreeNodeImpl<Set<String>>(
+        null, properties, queryProperties.getName());
+
+    copyPropertiesToResult(queryProperties, resultTree);
+    String hostType = Resource.Type.Host.name();
+    String hostComponentType = Resource.Type.HostComponent.name();
+    TreeNode<Set<String>> hostComponentNode = resultTree.getChild(
+        hostType + "/" + hostComponentType);
+
+    if (hostComponentNode == null) {
+      TreeNode<Set<String>> hostNode = resultTree.getChild(hostType);
+      if (hostNode == null) {
+        hostNode = resultTree.addChild(new HashSet<String>(), hostType);
+      }
+      hostComponentNode = hostNode.addChild(new HashSet<String>(), hostComponentType);
+    }
+    hostComponentNode.getObject().add("HostRoles/component_name");
+
+    return resultTree;
+  }
+
+  @Override
+  public Result finalizeResult(Result queryResult) {
+    TreeNode<Resource> resultTree = queryResult.getResultTree();
+    Result result = new ResultImpl(true);
+    TreeNode<Resource> blueprintResultTree = result.getResultTree();
+    if (isCollection(resultTree)) {
+      blueprintResultTree.setProperty("isCollection", "true");
+    }
+
+    for (TreeNode<Resource> node : resultTree.getChildren()) {
+      Resource blueprintResource = createBlueprintResource(node);
+      blueprintResultTree.addChild(new TreeNodeImpl<Resource>(
+          blueprintResultTree, blueprintResource, node.getName()));
+    }
+    return result;
+  }
+
+  @Override
+  public ResultPostProcessor getResultPostProcessor(Request request) {
+    return new BlueprintPostProcessor(request);
+  }
+
+  // ----- private instance methods ------------------------------------------
+
+  /**
+   * Create a blueprint resource.
+   *
+   * @param clusterNode  cluster tree node
+   *
+   * @return a new blueprint resource
+   */
+  private Resource createBlueprintResource(TreeNode<Resource> clusterNode) {
+    Resource clusterResource = clusterNode.getObject();
+    Resource blueprintResource = new ResourceImpl(Resource.Type.Cluster);
+    String clusterName = (String) clusterResource.getPropertyValue(
+        PropertyHelper.getPropertyId("Clusters", "cluster_name"));
+    //todo: deal with name collision?
+    String blueprintName = "blueprint-" + clusterName;
+    String[] stackTokens = ((String) clusterResource.getPropertyValue(
+            PropertyHelper.getPropertyId("Clusters", "version"))).split("-");
+
+    blueprintResource.setProperty("Blueprints/blueprint_name", blueprintName);
+    blueprintResource.setProperty("Blueprints/stack_name", stackTokens[0]);
+    blueprintResource.setProperty("Blueprints/stack_version", stackTokens[1]);
+    blueprintResource.setProperty(
+        "host_groups", processHostGroups(clusterNode.getChild("hosts")));
+
+    return blueprintResource;
+  }
+
+  /**
+   * Process host group information for all hosts.
+   *
+   * @param hostNode a host node
+   *
+   * @return list of host group property maps, one element for each host group
+   */
+  private List<Map<String, Object>> processHostGroups(TreeNode<Resource> hostNode) {
+    Map<HostGroup, HostGroup> mapHostGroups = new HashMap<HostGroup, HostGroup>();
+    for (TreeNode<Resource> host : hostNode.getChildren()) {
+      HostGroup group = HostGroup.parse(host);
+      if (mapHostGroups.containsKey(group)) {
+        mapHostGroups.get(group).incrementCardinality();
+      } else {
+        mapHostGroups.put(group, group);
+      }
+    }
+
+    int count = 1;
+    List<Map<String, Object>> listHostGroups = new ArrayList<Map<String, Object>>();
+    for (HostGroup group : mapHostGroups.values()) {
+      String groupName = "host_group_" + count++;
+      Map<String, Object> mapGroupProperties = new HashMap<String, Object>();
+      listHostGroups.add(mapGroupProperties);
+
+      mapGroupProperties.put("name", groupName);
+      mapGroupProperties.put("cardinality", String.valueOf(group.getCardinality()));
+      mapGroupProperties.put("components", processHostGroupComponents(group));
+    }
+    return listHostGroups;
+  }
+
+  /**
+   * Process host group component information for a specific host.
+   *
+   * @param group host group instance
+   *
+   * @return list of component names for the host
+   */
+  private List<Map<String, String>> processHostGroupComponents(HostGroup group) {
+    List<Map<String, String>> listHostGroupComponents = new ArrayList<Map<String, String>>();
+    for (String component : group.getComponents()) {
+      Map<String, String> mapComponentProperties = new HashMap<String, String>();
+      listHostGroupComponents.add(mapComponentProperties);
+      mapComponentProperties.put("name", component);
+    }
+    return listHostGroupComponents;
+  }
+
+  /**
+   * Determine whether a node represents a collection.
+   *
+   * @param node  node which is evaluated for being a collection
+   *
+   * @return true if the node represents a collection; false otherwise
+   */
+  private boolean isCollection(TreeNode<Resource> node) {
+    String isCollection = node.getProperty("isCollection");
+    return isCollection != null && isCollection.equals("true");
+  }
+
+  // ----- Host Group inner class --------------------------------------------
+
+  /**
+   * Host Group representation.
+   */
+  private static class HostGroup {
+    /**
+     * Associated components.
+     */
+    private Set<String> m_components = new HashSet<String>();
+
+    /**
+     * Number of instances.
+     */
+    private int m_cardinality = 1;
+
+    /**
+     * Factory method for obtaining a host group instance.
+     * Parses a host tree node for host related information.
+     *
+     * @param host  host tree node
+     *
+     * @return a new HostGroup instance
+     */
+    public static HostGroup parse(TreeNode<Resource> host) {
+      HostGroup group = new HostGroup();
+
+      TreeNode<Resource> components = host.getChild("host_components");
+      for (TreeNode<Resource> component : components.getChildren()) {
+        group.getComponents().add((String) component.getObject().getPropertyValue(
+            "HostRoles/component_name"));
+      }
+
+      group.addAmbariComponentIfLocalhost((String) host.getObject().getPropertyValue(
+          PropertyHelper.getPropertyId("Hosts", "host_name")));
+
+      return group;
+    }
+
+    /**                                                           `
+     * Obtain associated components.
+     *
+     * @return set of associated components
+     */
+    public Set<String> getComponents() {
+      return m_components;
+    }
+
+    /**
+     * Obtain the number of instances associated with this host group.
+     *
+     * @return number of hosts associated with this host group
+     */
+    public int getCardinality() {
+      return m_cardinality;
+    }
+
+    /**
+     * Increment the cardinality count by one.
+     */
+    public void incrementCardinality() {
+      m_cardinality += 1;
+    }
+
+    /**
+     * Add the AMBARI_SERVER component if the host is the local host.
+     *
+     * @param hostname  host to check
+     */
+    private void addAmbariComponentIfLocalhost(String hostname) {
+      try {
+        InetAddress hostAddress = InetAddress.getByName(hostname);
+        try {
+          if (hostAddress.equals(InetAddress.getLocalHost())) {
+            getComponents().add("AMBARI_SERVER");
+          }
+        } catch (UnknownHostException e) {
+          //todo: SystemException?
+          throw new RuntimeException("Unable to obtain local host name", e);
+        }
+      } catch (UnknownHostException e) {
+        // ignore
+      }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+
+      HostGroup hostGroup = (HostGroup) o;
+
+      return m_components.equals(hostGroup.m_components);
+    }
+
+    @Override
+    public int hashCode() {
+      return m_components.hashCode();
+    }
+  }
+
+  // ----- Blueprint Post Processor inner class ------------------------------
+
+  /**
+   * Post processor that strips href properties
+   */
+  private static class BlueprintPostProcessor extends ResultPostProcessorImpl {
+    private BlueprintPostProcessor(Request request) {
+      super(request);
+    }
+
+    @Override
+    protected void finalizeNode(TreeNode<Resource> node) {
+      node.removeProperty("href");
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/DefaultRenderer.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/DefaultRenderer.java b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/DefaultRenderer.java
new file mode 100644
index 0000000..1b996a2
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/DefaultRenderer.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.api.query.render;
+
+import org.apache.ambari.server.api.query.QueryInfo;
+import org.apache.ambari.server.api.services.Request;
+import org.apache.ambari.server.api.services.Result;
+import org.apache.ambari.server.api.services.ResultPostProcessor;
+import org.apache.ambari.server.api.services.ResultPostProcessorImpl;
+import org.apache.ambari.server.api.util.TreeNode;
+import org.apache.ambari.server.api.util.TreeNodeImpl;
+import java.util.Set;
+
+/**
+ * Default resource renderer.
+ * Provides the default "native" rendering for all resources.
+ */
+public class DefaultRenderer extends BaseRenderer implements Renderer {
+
+  // ----- Renderer ----------------------------------------------------------
+
+  @Override
+  public TreeNode<Set<String>> finalizeProperties(
+      TreeNode<QueryInfo> queryTree, boolean isCollection) {
+
+    QueryInfo queryInfo = queryTree.getObject();
+    TreeNode<Set<String>> resultTree = new TreeNodeImpl<Set<String>>(
+        null, queryInfo.getProperties(), queryTree.getName());
+
+    copyPropertiesToResult(queryTree, resultTree);
+
+    boolean addKeysToEmptyResource = true;
+    if (! isCollection && isRequestWithNoProperties(queryTree)) {
+      addSubResources(queryTree, resultTree);
+      addKeysToEmptyResource = false;
+    }
+    ensureRequiredProperties(resultTree, addKeysToEmptyResource);
+
+    return resultTree;
+  }
+
+  @Override
+  public ResultPostProcessor getResultPostProcessor(Request request) {
+    // simply return the native rendering
+    return new ResultPostProcessorImpl(request);
+  }
+
+  @Override
+  public Result finalizeResult(Result queryResult) {
+    return queryResult;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/MinimalRenderer.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/MinimalRenderer.java b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/MinimalRenderer.java
new file mode 100644
index 0000000..2fe4fce
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/MinimalRenderer.java
@@ -0,0 +1,229 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.api.query.render;
+
+import org.apache.ambari.server.api.query.QueryInfo;
+import org.apache.ambari.server.api.services.Request;
+import org.apache.ambari.server.api.services.Result;
+import org.apache.ambari.server.api.services.ResultPostProcessor;
+import org.apache.ambari.server.api.services.ResultPostProcessorImpl;
+import org.apache.ambari.server.api.util.TreeNode;
+import org.apache.ambari.server.api.util.TreeNodeImpl;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Minimal Renderer.
+ *
+ * All href properties are stripped from the result.
+ *
+ * For the root resource, this renderer behaves identically to the
+ * default renderer wrt resource properties and sub-resources. If
+ * no root properties or sub-resources are specified all top level
+ * properties and all sub-resources are included in the result. If
+ * any root properties or any sub-resources are requested, then only
+ * those will be included in the result.
+ *
+ * For sub-resource, only primary keys and any requested properties
+ * are included in the result.
+ *
+ * This renderer can be specified for any resource using
+ * 'format=minimal' or the older syntax 'minimal_response=true'.
+ */
+public class MinimalRenderer extends BaseRenderer implements Renderer {
+
+  /**
+   * Type of root resource.
+   */
+  private Resource.Type m_rootType;
+
+  /**
+   * Whether the request is for a collection.
+   */
+  private boolean m_isCollection;
+
+  /**
+   * Map of requested properties.
+   */
+  private Map<Resource.Type, Set<String>> m_originalProperties =
+      new HashMap<Resource.Type, Set<String>>();
+
+  // ----- Renderer ----------------------------------------------------------
+
+  @Override
+  public TreeNode<Set<String>> finalizeProperties(
+      TreeNode<QueryInfo> queryTree, boolean isCollection) {
+
+    QueryInfo queryInfo = queryTree.getObject();
+    TreeNode<Set<String>> resultTree = new TreeNodeImpl<Set<String>>(
+        null, queryInfo.getProperties(), queryTree.getName());
+
+    copyPropertiesToResult(queryTree, resultTree);
+
+    m_rootType     = queryTree.getObject().getResource().getType();
+    m_isCollection = isCollection;
+
+    boolean addKeysToEmptyResource = true;
+    if (! isCollection && isRequestWithNoProperties(queryTree)) {
+      addSubResources(queryTree, resultTree);
+      addKeysToEmptyResource = false;
+    }
+    ensureRequiredProperties(resultTree, addKeysToEmptyResource);
+    processRequestedProperties(queryTree);
+
+    return resultTree;
+  }
+
+  @Override
+  public Result finalizeResult(Result queryResult) {
+    // can't just return result, need to strip added properties.
+    processResultNode(queryResult.getResultTree());
+    return queryResult;
+  }
+
+  @Override
+  public ResultPostProcessor getResultPostProcessor(Request request) {
+    return new MinimalPostProcessor(request);
+  }
+
+  // ----- BaseRenderer ------------------------------------------------------
+
+  @Override
+  protected void addKeys(Resource.Type resourceType, Set<String> properties) {
+    // override to only add pk instead of pk and all fk's
+    addPrimaryKey(resourceType, properties);
+  }
+
+  // ----- private instance methods ------------------------------------------
+
+  /**
+   * Recursively save all requested properties to check the result
+   * properties against.
+   *
+   * @param queryTree  query tree to process
+   */
+  private void processRequestedProperties(TreeNode<QueryInfo> queryTree) {
+    QueryInfo queryInfo = queryTree.getObject();
+    if (queryInfo != null) {
+      Resource.Type type = queryInfo.getResource().getType();
+      Set<String> properties = m_originalProperties.get(type);
+      if (properties == null) {
+        properties = new HashSet<String>();
+        m_originalProperties.put(type, properties);
+      }
+      properties.addAll(queryInfo.getProperties());
+      for (TreeNode<QueryInfo> child : queryTree.getChildren()) {
+        processRequestedProperties(child);
+      }
+    }
+  }
+
+  /**
+   * Recursively strip all unwanted properties from the result nodes.
+   * During normal processing, foreign keys are always added to the request which need
+   * to be stripped unless they were requested.
+   *
+   * @param node  node to process for extra properties
+   */
+  private void processResultNode(TreeNode<Resource> node) {
+    Resource resource = node.getObject();
+    if (resource != null && ( resource.getType() != m_rootType || m_isCollection)) {
+      Resource.Type type = resource.getType();
+      Set<String> requestedProperties = m_originalProperties.get(type);
+      Map<String, Map<String, Object>> properties = resource.getPropertiesMap();
+
+      Iterator<Map.Entry<String, Map<String, Object>>> iter;
+      for(iter = properties.entrySet().iterator(); iter.hasNext(); ) {
+        Map.Entry<String, Map<String, Object>> entry = iter.next();
+        String categoryName = entry.getKey();
+        Iterator<String> valueIter;
+
+        for(valueIter = entry.getValue().keySet().iterator(); valueIter.hasNext(); ) {
+          String propName = valueIter.next();
+          // if property was not requested and it is not a pk, remove
+          String absPropertyName = PropertyHelper.getPropertyId(categoryName, propName);
+          if ((requestedProperties == null ||
+              (! requestedProperties.contains(absPropertyName) &&
+                  ! requestedProperties.contains(categoryName))) &&
+              ! getPrimaryKeys(type).contains(absPropertyName)) {
+            valueIter.remove();
+          }
+        }
+        if (entry.getValue().isEmpty()) {
+          iter.remove();
+        }
+      }
+    }
+    for (TreeNode<Resource> child : node.getChildren()) {
+      processResultNode(child);
+    }
+  }
+
+  /**
+   * Obtain the primary keys for the specified type.
+   * This method is necessary because some resource types, specifically
+   * the configuration type, don't have a proper pk even though one is
+   * registered.  Instead, multiple properties are used as a 'composite'
+   * key even though this is not supported by the framework.
+   *
+   * @param type  resource type
+   *
+   * @return set of pk's for a type
+   */
+  private Set<String> getPrimaryKeys(Resource.Type type) {
+    Set<String> primaryKeys = new HashSet<String>();
+
+    if (type == Resource.Type.Configuration) {
+      primaryKeys.add("type");
+      primaryKeys.add("tag");
+    } else {
+      Map<Resource.Type, String> keys = PropertyHelper.getKeyPropertyIds(type);
+      if (keys != null) {
+        String pk = PropertyHelper.getKeyPropertyIds(type).get(type);
+        if (pk != null) {
+          primaryKeys = Collections.singleton(pk);
+        }
+      }
+    }
+    return primaryKeys;
+  }
+
+  // ----- inner classes -----------------------------------------------------
+
+  /**
+   * Post processor which doesn't generate href properties in the result tree.
+   */
+  private static class MinimalPostProcessor extends ResultPostProcessorImpl {
+    private MinimalPostProcessor(Request request) {
+      super(request);
+    }
+
+    @Override
+    protected void finalizeNode(TreeNode<Resource> node) {
+      node.removeProperty("href");
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/Renderer.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/Renderer.java b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/Renderer.java
new file mode 100644
index 0000000..f353d53
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/Renderer.java
@@ -0,0 +1,83 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.api.query.render;
+
+
+import org.apache.ambari.server.api.query.QueryInfo;
+import org.apache.ambari.server.api.services.Request;
+import org.apache.ambari.server.api.services.Result;
+import org.apache.ambari.server.api.services.ResultPostProcessor;
+import org.apache.ambari.server.api.util.TreeNode;
+import org.apache.ambari.server.controller.spi.SchemaFactory;
+
+import java.util.Set;
+
+/**
+ * Responsible for the rendering of a result.
+ * This includes both the content (which properties), and the format
+ * of the query result.  Format doesn't refer to json or xml, but
+ * instead to the structure of the categories, sub-resources and
+ * properties.  Renderer's are registered for a resource type by
+ * adding them to the corresponding resource definition.
+ */
+public interface Renderer {
+
+  /**
+   * Set a schema factory on the renderer.
+   *
+   * @param schemaFactory  factory of schema instances
+   */
+  public void init(SchemaFactory schemaFactory);
+
+  /**
+   * Finalize which properties are requested by the query.
+   * This is called once per user query regardless of
+   * how many sub-queries the original query is decomposed
+   * into.
+   *
+   * @param queryProperties  tree of query information.  Contains query information
+   *                         for the root query and all sub-queries (children)
+   * @param isCollection     whether the query is a collection
+   *
+   * @return tree of sets of string properties for each query including any sub-queries
+   */
+  public TreeNode<Set<String>> finalizeProperties(
+      TreeNode<QueryInfo> queryProperties, boolean isCollection);
+
+  /**
+   * Finalize the query results.
+   *
+   * @param queryResult result of query in native (default) format
+   *
+   * @return result in the format dictated by the renderer
+   */
+  public Result finalizeResult(Result queryResult);
+
+  /**
+   * Obtain the associated post processor.
+   * Post Processors existed prior to renderer's to allow the native result
+   * to be augmented before returning it to the user.  This functionality should
+   * be merged into the renderer.
+   *
+   * @param request  original request
+   *
+   * @return associated post processor
+   */
+  public ResultPostProcessor getResultPostProcessor(Request request);
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/BaseResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/BaseResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/BaseResourceDefinition.java
index 85ca8e5..1db8518 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/BaseResourceDefinition.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/BaseResourceDefinition.java
@@ -19,6 +19,9 @@
 package org.apache.ambari.server.api.resources;
 
 
+import org.apache.ambari.server.api.query.render.DefaultRenderer;
+import org.apache.ambari.server.api.query.render.MinimalRenderer;
+import org.apache.ambari.server.api.query.render.Renderer;
 import org.apache.ambari.server.api.services.Request;
 import org.apache.ambari.server.api.util.TreeNode;
 import org.apache.ambari.server.controller.spi.ClusterController;
@@ -69,6 +72,18 @@ public abstract class BaseResourceDefinition implements ResourceDefinition {
     return listProcessors;
   }
 
+  @Override
+  public Renderer getRenderer(String name) {
+    if (name == null || name.equals("default")) {
+      return new DefaultRenderer();
+    } else if (name.equals("minimal")) {
+      return new MinimalRenderer();
+    } else {
+      throw new IllegalArgumentException("Invalid renderer name: " + name +
+          " for resource of type: " + m_type);
+    }
+  }
+
   ClusterController getClusterController() {
     return ClusterControllerHelper.getClusterController();
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ClusterResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ClusterResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ClusterResourceDefinition.java
index 4b0e8e1..43578c6 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ClusterResourceDefinition.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ClusterResourceDefinition.java
@@ -20,14 +20,15 @@ package org.apache.ambari.server.api.resources;
 
 import java.util.HashSet;
 import java.util.Set;
+
+import org.apache.ambari.server.api.query.render.ClusterBlueprintRenderer;
+import org.apache.ambari.server.api.query.render.Renderer;
 import org.apache.ambari.server.controller.spi.Resource;
 
 /**
  * Cluster resource definition.
  */
 public class ClusterResourceDefinition extends BaseResourceDefinition {
-
-
   /**
    * Constructor.
    */
@@ -47,6 +48,15 @@ public class ClusterResourceDefinition extends BaseResourceDefinition {
   }
 
   @Override
+  public Renderer getRenderer(String name) {
+    if (name != null && name.equals("blueprint")) {
+      return new ClusterBlueprintRenderer();
+    } else {
+      return super.getRenderer(name);
+    }
+  }
+
+  @Override
   public Set<SubResourceDefinition> getSubResourceDefinitions() {
     Set<SubResourceDefinition> setChildren = new HashSet<SubResourceDefinition>();
     setChildren.add(new SubResourceDefinition(Resource.Type.Service));

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceDefinition.java
index ba69869..6a169b1 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceDefinition.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceDefinition.java
@@ -18,6 +18,7 @@
 
 package org.apache.ambari.server.api.resources;
 
+import org.apache.ambari.server.api.query.render.Renderer;
 import org.apache.ambari.server.api.services.Request;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.api.util.TreeNode;
@@ -69,6 +70,16 @@ public interface ResourceDefinition {
   public List<PostProcessor> getPostProcessors();
 
   /**
+   * Obtain the associated renderer based on name.
+   *
+   * @param name  name of the renderer to obtain
+   *
+   * @return associated renderer instance
+   * @throws IllegalArgumentException if name is invalid for this resource
+   */
+  public Renderer getRenderer(String name) throws IllegalArgumentException;
+
+  /**
    * Resource specific result processor.
    * Used to provide resource specific processing of a result.
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseRequest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseRequest.java
index ed7bc45..6d9c13b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseRequest.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseRequest.java
@@ -22,6 +22,7 @@ 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.query.render.Renderer;
 import org.apache.ambari.server.api.resources.*;
 import org.apache.ambari.server.controller.internal.PageRequestImpl;
 import org.apache.ambari.server.controller.internal.TemporalInfoImpl;
@@ -75,6 +76,12 @@ public abstract class BaseRequest implements Request {
   private static final int DEFAULT_PAGE_SIZE = 20;
 
   /**
+   * Associated resource renderer.
+   * Will default to the default renderer if non is specified.
+   */
+  private Renderer m_renderer;
+
+  /**
    *  Logger instance.
    */
   private final static Logger LOG = LoggerFactory.getLogger(Request.class);
@@ -104,11 +111,15 @@ public abstract class BaseRequest implements Request {
 
     Result result;
     try {
+      parseRenderer();
       parseQueryPredicate();
       result = getRequestHandler().handleRequest(this);
     } catch (InvalidQueryException e) {
       result =  new ResultImpl(new ResultStatus(ResultStatus.STATUS.BAD_REQUEST,
           "Unable to compile query predicate: " + e.getMessage()));
+    } catch (IllegalArgumentException e) {
+      result =  new ResultImpl(new ResultStatus(ResultStatus.STATUS.BAD_REQUEST,
+          "Invalid Request: " + e.getMessage()));
     }
 
     if (! result.getStatus().isErrorState()) {
@@ -186,6 +197,11 @@ public abstract class BaseRequest implements Request {
   }
 
   @Override
+  public Renderer getRenderer() {
+   return m_renderer;
+  }
+
+  @Override
   public Map<String, List<String>> getHttpHeaders() {
     return m_headers.getRequestHeaders();
   }
@@ -229,12 +245,6 @@ public abstract class BaseRequest implements Request {
   }
 
   @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;
   }
@@ -245,8 +255,7 @@ public abstract class BaseRequest implements Request {
    * @return the result post processor
    */
   protected ResultPostProcessor getResultPostProcessor() {
-    //todo: inject
-    return new ResultPostProcessorImpl(this);
+    return m_renderer.getResultPostProcessor(this);
   }
 
   /**
@@ -260,6 +269,16 @@ public abstract class BaseRequest implements Request {
   }
 
   /**
+   * Check to see if 'minimal_response=true' is specified in the query string.
+   *
+   * @return true if 'minimal_response=true' is specified, false otherwise
+   */
+  private boolean isMinimal() {
+    String minimal = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_MINIMAL);
+    return minimal != null && minimal.equalsIgnoreCase("true");
+  }
+
+  /**
    * Parse the query string and compile it into a predicate.
    * The query string may have already been extracted from the http body.
    * If the query string didn't exist in the body use the query string in the URL.
@@ -281,6 +300,17 @@ public abstract class BaseRequest implements Request {
   }
 
   /**
+   * Parse the query string for the {@link QueryLexer#QUERY_FORMAT} property and obtain
+   * a renderer from the associated resource definition based on this property value.
+   */
+  private void parseRenderer() {
+    String rendererName = isMinimal() ? "minimal" :
+        m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_FORMAT);
+    m_renderer = m_resource.getResourceDefinition().
+        getRenderer(rendererName);
+  }
+
+  /**
    * Obtain the underlying request handler for the request.
    *
    * @return  the request handler

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseService.java
index 8bf7836..8953796 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseService.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/BaseService.java
@@ -66,9 +66,7 @@ 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));
-    boolean minimal = false;
-
+    Result result = new ResultImpl(new ResultStatus(ResultStatus.STATUS.OK));
     try {
       Set<RequestBody> requestBodySet = getBodyParser().parse(body);
 
@@ -79,7 +77,6 @@ public abstract class BaseService {
         Request request = getRequestFactory().createRequest(
             headers, requestBody, uriInfo, requestType, resource);
 
-        minimal = request.isMinimal();
         result  = request.process();
       }
     } catch (BodyParseException e) {
@@ -87,7 +84,7 @@ public abstract class BaseService {
     }
 
     return Response.status(result.getStatus().getStatusCode()).entity(
-        getResultSerializer().serialize(result, minimal)).build();
+        getResultSerializer().serialize(result)).build();
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/services/Request.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/Request.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/Request.java
index f2de36e..bb53cf6 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/Request.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/Request.java
@@ -18,6 +18,7 @@
 
 package org.apache.ambari.server.api.services;
 
+import org.apache.ambari.server.api.query.render.Renderer;
 import org.apache.ambari.server.api.resources.ResourceDefinition;
 import org.apache.ambari.server.api.resources.ResourceInstance;
 import org.apache.ambari.server.controller.spi.PageRequest;
@@ -116,9 +117,10 @@ public interface Request {
   public PageRequest getPageRequest();
 
   /**
-   * Is the minimal response parameter specified as true.
+   * Obtain the renderer for the request.
    *
-   * @return true if the minimal response parameter is specified as true
+   * @return renderer instance
    */
-  public boolean isMinimal();
+  public Renderer getRenderer();
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ResultPostProcessorImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ResultPostProcessorImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ResultPostProcessorImpl.java
index c02e0a2..bfb1e57 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ResultPostProcessorImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ResultPostProcessorImpl.java
@@ -91,6 +91,17 @@ public class ResultPostProcessorImpl implements ResultPostProcessor {
     for (TreeNode<Resource> child : node.getChildren()) {
       processNode(child, href);
     }
+
+    finalizeNode(node);
+  }
+
+  /**
+   * Allows subclasses to finalize node
+   *
+   * @param node  node to finalize
+   */
+  protected void finalizeNode(TreeNode<Resource> node) {
+    // no-op
   }
 
   /**
@@ -119,5 +130,4 @@ public class ResultPostProcessorImpl implements ResultPostProcessor {
     // always add Request post processors since they may be returned but will not be a child
     m_mapPostProcessors.put(Resource.Type.Request, new RequestResourceDefinition().getPostProcessors());
   }
-
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/JsonSerializer.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/JsonSerializer.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/JsonSerializer.java
index 15b2f47..35a9856 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/JsonSerializer.java
+++ b/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, boolean minimal) {
+  public Object serialize(Result result) {
     try {
       ByteArrayOutputStream bytesOut = init();
 
@@ -61,7 +61,7 @@ public class JsonSerializer implements ResultSerializer {
         return serializeError(result.getStatus());
       }
 
-      processNode(result.getResultTree(), minimal);
+      processNode(result.getResultTree());
 
       m_generator.close();
       return bytesOut.toString("UTF-8");
@@ -100,13 +100,11 @@ public class JsonSerializer implements ResultSerializer {
     return bytesOut;
   }
 
-  private void processNode(TreeNode<Resource> node, boolean minimal) throws IOException {
+  private void processNode(TreeNode<Resource> node) throws IOException {
     if (isObject(node)) {
       m_generator.writeStartObject();
 
-      if (!minimal) {
-        writeHref(node);
-      }
+      writeHref(node);
 
       Resource r = node.getObject();
       if (r != null) {
@@ -118,7 +116,7 @@ public class JsonSerializer implements ResultSerializer {
     }
 
     for (TreeNode<Resource> child : node.getChildren()) {
-      processNode(child, minimal);
+      processNode(child);
     }
 
     if (isArray(node)) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/ResultSerializer.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/ResultSerializer.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/ResultSerializer.java
index 22b8c88..53b3b80 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/ResultSerializer.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/ResultSerializer.java
@@ -30,11 +30,10 @@ 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, boolean minimal);
+  Object serialize(Result result);
 
   /**
    * Serialize an error result to the format expected by the client.

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/util/TreeNode.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/util/TreeNode.java b/ambari-server/src/main/java/org/apache/ambari/server/api/util/TreeNode.java
index ffb41fa..3f8abdd 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/util/TreeNode.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/util/TreeNode.java
@@ -100,6 +100,13 @@ public interface TreeNode<T> {
   public String getProperty(String name);
 
   /**
+   * Remove a property from the node.
+   *
+   * @param name  name of property to be removed
+   */
+  public void removeProperty(String name);
+
+  /**
    * Find a child node by name.
    * The name may contain '/' to delimit names to find a child more then one level deep.
    * To find a node named 'bar' that is a child of a child named 'foo', use the name 'foo/bar'.

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/api/util/TreeNodeImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/util/TreeNodeImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/api/util/TreeNodeImpl.java
index da7ead4..7c90715 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/util/TreeNodeImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/util/TreeNodeImpl.java
@@ -125,6 +125,13 @@ public class TreeNodeImpl<T> implements TreeNode<T> {
   }
 
   @Override
+  public void removeProperty(String name) {
+    if (m_mapNodeProps != null) {
+      m_mapNodeProps.remove(name);
+    }
+  }
+
+  @Override
   public TreeNode<T> getChild(String name) {
     if (name != null && name.contains("/")) {
       int i = name.indexOf('/');