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:16 UTC

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

Repository: ambari
Updated Branches:
  refs/heads/trunk 62aa47bef -> 7eb4bc6b9


http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/query/render/MinimalRendererTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/query/render/MinimalRendererTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/query/render/MinimalRendererTest.java
new file mode 100644
index 0000000..16cbb2a
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/query/render/MinimalRendererTest.java
@@ -0,0 +1,559 @@
+/**
+ * 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.ClusterResourceDefinition;
+import org.apache.ambari.server.api.resources.ComponentResourceDefinition;
+import org.apache.ambari.server.api.resources.HostResourceDefinition;
+import org.apache.ambari.server.api.resources.ServiceResourceDefinition;
+import org.apache.ambari.server.api.services.Result;
+import org.apache.ambari.server.api.services.ResultImpl;
+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.spi.Schema;
+import org.apache.ambari.server.controller.spi.SchemaFactory;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * MinimalRenderer unit tests.
+ */
+public class MinimalRendererTest {
+  @Test
+  public void testFinalizeProperties__instance_noProperties() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema schema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Component)).andReturn(schema).anyTimes();
+    expect(schema.getKeyPropertyId(Resource.Type.Component)).andReturn("ServiceComponentInfo/component_name").anyTimes();
+
+    replay(schemaFactory, schema);
+
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), new HashSet<String>());
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+
+    MinimalRenderer renderer = new MinimalRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, false);
+    // no properties should have been added
+    assertTrue(propertyTree.getObject().isEmpty());
+    assertEquals(1, propertyTree.getChildren().size());
+
+    TreeNode<Set<String>> componentNode = propertyTree.getChild("Component");
+    assertEquals(1, componentNode.getObject().size());
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/component_name"));
+
+    verify(schemaFactory, schema);
+  }
+
+  @Test
+  public void testFinalizeProperties__instance_properties() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema schema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Service)).andReturn(schema).anyTimes();
+    expect(schema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceInfo/service_name").anyTimes();
+
+    replay(schemaFactory, schema);
+
+    HashSet<String> serviceProperties = new HashSet<String>();
+    serviceProperties.add("foo/bar");
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), serviceProperties);
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+
+    MinimalRenderer renderer = new MinimalRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, false);
+
+    assertEquals(2, propertyTree.getObject().size());
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/service_name"));
+    assertTrue(propertyTree.getObject().contains("foo/bar"));
+
+    assertEquals(0, propertyTree.getChildren().size());
+
+    verify(schemaFactory, schema);
+  }
+
+  @Test
+  public void testFinalizeProperties__collection_noProperties() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema schema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Service)).andReturn(schema).anyTimes();
+    expect(schema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceInfo/service_name").anyTimes();
+
+    replay(schemaFactory, schema);
+
+    HashSet<String> serviceProperties = new HashSet<String>();
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), serviceProperties);
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+
+    MinimalRenderer renderer = new MinimalRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, true);
+
+    assertEquals(1, propertyTree.getObject().size());
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/service_name"));
+
+    assertEquals(0, propertyTree.getChildren().size());
+
+    verify(schemaFactory, schema);
+  }
+
+  @Test
+  public void testFinalizeProperties__collection_properties() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema schema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Service)).andReturn(schema).anyTimes();
+    expect(schema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceInfo/service_name").anyTimes();
+
+    replay(schemaFactory, schema);
+
+    HashSet<String> serviceProperties = new HashSet<String>();
+    serviceProperties.add("foo/bar");
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), serviceProperties);
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+
+    MinimalRenderer renderer = new MinimalRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, true);
+
+    assertEquals(2, propertyTree.getObject().size());
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/service_name"));
+    assertTrue(propertyTree.getObject().contains("foo/bar"));
+
+    assertEquals(0, propertyTree.getChildren().size());
+
+    verify(schemaFactory, schema);
+  }
+
+  @Test
+  public void testFinalizeProperties__instance_subResource_noProperties() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema serviceSchema = createNiceMock(Schema.class);
+    Schema componentSchema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Service)).andReturn(serviceSchema).anyTimes();
+    expect(serviceSchema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceInfo/service_name").anyTimes();
+    expect(schemaFactory.getSchema(Resource.Type.Component)).andReturn(componentSchema).anyTimes();
+    expect(componentSchema.getKeyPropertyId(Resource.Type.Component)).andReturn("ServiceComponentInfo/component_name").anyTimes();
+
+    replay(schemaFactory, serviceSchema, componentSchema);
+
+    HashSet<String> serviceProperties = new HashSet<String>();
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), serviceProperties);
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+    queryTree.addChild(new QueryInfo(new ComponentResourceDefinition(), new HashSet<String>()), "Component");
+
+    MinimalRenderer renderer = new MinimalRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, false);
+
+    assertEquals(1, propertyTree.getChildren().size());
+    assertEquals(1, propertyTree.getObject().size());
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/service_name"));
+
+
+    TreeNode<Set<String>> componentNode = propertyTree.getChild("Component");
+    assertEquals(0, componentNode.getChildren().size());
+    assertEquals(1, componentNode.getObject().size());
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/component_name"));
+
+
+    verify(schemaFactory, serviceSchema, componentSchema);
+  }
+
+  @Test
+  public void testFinalizeProperties__instance_subResource_properties() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema serviceSchema = createNiceMock(Schema.class);
+    Schema componentSchema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Service)).andReturn(serviceSchema).anyTimes();
+    expect(serviceSchema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceInfo/service_name").anyTimes();
+    expect(schemaFactory.getSchema(Resource.Type.Component)).andReturn(componentSchema).anyTimes();
+    expect(componentSchema.getKeyPropertyId(Resource.Type.Component)).andReturn("ServiceComponentInfo/component_name").anyTimes();
+
+    replay(schemaFactory, serviceSchema, componentSchema);
+
+    HashSet<String> serviceProperties = new HashSet<String>();
+    serviceProperties.add("foo/bar");
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), serviceProperties);
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+    HashSet<String> componentProperties = new HashSet<String>();
+    componentProperties.add("goo/car");
+    queryTree.addChild(new QueryInfo(new ComponentResourceDefinition(), componentProperties), "Component");
+
+    MinimalRenderer renderer = new MinimalRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, false);
+
+    assertEquals(1, propertyTree.getChildren().size());
+    assertEquals(2, propertyTree.getObject().size());
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/service_name"));
+    assertTrue(propertyTree.getObject().contains("foo/bar"));
+
+    TreeNode<Set<String>> componentNode = propertyTree.getChild("Component");
+    assertEquals(0, componentNode.getChildren().size());
+    assertEquals(2, componentNode.getObject().size());
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/component_name"));
+    assertTrue(componentNode.getObject().contains("goo/car"));
+
+    verify(schemaFactory, serviceSchema, componentSchema);
+  }
+
+  @Test
+  public void testFinalizeProperties__collection_subResource_noProperties() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema serviceSchema = createNiceMock(Schema.class);
+    Schema componentSchema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Service)).andReturn(serviceSchema).anyTimes();
+    expect(serviceSchema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceInfo/service_name").anyTimes();
+    expect(schemaFactory.getSchema(Resource.Type.Component)).andReturn(componentSchema).anyTimes();
+    expect(componentSchema.getKeyPropertyId(Resource.Type.Component)).andReturn("ServiceComponentInfo/component_name").anyTimes();
+
+    replay(schemaFactory, serviceSchema, componentSchema);
+
+    HashSet<String> serviceProperties = new HashSet<String>();
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), serviceProperties);
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+    queryTree.addChild(new QueryInfo(new ComponentResourceDefinition(), new HashSet<String>()), "Component");
+
+    MinimalRenderer renderer = new MinimalRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, true);
+
+    assertEquals(1, propertyTree.getChildren().size());
+    assertEquals(1, propertyTree.getObject().size());
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/service_name"));
+
+
+    TreeNode<Set<String>> componentNode = propertyTree.getChild("Component");
+    assertEquals(0, componentNode.getChildren().size());
+    assertEquals(1, componentNode.getObject().size());
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/component_name"));
+
+    verify(schemaFactory, serviceSchema, componentSchema);
+  }
+
+  @Test
+  public void testFinalizeProperties__collection_subResource_propertiesTopLevelOnly() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema serviceSchema = createNiceMock(Schema.class);
+    Schema componentSchema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Service)).andReturn(serviceSchema).anyTimes();
+    expect(serviceSchema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceInfo/service_name").anyTimes();
+    expect(schemaFactory.getSchema(Resource.Type.Component)).andReturn(componentSchema).anyTimes();
+    expect(componentSchema.getKeyPropertyId(Resource.Type.Component)).andReturn("ServiceComponentInfo/component_name").anyTimes();
+
+    replay(schemaFactory, serviceSchema, componentSchema);
+
+    HashSet<String> serviceProperties = new HashSet<String>();
+    serviceProperties.add("foo/bar");
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), serviceProperties);
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+    queryTree.addChild(new QueryInfo(new ComponentResourceDefinition(), new HashSet<String>()), "Component");
+
+    MinimalRenderer renderer = new MinimalRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, true);
+
+    assertEquals(1, propertyTree.getChildren().size());
+    assertEquals(2, propertyTree.getObject().size());
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/service_name"));
+    assertTrue(propertyTree.getObject().contains("foo/bar"));
+
+
+    TreeNode<Set<String>> componentNode = propertyTree.getChild("Component");
+    assertEquals(0, componentNode.getChildren().size());
+    assertEquals(1, componentNode.getObject().size());
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/component_name"));
+
+    verify(schemaFactory, serviceSchema, componentSchema);
+  }
+
+  @Test
+  public void testFinalizeResult() throws Exception {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema clusterSchema = createNiceMock(Schema.class);
+    Schema hostSchema = createNiceMock(Schema.class);
+    Schema hostComponentSchema = createNiceMock(Schema.class);
+
+    // mock expectations
+    expect(schemaFactory.getSchema(Resource.Type.Cluster)).andReturn(clusterSchema).anyTimes();
+    expect(schemaFactory.getSchema(Resource.Type.Host)).andReturn(hostSchema).anyTimes();
+    expect(schemaFactory.getSchema(Resource.Type.HostComponent)).andReturn(hostComponentSchema).anyTimes();
+
+    expect(clusterSchema.getKeyPropertyId(Resource.Type.Cluster)).andReturn("Clusters/cluster_name").anyTimes();
+
+    expect(hostSchema.getKeyPropertyId(Resource.Type.Cluster)).andReturn("Hosts/cluster_name").anyTimes();
+    expect(hostSchema.getKeyPropertyId(Resource.Type.Host)).andReturn("Hosts/host_name").anyTimes();
+
+    expect(hostComponentSchema.getKeyPropertyId(Resource.Type.Cluster)).andReturn("HostRoles/cluster_name").anyTimes();
+    expect(hostComponentSchema.getKeyPropertyId(Resource.Type.Host)).andReturn("HostRoles/host_name").anyTimes();
+    expect(hostComponentSchema.getKeyPropertyId(Resource.Type.HostComponent)).andReturn("HostRoles/component_name").anyTimes();
+
+    replay(schemaFactory, clusterSchema, hostSchema, hostComponentSchema);
+
+    Result result = new ResultImpl(true);
+    createResultTree(result.getResultTree());
+
+    MinimalRenderer renderer = new MinimalRenderer();
+    renderer.init(schemaFactory);
+    //call finalizeProperties so that renderer know which properties are requested by user
+    renderer.finalizeProperties(createPropertyTree(), false);
+
+    TreeNode<Resource> resultTree = renderer.finalizeResult(result).getResultTree();
+    assertNull(resultTree.getProperty("isCollection"));
+    assertEquals(1, resultTree.getChildren().size());
+
+    TreeNode<Resource> clusterNode = resultTree.getChildren().iterator().next();
+    Resource clusterResource = clusterNode.getObject();
+    Map<String, Map<String, Object>> clusterProperties = clusterResource.getPropertiesMap();
+    assertEquals(2, clusterProperties.size());
+
+    assertEquals(3, clusterProperties.get("Clusters").size());
+    assertEquals("testCluster", clusterProperties.get("Clusters").get("cluster_name"));
+    assertEquals("HDP-1.3.3", clusterProperties.get("Clusters").get("version"));
+    assertEquals("value1", clusterProperties.get("Clusters").get("prop1"));
+
+    assertEquals(1, clusterProperties.get("").size());
+    assertEquals("bar", clusterProperties.get("").get("foo"));
+
+    TreeNode<Resource> hosts = clusterNode.getChildren().iterator().next();
+    for (TreeNode<Resource> hostNode : hosts.getChildren()){
+      Resource hostResource = hostNode.getObject();
+      Map<String, Map<String, Object>> hostProperties = hostResource.getPropertiesMap();
+      assertEquals(1, hostProperties.size());
+      assertEquals(1, hostProperties.get("Hosts").size());
+      assertTrue(hostProperties.get("Hosts").containsKey("host_name"));
+
+      for (TreeNode<Resource> componentNode : hostNode.getChildren().iterator().next().getChildren()) {
+        Resource componentResource = componentNode.getObject();
+        Map<String, Map<String, Object>> componentProperties = componentResource.getPropertiesMap();
+        assertEquals(1, componentProperties.size());
+        assertEquals(1, componentProperties.get("HostRoles").size());
+        assertTrue(componentProperties.get("HostRoles").containsKey("component_name"));
+      }
+    }
+  }
+
+  @Test
+  public void testFinalizeResult_propsSetOnSubResource() throws Exception {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema clusterSchema = createNiceMock(Schema.class);
+    Schema hostSchema = createNiceMock(Schema.class);
+    Schema hostComponentSchema = createNiceMock(Schema.class);
+
+    // mock expectations
+    expect(schemaFactory.getSchema(Resource.Type.Cluster)).andReturn(clusterSchema).anyTimes();
+    expect(schemaFactory.getSchema(Resource.Type.Host)).andReturn(hostSchema).anyTimes();
+    expect(schemaFactory.getSchema(Resource.Type.HostComponent)).andReturn(hostComponentSchema).anyTimes();
+
+    expect(clusterSchema.getKeyPropertyId(Resource.Type.Cluster)).andReturn("Clusters/cluster_name").anyTimes();
+
+    expect(hostSchema.getKeyPropertyId(Resource.Type.Cluster)).andReturn("Hosts/cluster_name").anyTimes();
+    expect(hostSchema.getKeyPropertyId(Resource.Type.Host)).andReturn("Hosts/host_name").anyTimes();
+
+    expect(hostComponentSchema.getKeyPropertyId(Resource.Type.Cluster)).andReturn("HostRoles/cluster_name").anyTimes();
+    expect(hostComponentSchema.getKeyPropertyId(Resource.Type.Host)).andReturn("HostRoles/host_name").anyTimes();
+    expect(hostComponentSchema.getKeyPropertyId(Resource.Type.HostComponent)).andReturn("HostRoles/component_name").anyTimes();
+
+    replay(schemaFactory, clusterSchema, hostSchema, hostComponentSchema);
+
+    Result result = new ResultImpl(true);
+    createResultTree(result.getResultTree());
+
+    MinimalRenderer renderer = new MinimalRenderer();
+    renderer.init(schemaFactory);
+    //call finalizeProperties so that renderer know which properties are requested by user
+    renderer.finalizeProperties(createPropertyTreeWithSubProps(), false);
+
+    TreeNode<Resource> resultTree = renderer.finalizeResult(result).getResultTree();
+    assertNull(resultTree.getProperty("isCollection"));
+    assertEquals(1, resultTree.getChildren().size());
+
+    TreeNode<Resource> clusterNode = resultTree.getChildren().iterator().next();
+    Resource clusterResource = clusterNode.getObject();
+    Map<String, Map<String, Object>> clusterProperties = clusterResource.getPropertiesMap();
+    assertEquals(2, clusterProperties.size());
+
+    assertEquals(3, clusterProperties.get("Clusters").size());
+    assertEquals("testCluster", clusterProperties.get("Clusters").get("cluster_name"));
+    assertEquals("HDP-1.3.3", clusterProperties.get("Clusters").get("version"));
+    assertEquals("value1", clusterProperties.get("Clusters").get("prop1"));
+
+    assertEquals(1, clusterProperties.get("").size());
+    assertEquals("bar", clusterProperties.get("").get("foo"));
+
+    TreeNode<Resource> hosts = clusterNode.getChildren().iterator().next();
+    for (TreeNode<Resource> hostNode : hosts.getChildren()){
+      Resource hostResource = hostNode.getObject();
+      Map<String, Map<String, Object>> hostProperties = hostResource.getPropertiesMap();
+      assertEquals(2, hostProperties.size());
+      assertEquals(1, hostProperties.get("Hosts").size());
+      assertTrue(hostProperties.get("Hosts").containsKey("host_name"));
+      assertEquals(1, hostProperties.get("").size());
+      assertEquals("bar", hostProperties.get("").get("foo"));
+
+      for (TreeNode<Resource> componentNode : hostNode.getChildren().iterator().next().getChildren()) {
+        Resource componentResource = componentNode.getObject();
+        Map<String, Map<String, Object>> componentProperties = componentResource.getPropertiesMap();
+        assertEquals(1, componentProperties.size());
+        assertEquals(1, componentProperties.get("HostRoles").size());
+        assertTrue(componentProperties.get("HostRoles").containsKey("component_name"));
+      }
+    }
+  }
+
+  //todo: test post processing to ensure href removal
+  //todo: Need to do some refactoring to do this.
+  //todo: BaseResourceDefinition.BaseHrefPostProcessor calls static ClusterControllerHelper.getClusterController().
+
+
+  private TreeNode<QueryInfo> createPropertyTree() {
+    TreeNode<QueryInfo> propertyTree = new TreeNodeImpl<QueryInfo>(null, new QueryInfo(
+        new ClusterResourceDefinition(), new HashSet<String>()), "Cluster");
+    Set<String> clusterProperties = propertyTree.getObject().getProperties();
+    clusterProperties.add("Clusters/cluster_name");
+    clusterProperties.add("Clusters/version");
+    clusterProperties.add("Clusters/prop1");
+    clusterProperties.add("foo");
+
+    return propertyTree;
+  }
+
+  private TreeNode<QueryInfo> createPropertyTreeWithSubProps() {
+    TreeNode<QueryInfo> propertyTree = new TreeNodeImpl<QueryInfo>(null, new QueryInfo(
+        new ClusterResourceDefinition(), new HashSet<String>()), "Cluster");
+    Set<String> clusterProperties = propertyTree.getObject().getProperties();
+    clusterProperties.add("Clusters/cluster_name");
+    clusterProperties.add("Clusters/version");
+    clusterProperties.add("Clusters/prop1");
+    clusterProperties.add("foo");
+
+    propertyTree.addChild(new QueryInfo(new HostResourceDefinition(), new HashSet<String>()), "Host");
+    propertyTree.getChild("Host").getObject().getProperties().add("foo");
+
+    return propertyTree;
+  }
+
+  private void createResultTree(TreeNode<Resource> resultTree) throws Exception{
+    Resource clusterResource = new ResourceImpl(Resource.Type.Cluster);
+    clusterResource.setProperty("Clusters/cluster_name", "testCluster");
+    clusterResource.setProperty("Clusters/version", "HDP-1.3.3");
+    clusterResource.setProperty("Clusters/prop1", "value1");
+    clusterResource.setProperty("foo", "bar");
+
+    TreeNode<Resource> clusterTree = resultTree.addChild(clusterResource, "Cluster:1");
+
+    TreeNode<Resource> hostsTree = clusterTree.addChild(null, "hosts");
+    hostsTree.setProperty("isCollection", "true");
+
+    // host 1 : ambari host
+    Resource hostResource = new ResourceImpl(Resource.Type.Host);
+    hostResource.setProperty("Hosts/host_name", "testHost");
+    hostResource.setProperty("Hosts/cluster_name", "testCluster");
+    hostResource.setProperty("foo", "bar");
+    TreeNode<Resource> hostTree = hostsTree.addChild(hostResource, "Host:1");
+
+    TreeNode<Resource> hostComponentsTree = hostTree.addChild(null, "host_components");
+    hostComponentsTree.setProperty("isCollection", "true");
+
+    // host 1 components
+    Resource nnComponentResource = new ResourceImpl(Resource.Type.HostComponent);
+    nnComponentResource.setProperty("HostRoles/component_name", "NAMENODE");
+    nnComponentResource.setProperty("HostRoles/host_name", "testHost");
+    nnComponentResource.setProperty("HostRoles/cluster_name", "testCluster");
+
+    Resource dnComponentResource = new ResourceImpl(Resource.Type.HostComponent);
+    dnComponentResource.setProperty("HostRoles/component_name", "DATANODE");
+    dnComponentResource.setProperty("HostRoles/host_name", "testHost");
+    dnComponentResource.setProperty("HostRoles/cluster_name", "testCluster");
+
+    Resource jtComponentResource = new ResourceImpl(Resource.Type.HostComponent);
+    jtComponentResource.setProperty("HostRoles/component_name", "JOBTRACKER");
+    jtComponentResource.setProperty("HostRoles/host_name", "testHost");
+    jtComponentResource.setProperty("HostRoles/cluster_name", "testCluster");
+
+    Resource ttComponentResource = new ResourceImpl(Resource.Type.HostComponent);
+    ttComponentResource.setProperty("HostRoles/component_name", "TASKTRACKER");
+    jtComponentResource.setProperty("HostRoles/host_name", "testHost");
+    jtComponentResource.setProperty("HostRoles/cluster_name", "testCluster");
+
+    hostComponentsTree.addChild(nnComponentResource, "HostComponent:1");
+    hostComponentsTree.addChild(dnComponentResource, "HostComponent:2");
+    hostComponentsTree.addChild(jtComponentResource, "HostComponent:3");
+    hostComponentsTree.addChild(ttComponentResource, "HostComponent:4");
+
+    // host 2
+    Resource host2Resource = new ResourceImpl(Resource.Type.Host);
+    host2Resource.setProperty("Hosts/host_name", "testHost2");
+    host2Resource.setProperty("Hosts/cluster_name", "testCluster");
+    host2Resource.setProperty("foo", "bar");
+    TreeNode<Resource> host2Tree = hostsTree.addChild(host2Resource, "Host:2");
+
+    TreeNode<Resource> host2ComponentsTree = host2Tree.addChild(null, "host_components");
+    host2ComponentsTree.setProperty("isCollection", "true");
+
+    // host 2 components
+    host2ComponentsTree.addChild(dnComponentResource, "HostComponent:1");
+    host2ComponentsTree.addChild(ttComponentResource, "HostComponent:2");
+
+    // host 3 : same topology as host 2
+    Resource host3Resource = new ResourceImpl(Resource.Type.Host);
+    host3Resource.setProperty("Hosts/host_name", "testHost3");
+    host3Resource.setProperty("Hosts/host_name", "testHost2");
+    host3Resource.setProperty("foo", "bar");
+    TreeNode<Resource> host3Tree = hostsTree.addChild(host3Resource, "Host:3");
+
+    TreeNode<Resource> host3ComponentsTree = host3Tree.addChild(null, "host_components");
+    host3ComponentsTree.setProperty("isCollection", "true");
+
+    // host 3 components
+    host3ComponentsTree.addChild(dnComponentResource, "HostComponent:1");
+    host3ComponentsTree.addChild(ttComponentResource, "HostComponent:2");
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/resources/BaseResourceDefinitionTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/resources/BaseResourceDefinitionTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/resources/BaseResourceDefinitionTest.java
index 152a30b..8d518e4 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/resources/BaseResourceDefinitionTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/resources/BaseResourceDefinitionTest.java
@@ -22,8 +22,12 @@ import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.anyObject;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import org.apache.ambari.server.api.handlers.BaseManagementHandler;
+import org.apache.ambari.server.api.query.render.DefaultRenderer;
+import org.apache.ambari.server.api.query.render.MinimalRenderer;
 import org.apache.ambari.server.api.util.TreeNode;
 import org.apache.ambari.server.api.util.TreeNodeImpl;
 import org.apache.ambari.server.controller.AmbariManagementController;
@@ -46,7 +50,6 @@ import java.util.Set;
  */
 public class BaseResourceDefinitionTest {
 
-
   @Test
   public void testGetPostProcessors() {
     BaseResourceDefinition resourceDefinition = getResourceDefinition();
@@ -103,6 +106,22 @@ public class BaseResourceDefinitionTest {
     Assert.assertEquals("http://c6401.ambari.apache.org:8080/api/v1/clusters/c1/config_groups/2", href);
   }
 
+  @Test
+  public void testGetRenderer() {
+    ResourceDefinition resource = getResourceDefinition();
+
+    assertTrue(resource.getRenderer(null) instanceof DefaultRenderer);
+    assertTrue(resource.getRenderer("default") instanceof DefaultRenderer);
+    assertTrue(resource.getRenderer("minimal") instanceof MinimalRenderer);
+
+    try {
+      resource.getRenderer("foo");
+      fail("Should have thrown an exception due to invalid renderer type");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+  }
+
   private BaseResourceDefinition getResourceDefinition() {
     return new BaseResourceDefinition(Resource.Type.Service) {
       @Override

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/resources/ClusterResourceDefinitionTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/resources/ClusterResourceDefinitionTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/resources/ClusterResourceDefinitionTest.java
new file mode 100644
index 0000000..a4ee74b
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/resources/ClusterResourceDefinitionTest.java
@@ -0,0 +1,88 @@
+/**
+ * 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.resources;
+
+import org.apache.ambari.server.api.query.render.ClusterBlueprintRenderer;
+import org.apache.ambari.server.api.query.render.DefaultRenderer;
+import org.apache.ambari.server.api.query.render.MinimalRenderer;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.junit.Test;
+
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * ClusterResourceDefinition unit tests.
+ */
+public class ClusterResourceDefinitionTest {
+
+  @Test
+  public void testGetPluralName() {
+    assertEquals("clusters", new ClusterResourceDefinition().getPluralName());
+  }
+
+  @Test
+  public void testGetSingularName() {
+    assertEquals("cluster", new ClusterResourceDefinition().getSingularName());
+  }
+
+  @Test
+  public void testGetSubResourceDefinitions() {
+    ResourceDefinition resource = new ClusterResourceDefinition();
+    Set<SubResourceDefinition> subResources = resource.getSubResourceDefinitions();
+
+    assertEquals(6, subResources.size());
+    assertTrue(includesType(subResources, Resource.Type.Service));
+    assertTrue(includesType(subResources, Resource.Type.Host));
+    assertTrue(includesType(subResources, Resource.Type.Configuration));
+    assertTrue(includesType(subResources, Resource.Type.Request));
+    assertTrue(includesType(subResources, Resource.Type.Workflow));
+    assertTrue(includesType(subResources, Resource.Type.ConfigGroup));
+  }
+
+  @Test
+  public void testGetRenderer() {
+    ResourceDefinition resource = new ClusterResourceDefinition();
+
+    assertTrue(resource.getRenderer(null) instanceof DefaultRenderer);
+    assertTrue(resource.getRenderer("default") instanceof DefaultRenderer);
+    assertTrue(resource.getRenderer("minimal") instanceof MinimalRenderer);
+    assertTrue(resource.getRenderer("blueprint") instanceof ClusterBlueprintRenderer);
+
+    try {
+      resource.getRenderer("foo");
+      fail("Should have thrown an exception due to invalid renderer type");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+  }
+
+  private boolean includesType(Set<SubResourceDefinition> resources, Resource.Type type) {
+    for (SubResourceDefinition subResource : resources) {
+      if (subResource.getType() == type) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/services/BaseRequestTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/BaseRequestTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/BaseRequestTest.java
index 9a68ec4..9cfd8c6 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/services/BaseRequestTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/BaseRequestTest.java
@@ -22,6 +22,11 @@ import com.sun.jersey.core.util.MultivaluedMapImpl;
 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.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.resources.ResourceDefinition;
 import org.apache.ambari.server.api.resources.ResourceInstance;
 import org.apache.ambari.server.controller.internal.TemporalInfoImpl;
 import org.apache.ambari.server.controller.spi.Predicate;
@@ -85,26 +90,36 @@ public abstract class BaseRequestTest {
     URI uri = new URI(URLEncoder.encode(uriString, "UTF-8"));
     PredicateCompiler compiler = createStrictMock(PredicateCompiler.class);
     UriInfo uriInfo = createMock(UriInfo.class);
+    @SuppressWarnings("unchecked")
+    MultivaluedMap<String, String> queryParams = createMock(MultivaluedMap.class);
     RequestHandler handler = createStrictMock(RequestHandler.class);
     Result result = createMock(Result.class);
     ResultStatus resultStatus = createMock(ResultStatus.class);
     ResultPostProcessor processor = createStrictMock(ResultPostProcessor.class);
     RequestBody body = createNiceMock(RequestBody.class);
+    ResourceInstance resource = createNiceMock(ResourceInstance.class);
+    ResourceDefinition resourceDefinition = createNiceMock(ResourceDefinition.class);
+    Renderer renderer = new DefaultRenderer();
 
-    Request request = getTestRequest(null, body, uriInfo, compiler, handler, processor, null);
+    Request request = getTestRequest(null, body, uriInfo, compiler, handler, processor, resource);
 
     //expectations
+    expect(uriInfo.getQueryParameters()).andReturn(queryParams).anyTimes();
+    expect(queryParams.getFirst(QueryLexer.QUERY_MINIMAL)).andReturn(null);
+    expect(queryParams.getFirst(QueryLexer.QUERY_FORMAT)).andReturn(null);
+    expect(resource.getResourceDefinition()).andReturn(resourceDefinition);
+    expect(resourceDefinition.getRenderer(null)).andReturn(renderer);
     expect(uriInfo.getRequestUri()).andReturn(uri).anyTimes();
     expect(handler.handleRequest(request)).andReturn(result);
     expect(result.getStatus()).andReturn(resultStatus).anyTimes();
     expect(resultStatus.isErrorState()).andReturn(false).anyTimes();
     processor.process(result);
 
-    replay(compiler, uriInfo, handler, result, resultStatus, processor, body);
+    replay(compiler, uriInfo, handler, queryParams, resource, resourceDefinition, result, resultStatus, processor, body);
 
     Result processResult = request.process();
 
-    verify(compiler, uriInfo, handler, result, resultStatus, processor, body);
+    verify(compiler, uriInfo, handler, queryParams, resource, resourceDefinition, result, resultStatus, processor, body);
     assertSame(result, processResult);
     assertNull(request.getQueryPredicate());
   }
@@ -115,15 +130,25 @@ public abstract class BaseRequestTest {
     URI uri = new URI(URLEncoder.encode(uriString, "UTF-8"));
     PredicateCompiler compiler = createStrictMock(PredicateCompiler.class);
     UriInfo uriInfo = createMock(UriInfo.class);
+    @SuppressWarnings("unchecked")
+    MultivaluedMap<String, String> queryParams = createMock(MultivaluedMap.class);
     RequestHandler handler = createStrictMock(RequestHandler.class);
     Result result = createMock(Result.class);
     ResultStatus resultStatus = createMock(ResultStatus.class);
     ResultPostProcessor processor = createStrictMock(ResultPostProcessor.class);
     RequestBody body = createNiceMock(RequestBody.class);
+    ResourceInstance resource = createNiceMock(ResourceInstance.class);
+    ResourceDefinition resourceDefinition = createNiceMock(ResourceDefinition.class);
+    Renderer renderer = new DefaultRenderer();
 
-    Request request = getTestRequest(null, body, uriInfo, compiler, handler, processor, null);
+    Request request = getTestRequest(null, body, uriInfo, compiler, handler, processor, resource);
 
     //expectations
+    expect(uriInfo.getQueryParameters()).andReturn(queryParams).anyTimes();
+    expect(queryParams.getFirst(QueryLexer.QUERY_MINIMAL)).andReturn(null);
+    expect(queryParams.getFirst(QueryLexer.QUERY_FORMAT)).andReturn(null);
+    expect(resource.getResourceDefinition()).andReturn(resourceDefinition);
+    expect(resourceDefinition.getRenderer(null)).andReturn(renderer);
     expect(uriInfo.getRequestUri()).andReturn(uri).anyTimes();
     expect(handler.handleRequest(request)).andReturn(result);
     expect(result.getStatus()).andReturn(resultStatus).anyTimes();
@@ -131,11 +156,13 @@ public abstract class BaseRequestTest {
     processor.process(result);
     expect(body.getQueryString()).andReturn(null);
 
-    replay(compiler, uriInfo, handler, result, resultStatus, processor, body);
+    replay(compiler, uriInfo, handler, queryParams, resource,
+        resourceDefinition, result, resultStatus, processor, body);
 
     Result processResult = request.process();
 
-    verify(compiler, uriInfo, handler, result, resultStatus, processor, body);
+    verify(compiler, uriInfo, handler, queryParams, resource,
+        resourceDefinition, result, resultStatus, processor, body);
     assertSame(result, processResult);
     assertNull(request.getQueryPredicate());
   }
@@ -149,15 +176,25 @@ public abstract class BaseRequestTest {
     PredicateCompiler compiler = createStrictMock(PredicateCompiler.class);
     Predicate predicate = createNiceMock(Predicate.class);
     UriInfo uriInfo = createMock(UriInfo.class);
+    @SuppressWarnings("unchecked")
+    MultivaluedMap<String, String> queryParams = createMock(MultivaluedMap.class);
     RequestHandler handler = createStrictMock(RequestHandler.class);
     Result result = createMock(Result.class);
     ResultStatus resultStatus = createMock(ResultStatus.class);
     ResultPostProcessor processor = createStrictMock(ResultPostProcessor.class);
     RequestBody body = createNiceMock(RequestBody.class);
+    ResourceInstance resource = createNiceMock(ResourceInstance.class);
+    ResourceDefinition resourceDefinition = createNiceMock(ResourceDefinition.class);
+    Renderer renderer = new DefaultRenderer();
 
-    Request request = getTestRequest(headers, body, uriInfo, compiler, handler, processor, null);
+    Request request = getTestRequest(headers, body, uriInfo, compiler, handler, processor, resource);
 
     //expectations
+    expect(uriInfo.getQueryParameters()).andReturn(queryParams).anyTimes();
+    expect(queryParams.getFirst(QueryLexer.QUERY_MINIMAL)).andReturn(null);
+    expect(queryParams.getFirst(QueryLexer.QUERY_FORMAT)).andReturn(null);
+    expect(resource.getResourceDefinition()).andReturn(resourceDefinition);
+    expect(resourceDefinition.getRenderer(null)).andReturn(renderer);
     expect(uriInfo.getRequestUri()).andReturn(uri).anyTimes();
     expect(body.getQueryString()).andReturn(null);
     expect(compiler.compile("foo=foo-value&bar=bar-value")).andReturn(predicate);
@@ -166,11 +203,13 @@ public abstract class BaseRequestTest {
     expect(resultStatus.isErrorState()).andReturn(false).anyTimes();
     processor.process(result);
 
-    replay(headers, compiler, uriInfo, handler, result, resultStatus, processor, predicate, body);
+    replay(headers, compiler, uriInfo, handler, queryParams, resource,
+        resourceDefinition, result, resultStatus, processor, predicate, body);
 
     Result processResult = request.process();
 
-    verify(headers, compiler, uriInfo, handler, result, resultStatus, processor, predicate, body);
+    verify(headers, compiler, uriInfo, handler, queryParams, resource,
+        resourceDefinition, result, resultStatus, processor, predicate, body);
 
     assertSame(processResult, result);
     assertSame(predicate, request.getQueryPredicate());
@@ -181,6 +220,8 @@ public abstract class BaseRequestTest {
     HttpHeaders headers = createNiceMock(HttpHeaders.class);
     String uriString = "http://localhost.com:8080/api/v1/clusters/c1";
     URI uri = new URI(URLEncoder.encode(uriString, "UTF-8"));
+    @SuppressWarnings("unchecked")
+    MultivaluedMap<String, String> queryParams = createMock(MultivaluedMap.class);
     PredicateCompiler compiler = createStrictMock(PredicateCompiler.class);
     Predicate predicate = createNiceMock(Predicate.class);
     UriInfo uriInfo = createMock(UriInfo.class);
@@ -189,10 +230,18 @@ public abstract class BaseRequestTest {
     ResultStatus resultStatus = createMock(ResultStatus.class);
     ResultPostProcessor processor = createStrictMock(ResultPostProcessor.class);
     RequestBody body = createNiceMock(RequestBody.class);
+    ResourceInstance resource = createNiceMock(ResourceInstance.class);
+    ResourceDefinition resourceDefinition = createNiceMock(ResourceDefinition.class);
+    Renderer renderer = new DefaultRenderer();
 
-    Request request = getTestRequest(headers, body, uriInfo, compiler, handler, processor, null);
+    Request request = getTestRequest(headers, body, uriInfo, compiler, handler, processor, resource);
 
     //expectations
+    expect(uriInfo.getQueryParameters()).andReturn(queryParams).anyTimes();
+    expect(queryParams.getFirst(QueryLexer.QUERY_MINIMAL)).andReturn(null);
+    expect(queryParams.getFirst(QueryLexer.QUERY_FORMAT)).andReturn(null);
+    expect(resource.getResourceDefinition()).andReturn(resourceDefinition);
+    expect(resourceDefinition.getRenderer(null)).andReturn(renderer);
     expect(uriInfo.getRequestUri()).andReturn(uri).anyTimes();
     expect(body.getQueryString()).andReturn("foo=bar");
     expect(compiler.compile("foo=bar")).andReturn(predicate);
@@ -201,11 +250,13 @@ public abstract class BaseRequestTest {
     expect(resultStatus.isErrorState()).andReturn(false).anyTimes();
     processor.process(result);
 
-    replay(headers, compiler, uriInfo, handler, result, resultStatus, processor, predicate, body);
+    replay(headers, compiler, uriInfo, handler, queryParams, resource,
+        resourceDefinition, result, resultStatus, processor, predicate, body);
 
     Result processResult = request.process();
 
-    verify(headers, compiler, uriInfo, handler, result, resultStatus, processor, predicate, body);
+    verify(headers, compiler, uriInfo, handler, queryParams, resource,
+        resourceDefinition, result, resultStatus, processor, predicate, body);
 
     assertSame(processResult, result);
     assertSame(predicate, request.getQueryPredicate());
@@ -219,15 +270,25 @@ public abstract class BaseRequestTest {
     PredicateCompiler compiler = createStrictMock(PredicateCompiler.class);
     Predicate predicate = createNiceMock(Predicate.class);
     UriInfo uriInfo = createMock(UriInfo.class);
+    @SuppressWarnings("unchecked")
+    MultivaluedMap<String, String> queryParams = createMock(MultivaluedMap.class);
     RequestHandler handler = createStrictMock(RequestHandler.class);
     Result result = createMock(Result.class);
     ResultStatus resultStatus = createMock(ResultStatus.class);
     ResultPostProcessor processor = createStrictMock(ResultPostProcessor.class);
     RequestBody body = createNiceMock(RequestBody.class);
+    ResourceInstance resource = createNiceMock(ResourceInstance.class);
+    ResourceDefinition resourceDefinition = createNiceMock(ResourceDefinition.class);
+    Renderer renderer = new DefaultRenderer();
 
-    Request request = getTestRequest(headers, body, uriInfo, compiler, handler, processor, null);
+    Request request = getTestRequest(headers, body, uriInfo, compiler, handler, processor, resource);
 
     //expectations
+    expect(uriInfo.getQueryParameters()).andReturn(queryParams).anyTimes();
+    expect(queryParams.getFirst(QueryLexer.QUERY_MINIMAL)).andReturn(null);
+    expect(queryParams.getFirst(QueryLexer.QUERY_FORMAT)).andReturn(null);
+    expect(resource.getResourceDefinition()).andReturn(resourceDefinition);
+    expect(resourceDefinition.getRenderer(null)).andReturn(renderer);
     expect(uriInfo.getRequestUri()).andReturn(uri).anyTimes();
     expect(body.getQueryString()).andReturn("foo=bar");
     expect(compiler.compile("foo=bar")).andReturn(predicate);
@@ -236,11 +297,13 @@ public abstract class BaseRequestTest {
     expect(resultStatus.isErrorState()).andReturn(false).anyTimes();
     processor.process(result);
 
-    replay(headers, compiler, uriInfo, handler, result, resultStatus, processor, predicate, body);
+    replay(headers, compiler, uriInfo, handler, queryParams, resource,
+        resourceDefinition, result, resultStatus, processor, predicate, body);
 
     Result processResult = request.process();
 
-    verify(headers, compiler, uriInfo, handler, result, resultStatus, processor, predicate, body);
+    verify(headers, compiler, uriInfo, handler, queryParams, resource,
+        resourceDefinition, result, resultStatus, processor, predicate, body);
 
     assertSame(processResult, result);
     assertSame(predicate, request.getQueryPredicate());
@@ -250,23 +313,35 @@ public abstract class BaseRequestTest {
   public void testProcess_WithBody_InvalidQuery() throws Exception {
     UriInfo uriInfo = createMock(UriInfo.class);
     String uriString = "http://localhost.com:8080/api/v1/clusters/c1";
+    @SuppressWarnings("unchecked")
+    MultivaluedMap<String, String> queryParams = createMock(MultivaluedMap.class);
     URI uri = new URI(URLEncoder.encode(uriString, "UTF-8"));
     PredicateCompiler compiler = createStrictMock(PredicateCompiler.class);
     RequestBody body = createNiceMock(RequestBody.class);
     Exception exception = new InvalidQueryException("test");
+    ResourceInstance resource = createNiceMock(ResourceInstance.class);
+    ResourceDefinition resourceDefinition = createNiceMock(ResourceDefinition.class);
+    Renderer renderer = new DefaultRenderer();
 
-    Request request = getTestRequest(null, body, uriInfo, compiler, null, null, null);
+    Request request = getTestRequest(null, body, uriInfo, compiler, null, null, resource);
 
     //expectations
+    expect(uriInfo.getQueryParameters()).andReturn(queryParams).anyTimes();
+    expect(queryParams.getFirst(QueryLexer.QUERY_MINIMAL)).andReturn(null);
+    expect(queryParams.getFirst(QueryLexer.QUERY_FORMAT)).andReturn(null);
+    expect(resource.getResourceDefinition()).andReturn(resourceDefinition);
+    expect(resourceDefinition.getRenderer(null)).andReturn(renderer);
     expect(uriInfo.getRequestUri()).andReturn(uri).anyTimes();
     expect(body.getQueryString()).andReturn("blahblahblah");
     expect(compiler.compile("blahblahblah")).andThrow(exception);
 
-    replay(compiler, uriInfo, body);
+    replay(compiler, uriInfo, queryParams, resource,
+        resourceDefinition, body);
 
     Result processResult = request.process();
 
-    verify(compiler, uriInfo, body);
+    verify(compiler, uriInfo, queryParams, resource,
+        resourceDefinition, body);
 
     assertEquals(400, processResult.getStatus().getStatusCode());
     assertTrue(processResult.getStatus().isErrorState());
@@ -279,25 +354,37 @@ public abstract class BaseRequestTest {
     URI uri = new URI(URLEncoder.encode(uriString, "UTF-8"));
     PredicateCompiler compiler = createStrictMock(PredicateCompiler.class);
     UriInfo uriInfo = createMock(UriInfo.class);
+    @SuppressWarnings("unchecked")
+    MultivaluedMap<String, String> queryParams = createMock(MultivaluedMap.class);
     RequestHandler handler = createStrictMock(RequestHandler.class);
     Result result = createMock(Result.class);
     ResultStatus resultStatus = createMock(ResultStatus.class);
     ResultPostProcessor processor = createStrictMock(ResultPostProcessor.class);
     RequestBody body = createNiceMock(RequestBody.class);
+    ResourceInstance resource = createNiceMock(ResourceInstance.class);
+    ResourceDefinition resourceDefinition = createNiceMock(ResourceDefinition.class);
+    Renderer renderer = new DefaultRenderer();
 
-    Request request = getTestRequest(null, body, uriInfo, compiler, handler, processor, null);
+    Request request = getTestRequest(null, body, uriInfo, compiler, handler, processor, resource);
 
     //expectations
+    expect(uriInfo.getQueryParameters()).andReturn(queryParams).anyTimes();
+    expect(queryParams.getFirst(QueryLexer.QUERY_MINIMAL)).andReturn(null);
+    expect(queryParams.getFirst(QueryLexer.QUERY_FORMAT)).andReturn(null);
+    expect(resource.getResourceDefinition()).andReturn(resourceDefinition);
+    expect(resourceDefinition.getRenderer(null)).andReturn(renderer);
     expect(uriInfo.getRequestUri()).andReturn(uri).anyTimes();
     expect(handler.handleRequest(request)).andReturn(result);
     expect(result.getStatus()).andReturn(resultStatus).anyTimes();
     expect(resultStatus.isErrorState()).andReturn(true).anyTimes();
 
-    replay(compiler, uriInfo, handler, result, resultStatus, processor, body);
+    replay(compiler, uriInfo, handler, queryParams, resource,
+        resourceDefinition, result, resultStatus, processor, body);
 
     Result processResult = request.process();
 
-    verify(compiler, uriInfo, handler, result, resultStatus, processor, body);
+    verify(compiler, uriInfo, handler, queryParams, resource,
+        resourceDefinition, result, resultStatus, processor, body);
     assertSame(result, processResult);
     assertNull(request.getQueryPredicate());
   }
@@ -351,6 +438,89 @@ public abstract class BaseRequestTest {
     verify(uriInfo, mapQueryParams);
   }
 
+  @Test
+  public void testParseRenderer_minimalResponse() throws Exception {
+
+    String uriString = "http://localhost.com:8080/api/v1/clusters/c1";
+    URI uri = new URI(URLEncoder.encode(uriString, "UTF-8"));
+    PredicateCompiler compiler = createStrictMock(PredicateCompiler.class);
+    UriInfo uriInfo = createMock(UriInfo.class);
+    @SuppressWarnings("unchecked")
+    MultivaluedMap<String, String> queryParams = createMock(MultivaluedMap.class);
+
+    RequestHandler handler = createStrictMock(RequestHandler.class);
+    Result result = createMock(Result.class);
+    ResultStatus resultStatus = createMock(ResultStatus.class);
+    ResultPostProcessor processor = createStrictMock(ResultPostProcessor.class);
+    RequestBody body = createNiceMock(RequestBody.class);
+    ResourceInstance resource = createNiceMock(ResourceInstance.class);
+    ResourceDefinition resourceDefinition = createNiceMock(ResourceDefinition.class);
+    Renderer renderer = new MinimalRenderer();
+
+    Request request = getTestRequest(null, body, uriInfo, compiler, handler, processor, resource);
+
+    //expectations
+    expect(uriInfo.getQueryParameters()).andReturn(queryParams).anyTimes();
+    expect(queryParams.getFirst(QueryLexer.QUERY_MINIMAL)).andReturn("true");
+    expect(resource.getResourceDefinition()).andReturn(resourceDefinition);
+    expect(resourceDefinition.getRenderer("minimal")).andReturn(renderer);
+    expect(uriInfo.getRequestUri()).andReturn(uri).anyTimes();
+    expect(handler.handleRequest(request)).andReturn(result);
+    expect(result.getStatus()).andReturn(resultStatus).anyTimes();
+    expect(resultStatus.isErrorState()).andReturn(false).anyTimes();
+    processor.process(result);
+
+    replay(compiler, uriInfo, handler, queryParams, resource, resourceDefinition, result, resultStatus, processor, body);
+
+    request.process();
+
+    verify(compiler, uriInfo, handler, queryParams, resource, resourceDefinition, result, resultStatus, processor, body);
+
+    assertSame(renderer, request.getRenderer());
+  }
+
+  @Test
+  public void testParseRenderer_formatSpecified() throws Exception {
+
+    String uriString = "http://localhost.com:8080/api/v1/clusters/c1";
+    URI uri = new URI(URLEncoder.encode(uriString, "UTF-8"));
+    PredicateCompiler compiler = createStrictMock(PredicateCompiler.class);
+    UriInfo uriInfo = createMock(UriInfo.class);
+    @SuppressWarnings("unchecked")
+    MultivaluedMap<String, String> queryParams = createMock(MultivaluedMap.class);
+
+    RequestHandler handler = createStrictMock(RequestHandler.class);
+    Result result = createMock(Result.class);
+    ResultStatus resultStatus = createMock(ResultStatus.class);
+    ResultPostProcessor processor = createStrictMock(ResultPostProcessor.class);
+    RequestBody body = createNiceMock(RequestBody.class);
+    ResourceInstance resource = createNiceMock(ResourceInstance.class);
+    ResourceDefinition resourceDefinition = createNiceMock(ResourceDefinition.class);
+    Renderer renderer = new DefaultRenderer();
+
+    Request request = getTestRequest(null, body, uriInfo, compiler, handler, processor, resource);
+
+    //expectations
+    expect(uriInfo.getQueryParameters()).andReturn(queryParams).anyTimes();
+    expect(queryParams.getFirst(QueryLexer.QUERY_MINIMAL)).andReturn(null);
+    expect(queryParams.getFirst(QueryLexer.QUERY_FORMAT)).andReturn("default");
+    expect(resource.getResourceDefinition()).andReturn(resourceDefinition);
+    expect(resourceDefinition.getRenderer("default")).andReturn(renderer);
+    expect(uriInfo.getRequestUri()).andReturn(uri).anyTimes();
+    expect(handler.handleRequest(request)).andReturn(result);
+    expect(result.getStatus()).andReturn(resultStatus).anyTimes();
+    expect(resultStatus.isErrorState()).andReturn(false).anyTimes();
+    processor.process(result);
+
+    replay(compiler, uriInfo, handler, queryParams, resource, resourceDefinition, result, resultStatus, processor, body);
+
+    request.process();
+
+    verify(compiler, uriInfo, handler, queryParams, resource, resourceDefinition, result, resultStatus, processor, body);
+
+    assertSame(renderer, request.getRenderer());
+  }
+
    protected abstract Request getTestRequest(HttpHeaders headers, RequestBody body, UriInfo uriInfo, PredicateCompiler compiler,
                                              RequestHandler handler, ResultPostProcessor processor, ResourceInstance resource);
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/services/BaseServiceTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/BaseServiceTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/BaseServiceTest.java
index 8217b9c..ef1b21a 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/services/BaseServiceTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/BaseServiceTest.java
@@ -93,7 +93,6 @@ public abstract class BaseServiceTest {
     List<ServiceTestInvocation> listTestInvocations = getTestInvocations();
     for (ServiceTestInvocation testInvocation : listTestInvocations) {
       testMethod(testInvocation);
-      testMethodMinimal(testInvocation);
       testMethod_bodyParseException(testInvocation);
       testMethod_resultInErrorState(testInvocation);
     }
@@ -110,7 +109,7 @@ 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, false)).andReturn(serializedResult);
+    expect(serializer.serialize(result)).andReturn(serializedResult);
 
     replayMocks();
 
@@ -121,34 +120,13 @@ public abstract class BaseServiceTest {
     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();
-
-    Response r = testMethod.invoke();
 
-    assertEquals(serializedResult, r.getEntity());
-    assertEquals(testMethod.getStatusCode(), r.getStatus());
-    verifyAndResetMocks();
-  }
 
   private void testMethod_bodyParseException(ServiceTestInvocation testMethod) throws Exception {
     Capture<Result> resultCapture = new Capture<Result>();
     BodyParseException e = new BodyParseException("TEST MSG");
     expect(bodyParser.parse(testMethod.getBody())).andThrow(e);
-    expect(serializer.serialize(capture(resultCapture), eq(false))).andReturn(serializedResult);
+    expect(serializer.serialize(capture(resultCapture))).andReturn(serializedResult);
 
     replayMocks();
 
@@ -170,7 +148,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, false)).andReturn(serializedResult);
+    expect(serializer.serialize(result)).andReturn(serializedResult);
 
     replayMocks();
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/services/serializers/JsonSerializerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/serializers/JsonSerializerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/serializers/JsonSerializerTest.java
index 63eb33a..90063a0 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/services/serializers/JsonSerializerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/serializers/JsonSerializerTest.java
@@ -72,7 +72,7 @@ public class JsonSerializerTest {
     replay(uriInfo, resource/*, resource2*/);
 
     //execute test
-    Object o = new JsonSerializer().serialize(result, false);
+    Object o = new JsonSerializer().serialize(result);
 
     String expected = "{\n" +
         "  \"href\" : \"this is an href\",\n" +
@@ -90,57 +90,6 @@ public class JsonSerializerTest {
   }
 
   @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 {
     UriInfo uriInfo = createMock(UriInfo.class);
     Resource resource = createMock(Resource.class);
@@ -177,7 +126,7 @@ public class JsonSerializerTest {
     replay(uriInfo, resource);
 
     //execute test
-    Object o = new JsonSerializer().serialize(result, false);
+    Object o = new JsonSerializer().serialize(result);
 
     String expected = "{\n" +
         "  \"resources\" : [\n" +


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

Posted by js...@apache.org.
http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/ClusterController.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/ClusterController.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/ClusterController.java
index c50ff7e..f6f94ff 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/ClusterController.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/ClusterController.java
@@ -24,7 +24,7 @@ import java.util.Set;
  * from the backend sources.  A cluster controller maintains a mapping of
  * resource providers keyed by resource types.
  */
-public interface ClusterController {
+public interface ClusterController extends SchemaFactory {
 
 
   // ----- Monitoring ------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/SchemaFactory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/SchemaFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/SchemaFactory.java
new file mode 100644
index 0000000..290b508
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/SchemaFactory.java
@@ -0,0 +1,32 @@
+/**
+ * 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.controller.spi;
+
+/**
+ * Factory for Schema instances.
+ */
+public interface SchemaFactory {
+  /**
+   * Obtain a schema instance for a given resource type.
+   *
+   * @param type  resource type
+   * @return schema instance for the specified type
+   */
+  public Schema getSchema(Resource.Type type);
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/CreateHandlerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/CreateHandlerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/CreateHandlerTest.java
index 22356f0..f7747f3 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/CreateHandlerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/CreateHandlerTest.java
@@ -18,6 +18,9 @@
 
 package org.apache.ambari.server.api.handlers;
 
+import org.apache.ambari.server.api.query.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.ResourceInstance;
 import org.apache.ambari.server.api.services.*;
 import org.apache.ambari.server.api.services.persistence.PersistenceManager;
@@ -41,11 +44,12 @@ public class CreateHandlerTest {
     Request request = createNiceMock(Request.class);
     RequestBody body = createNiceMock(RequestBody.class);
     ResourceInstance resource = createNiceMock(ResourceInstance.class);
+    Query query = createStrictMock(Query.class);
     PersistenceManager pm = createStrictMock(PersistenceManager.class);
     RequestStatus status = createNiceMock(RequestStatus.class);
     Resource resource1 = createNiceMock(Resource.class);
     Resource resource2 = createNiceMock(Resource.class);
-
+    Renderer renderer = new DefaultRenderer();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
@@ -54,15 +58,19 @@ public class CreateHandlerTest {
     // expectations
     expect(request.getResource()).andReturn(resource).atLeastOnce();
     expect(request.getQueryPredicate()).andReturn(null).atLeastOnce();
+    expect(request.getRenderer()).andReturn(renderer);
     expect(request.getBody()).andReturn(body);
 
+    expect(resource.getQuery()).andReturn(query);
+    query.setRenderer(renderer);
+
     expect(pm.create(resource, body)).andReturn(status);
     expect(status.getStatus()).andReturn(RequestStatus.Status.Complete);
     expect(status.getAssociatedResources()).andReturn(setResources);
     expect(resource1.getType()).andReturn(Resource.Type.Cluster).anyTimes();
     expect(resource2.getType()).andReturn(Resource.Type.Cluster).anyTimes();
 
-    replay(request, body, resource, pm, status, resource1, resource2);
+    replay(request, body, resource, query, pm, status, resource1, resource2);
 
     Result result = new TestCreateHandler(pm).handleRequest(request);
 
@@ -85,7 +93,7 @@ public class CreateHandlerTest {
     }
 
     assertEquals(ResultStatus.STATUS.CREATED, result.getStatus().getStatus());
-    verify(request, body, resource, pm, status, resource1, resource2);
+    verify(request, body, resource, query, pm, status, resource1, resource2);
   }
 
   @Test
@@ -93,10 +101,12 @@ public class CreateHandlerTest {
     Request request = createNiceMock(Request.class);
     RequestBody body = createNiceMock(RequestBody.class);
     ResourceInstance resource = createNiceMock(ResourceInstance.class);
+    Query query = createStrictMock(Query.class);
     PersistenceManager pm = createStrictMock(PersistenceManager.class);
     RequestStatus status = createNiceMock(RequestStatus.class);
     Resource resource1 = createNiceMock(Resource.class);
     Resource resource2 = createNiceMock(Resource.class);
+    Renderer renderer = new DefaultRenderer();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
@@ -105,15 +115,19 @@ public class CreateHandlerTest {
     // expectations
     expect(request.getResource()).andReturn(resource).atLeastOnce();
     expect(request.getQueryPredicate()).andReturn(null).atLeastOnce();
+    expect(request.getRenderer()).andReturn(renderer);
     expect(request.getBody()).andReturn(body).atLeastOnce();
 
+    expect(resource.getQuery()).andReturn(query);
+    query.setRenderer(renderer);
+
     expect(pm.create(resource, body)).andReturn(status);
     expect(status.getStatus()).andReturn(RequestStatus.Status.Complete);
     expect(status.getAssociatedResources()).andReturn(setResources);
     expect(resource1.getType()).andReturn(Resource.Type.Cluster).anyTimes();
     expect(resource2.getType()).andReturn(Resource.Type.Cluster).anyTimes();
 
-    replay(request, body, resource, pm, status, resource1, resource2);
+    replay(request, body, resource, query, pm, status, resource1, resource2);
 
     Result result = new TestCreateHandler(pm).handleRequest(request);
 
@@ -135,7 +149,7 @@ public class CreateHandlerTest {
       }
     }
     assertEquals(ResultStatus.STATUS.CREATED, result.getStatus().getStatus());
-    verify(request, body, resource, pm, status, resource1, resource2);
+    verify(request, body, resource, query, pm, status, resource1, resource2);
   }
 
   @Test
@@ -143,20 +157,26 @@ public class CreateHandlerTest {
     Request request = createNiceMock(Request.class);
     RequestBody body = createNiceMock(RequestBody.class);
     ResourceInstance resource = createNiceMock(ResourceInstance.class);
+    Query query = createStrictMock(Query.class);
     PersistenceManager pm = createStrictMock(PersistenceManager.class);
     RequestStatus status = createNiceMock(RequestStatus.class);
     Resource resource1 = createNiceMock(Resource.class);
     Resource resource2 = createNiceMock(Resource.class);
     Resource requestResource = createNiceMock(Resource.class);
+    Renderer renderer = new DefaultRenderer();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
     setResources.add(resource2);
 
     // expectations
-    expect(request.getResource()).andReturn(resource);
+    expect(request.getResource()).andReturn(resource).atLeastOnce();
     expect(request.getBody()).andReturn(body).atLeastOnce();
     expect(request.getQueryPredicate()).andReturn(null).atLeastOnce();
+    expect(request.getRenderer()).andReturn(renderer);
+
+    expect(resource.getQuery()).andReturn(query);
+    query.setRenderer(renderer);
 
     expect(pm.create(resource, body)).andReturn(status);
     expect(status.getStatus()).andReturn(RequestStatus.Status.Accepted);
@@ -165,7 +185,7 @@ public class CreateHandlerTest {
     expect(resource2.getType()).andReturn(Resource.Type.Cluster).anyTimes();
     expect(status.getRequestResource()).andReturn(requestResource).anyTimes();
 
-    replay(request, body, resource, pm, status, resource1, resource2, requestResource);
+    replay(request, body, resource, query, pm, status, resource1, resource2, requestResource);
 
     Result result = new TestCreateHandler(pm).handleRequest(request);
 
@@ -193,7 +213,7 @@ public class CreateHandlerTest {
     assertSame(requestResource, statusNode.getObject());
 
     assertEquals(ResultStatus.STATUS.ACCEPTED, result.getStatus().getStatus());
-    verify(request, body, resource, pm, status, resource1, resource2, requestResource);
+    verify(request, body, resource, query, pm, status, resource1, resource2, requestResource);
   }
 
   private class TestCreateHandler extends CreateHandler {

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/DeleteHandlerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/DeleteHandlerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/DeleteHandlerTest.java
index ac84348..3876058 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/DeleteHandlerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/DeleteHandlerTest.java
@@ -19,6 +19,8 @@ package org.apache.ambari.server.api.handlers;
  */
 
 import org.apache.ambari.server.api.query.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.ResourceInstance;
 import org.apache.ambari.server.api.services.*;
 import org.apache.ambari.server.api.services.persistence.PersistenceManager;
@@ -50,6 +52,7 @@ public class DeleteHandlerTest {
     Resource resource2 = createMock(Resource.class);
     Predicate userPredicate = createNiceMock(Predicate.class);
     Query query = createNiceMock(Query.class);
+    Renderer renderer = new DefaultRenderer();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
@@ -61,6 +64,9 @@ public class DeleteHandlerTest {
 
     expect(request.getQueryPredicate()).andReturn(userPredicate).atLeastOnce();
     expect(resource.getQuery()).andReturn(query).atLeastOnce();
+    expect(request.getRenderer()).andReturn(renderer);
+
+    query.setRenderer(renderer);
     query.setUserPredicate(userPredicate);
 
     expect(pm.delete(resource, body)).andReturn(status);
@@ -106,6 +112,7 @@ public class DeleteHandlerTest {
     Resource resource2 = createMock(Resource.class);
     Predicate userPredicate = createNiceMock(Predicate.class);
     Query query = createNiceMock(Query.class);
+    Renderer renderer = new DefaultRenderer();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
@@ -117,6 +124,9 @@ public class DeleteHandlerTest {
 
     expect(request.getQueryPredicate()).andReturn(userPredicate).atLeastOnce();
     expect(resource.getQuery()).andReturn(query).atLeastOnce();
+    expect(request.getRenderer()).andReturn(renderer);
+
+    query.setRenderer(renderer);
     query.setUserPredicate(userPredicate);
 
     expect(pm.delete(resource, body)).andReturn(status);
@@ -162,16 +172,21 @@ public class DeleteHandlerTest {
     Resource resource1 = createMock(Resource.class);
     Resource resource2 = createMock(Resource.class);
     Resource requestResource = createMock(Resource.class);
+    Query query = createNiceMock(Query.class);
+    Renderer renderer = new DefaultRenderer();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
     setResources.add(resource2);
 
     // expectations
-    expect(request.getResource()).andReturn(resource);
+    expect(request.getResource()).andReturn(resource).anyTimes();
     expect(request.getBody()).andReturn(body).atLeastOnce();
     // test delete with no user predicate
     expect(request.getQueryPredicate()).andReturn(null).atLeastOnce();
+    expect(resource.getQuery()).andReturn(query).atLeastOnce();
+    expect(request.getRenderer()).andReturn(renderer);
+    query.setRenderer(renderer);
 
     expect(pm.delete(resource, body)).andReturn(status);
     expect(status.getStatus()).andReturn(RequestStatus.Status.Accepted);
@@ -180,7 +195,7 @@ public class DeleteHandlerTest {
     expect(resource2.getType()).andReturn(Resource.Type.Cluster).anyTimes();
     expect(status.getRequestResource()).andReturn(requestResource).anyTimes();
 
-    replay(request, body, resource, pm, status, resource1, resource2, requestResource);
+    replay(request, body, resource, pm, status, resource1, resource2, requestResource, query);
 
     Result result = new TestDeleteHandler(pm).handleRequest(request);
 
@@ -208,7 +223,7 @@ public class DeleteHandlerTest {
     assertSame(requestResource, statusNode.getObject());
     assertEquals(ResultStatus.STATUS.ACCEPTED, result.getStatus().getStatus());
 
-    verify(request, body, resource, pm, status, resource1, resource2, requestResource);
+    verify(request, body, resource, pm, status, resource1, resource2, requestResource, query);
   }
 
   private class TestDeleteHandler extends DeleteHandler {

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/ReadHandlerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/ReadHandlerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/ReadHandlerTest.java
index f1d2ea6..782e128 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/ReadHandlerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/ReadHandlerTest.java
@@ -19,6 +19,8 @@
 package org.apache.ambari.server.api.handlers;
 
 import org.apache.ambari.server.api.query.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.ResourceInstance;
 import org.apache.ambari.server.api.services.Request;
 import org.apache.ambari.server.api.services.Result;
@@ -49,13 +51,15 @@ public class ReadHandlerTest {
 
     Map<String, TemporalInfo> mapPartialResponseFields = new HashMap<String, TemporalInfo>();
     mapPartialResponseFields.put("foo/bar", null);
+    Renderer renderer = new DefaultRenderer();
 
     expect(request.getResource()).andReturn(resource);
     expect(request.getFields()).andReturn(mapPartialResponseFields);
+    expect(request.getRenderer()).andReturn(renderer);
     expect(resource.getQuery()).andReturn(query);
 
     query.setPageRequest(null);
-    query.setMinimal(false);
+    query.setRenderer(renderer);
     query.addProperty("foo/bar", null);
     expectLastCall().andThrow(new IllegalArgumentException("testMsg"));
 
@@ -77,6 +81,7 @@ public class ReadHandlerTest {
     Query query = createMock(Query.class);
     Predicate predicate = createMock(Predicate.class);
     Result result = createStrictMock(Result.class);
+    Renderer renderer = new DefaultRenderer();
     Capture<ResultStatus> resultStatusCapture = new Capture<ResultStatus>();
 
     Map<String, TemporalInfo> mapPartialResponseFields = new HashMap<String, TemporalInfo>();
@@ -89,8 +94,9 @@ public class ReadHandlerTest {
     expect(resource.getQuery()).andReturn(query);
 
     expect(request.getPageRequest()).andReturn(null);
-    expect(request.isMinimal()).andReturn(false);
+    expect(request.getRenderer()).andReturn(renderer);
     expect(request.getFields()).andReturn(mapPartialResponseFields);
+
     query.addProperty("foo", null);
     query.addProperty("bar/c", null);
     query.addProperty("bar/d/e", null);
@@ -99,7 +105,7 @@ public class ReadHandlerTest {
     expect(request.getQueryPredicate()).andReturn(predicate);
     query.setUserPredicate(predicate);
     query.setPageRequest(null);
-    query.setMinimal(false);
+    query.setRenderer(renderer);
     expect(query.execute()).andReturn(result);
     result.setResultStatus(capture(resultStatusCapture));
 
@@ -118,18 +124,19 @@ public class ReadHandlerTest {
     ResourceInstance resource = createStrictMock(ResourceInstance.class);
     Query query = createMock(Query.class);
     Predicate predicate = createMock(Predicate.class);
+    Renderer renderer = new DefaultRenderer();
 
     expect(request.getResource()).andReturn(resource);
     expect(resource.getQuery()).andReturn(query);
 
     expect(request.getPageRequest()).andReturn(null);
-    expect(request.isMinimal()).andReturn(false);
+    expect(request.getRenderer()).andReturn(renderer);
     expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
 
     expect(request.getQueryPredicate()).andReturn(predicate);
     query.setUserPredicate(predicate);
     query.setPageRequest(null);
-    query.setMinimal(false);
+    query.setRenderer(renderer);
     SystemException systemException = new SystemException("testMsg", new RuntimeException());
     expect(query.execute()).andThrow(systemException);
 
@@ -150,18 +157,19 @@ public class ReadHandlerTest {
     Query query = createMock(Query.class);
     Predicate predicate = createMock(Predicate.class);
     NoSuchParentResourceException exception = new NoSuchParentResourceException("exceptionMsg", new RuntimeException());
+    Renderer renderer = new DefaultRenderer();
 
     expect(request.getResource()).andReturn(resource);
     expect(resource.getQuery()).andReturn(query);
 
     expect(request.getPageRequest()).andReturn(null);
-    expect(request.isMinimal()).andReturn(false);
+    expect(request.getRenderer()).andReturn(renderer);
     expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
 
     expect(request.getQueryPredicate()).andReturn(predicate);
     query.setUserPredicate(predicate);
     query.setPageRequest(null);
-    query.setMinimal(false);
+    query.setRenderer(renderer);
 
     expect(query.execute()).andThrow(exception);
 
@@ -181,6 +189,7 @@ public class ReadHandlerTest {
     ResourceInstance resource = createStrictMock(ResourceInstance.class);
     Query query = createMock(Query.class);
     Predicate predicate = createMock(Predicate.class);
+    Renderer renderer = new DefaultRenderer();
     UnsupportedPropertyException exception = new UnsupportedPropertyException(
         Resource.Type.Cluster, Collections.singleton("foo"));
 
@@ -188,13 +197,13 @@ public class ReadHandlerTest {
     expect(resource.getQuery()).andReturn(query);
 
     expect(request.getPageRequest()).andReturn(null);
-    expect(request.isMinimal()).andReturn(false);
+    expect(request.getRenderer()).andReturn(renderer);
     expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
 
     expect(request.getQueryPredicate()).andReturn(predicate);
     query.setUserPredicate(predicate);
     query.setPageRequest(null);
-    query.setMinimal(false);
+    query.setRenderer(renderer);
 
     expect(query.execute()).andThrow(exception);
 
@@ -215,18 +224,19 @@ public class ReadHandlerTest {
     Query query = createMock(Query.class);
     Predicate predicate = createMock(Predicate.class);
     NoSuchResourceException exception = new NoSuchResourceException("msg", new RuntimeException());
+    Renderer renderer = new DefaultRenderer();
 
     expect(request.getResource()).andReturn(resource);
     expect(resource.getQuery()).andReturn(query);
 
     expect(request.getPageRequest()).andReturn(null);
-    expect(request.isMinimal()).andReturn(false);
+    expect(request.getRenderer()).andReturn(renderer);
     expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
 
     expect(request.getQueryPredicate()).andReturn(predicate).anyTimes();
     query.setUserPredicate(predicate);
     query.setPageRequest(null);
-    query.setMinimal(false);
+    query.setRenderer(renderer);
 
     expect(query.execute()).andThrow(exception);
 
@@ -246,18 +256,19 @@ public class ReadHandlerTest {
     ResourceInstance resource = createStrictMock(ResourceInstance.class);
     Query query = createMock(Query.class);
     NoSuchResourceException exception = new NoSuchResourceException("msg", new RuntimeException());
+    Renderer renderer = new DefaultRenderer();
 
     expect(request.getResource()).andReturn(resource);
     expect(resource.getQuery()).andReturn(query);
 
     expect(request.getPageRequest()).andReturn(null);
-    expect(request.isMinimal()).andReturn(false);
+    expect(request.getRenderer()).andReturn(renderer);
     expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
 
     expect(request.getQueryPredicate()).andReturn(null).anyTimes();
     query.setUserPredicate(null);
     query.setPageRequest(null);
-    query.setMinimal(false);
+    query.setRenderer(renderer);
 
     expect(query.execute()).andThrow(exception);
 
@@ -271,34 +282,4 @@ public class ReadHandlerTest {
     assertEquals(exception.getMessage(), result.getStatus().getMessage());
     verify(request, resource, query);
   }
-
-//todo: reverted to just logging the exception and re-throwing it
-//  @Test
-//  public void testHandleRequest__RuntimeException() throws Exception {
-//    Request request = createStrictMock(Request.class);
-//    ResourceInstance resource = createStrictMock(ResourceInstance.class);
-//    Query query = createMock(Query.class);
-//    RuntimeException exception = new RuntimeException("msg");
-//
-//    expect(request.getResource()).andReturn(resource);
-//    expect(resource.getQuery()).andReturn(query);
-//
-//    expect(request.getPageRequest()).andReturn(null);
-//    expect(request.getFields()).andReturn(Collections.<String, TemporalInfo>emptyMap());
-//
-//    expect(request.getQueryPredicate()).andReturn(null).anyTimes();
-//    query.setUserPredicate(null);
-//
-//    expect(query.execute()).andThrow(exception);
-//
-//    replay(request, resource, query);
-//
-//    //test
-//    ReadHandler handler = new ReadHandler();
-//    Result result = handler.handleRequest(request);
-//    // not a query, so not found
-//    assertEquals(ResultStatus.STATUS.SERVER_ERROR, result.getStatus().getStatus());
-//    assertEquals(exception.toString(), result.getStatus().getMessage());
-//    verify(request, resource, query);
-//  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/UpdateHandlerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/UpdateHandlerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/UpdateHandlerTest.java
index 161a61d..4140ce2 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/UpdateHandlerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/handlers/UpdateHandlerTest.java
@@ -19,6 +19,8 @@
 package org.apache.ambari.server.api.handlers;
 
 import org.apache.ambari.server.api.query.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.ResourceInstance;
 import org.apache.ambari.server.api.services.*;
 import org.apache.ambari.server.api.services.persistence.PersistenceManager;
@@ -49,6 +51,7 @@ public class UpdateHandlerTest {
     Resource resource2 = createMock(Resource.class);
     Predicate userPredicate = createNiceMock(Predicate.class);
     Query query = createNiceMock(Query.class);
+    Renderer renderer = new DefaultRenderer();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
@@ -58,8 +61,10 @@ public class UpdateHandlerTest {
     expect(request.getResource()).andReturn(resource).anyTimes();
     expect(request.getBody()).andReturn(body).anyTimes();
     expect(request.getQueryPredicate()).andReturn(userPredicate).atLeastOnce();
+    expect(request.getRenderer()).andReturn(renderer);
 
     expect(resource.getQuery()).andReturn(query).atLeastOnce();
+    query.setRenderer(renderer);
     query.setUserPredicate(userPredicate);
 
     expect(pm.update(resource, body)).andReturn(status);
@@ -105,6 +110,7 @@ public class UpdateHandlerTest {
     Resource resource2 = createMock(Resource.class);
     Predicate userPredicate = createNiceMock(Predicate.class);
     Query query = createNiceMock(Query.class);
+    Renderer renderer = new DefaultRenderer();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
@@ -114,8 +120,10 @@ public class UpdateHandlerTest {
     expect(request.getResource()).andReturn(resource).anyTimes();
     expect(request.getBody()).andReturn(body).anyTimes();
     expect(request.getQueryPredicate()).andReturn(userPredicate).atLeastOnce();
+    expect(request.getRenderer()).andReturn(renderer);
 
     expect(resource.getQuery()).andReturn(query).atLeastOnce();
+    query.setRenderer(renderer);
     query.setUserPredicate(userPredicate);
 
     expect(pm.update(resource, body)).andReturn(status);
@@ -162,7 +170,7 @@ public class UpdateHandlerTest {
     Resource requestResource = createMock(Resource.class);
     Predicate userPredicate = createNiceMock(Predicate.class);
     Query query = createNiceMock(Query.class);
-
+    Renderer renderer = new DefaultRenderer();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
@@ -172,8 +180,10 @@ public class UpdateHandlerTest {
     expect(request.getResource()).andReturn(resource).anyTimes();
     expect(request.getBody()).andReturn(body).anyTimes();
     expect(request.getQueryPredicate()).andReturn(userPredicate).atLeastOnce();
+    expect(request.getRenderer()).andReturn(renderer);
 
     expect(resource.getQuery()).andReturn(query).atLeastOnce();
+    query.setRenderer(renderer);
     query.setUserPredicate(userPredicate);
 
     expect(pm.update(resource, body)).andReturn(status);

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/predicate/QueryLexerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/predicate/QueryLexerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/predicate/QueryLexerTest.java
index 4cffb3b..a587480 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/predicate/QueryLexerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/predicate/QueryLexerTest.java
@@ -159,6 +159,42 @@ public class QueryLexerTest {
   }
 
   @Test
+  public void testTokens_ignoreFormatSyntax___noPredicate() throws InvalidQueryException {
+
+    QueryLexer lexer = new QueryLexer();
+    Token[] tokens = lexer.tokens("format=default");
+    assertEquals(0, tokens.length);
+  }
+
+  @Test
+  public void testTokens_ignoreFormatSyntax___formatFirst() throws InvalidQueryException {
+
+    List<Token> listTokens = new ArrayList<Token>();
+    listTokens.add(new Token(Token.TYPE.RELATIONAL_OPERATOR, "="));
+    listTokens.add(new Token(Token.TYPE.PROPERTY_OPERAND, "foo"));
+    listTokens.add(new Token(Token.TYPE.VALUE_OPERAND, "1"));
+
+    QueryLexer lexer = new QueryLexer();
+    Token[] tokens = lexer.tokens("format=default&foo=1");
+
+    assertArrayEquals(listTokens.toArray(new Token[listTokens.size()]), tokens);
+  }
+
+  @Test
+  public void testTokens_ignoreFormatSyntax___formatLast() throws InvalidQueryException {
+
+    List<Token> listTokens = new ArrayList<Token>();
+    listTokens.add(new Token(Token.TYPE.RELATIONAL_OPERATOR, "="));
+    listTokens.add(new Token(Token.TYPE.PROPERTY_OPERAND, "foo"));
+    listTokens.add(new Token(Token.TYPE.VALUE_OPERAND, "1"));
+
+    QueryLexer lexer = new QueryLexer();
+    Token[] tokens = lexer.tokens("foo=1&format=foo");
+
+    assertArrayEquals(listTokens.toArray(new Token[listTokens.size()]), tokens);
+  }
+
+  @Test
   public void testTokens_ignoreUnderscoreSyntax___noPredicate() throws InvalidQueryException {
 
     QueryLexer lexer = new QueryLexer();

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/query/QueryImplTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/query/QueryImplTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/query/QueryImplTest.java
index 12160ff..ad453d9 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/query/QueryImplTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/query/QueryImplTest.java
@@ -20,6 +20,7 @@
 package org.apache.ambari.server.api.query;
 
 
+import org.apache.ambari.server.api.query.render.DefaultRenderer;
 import org.apache.ambari.server.api.resources.ResourceDefinition;
 import org.apache.ambari.server.api.resources.ResourceInstance;
 import org.apache.ambari.server.api.resources.StackResourceDefinition;
@@ -111,7 +112,6 @@ public class QueryImplTest {
 
     //test
     QueryImpl instance = new TestQuery(mapIds, resourceDefinition);
-
     Result result = instance.execute();
 
     verify(resourceDefinition);
@@ -536,6 +536,7 @@ public class QueryImplTest {
   public static class TestQuery extends QueryImpl {
     public TestQuery(Map<Resource.Type, String> mapIds, ResourceDefinition resourceDefinition) {
       super(mapIds, resourceDefinition, new ClusterControllerImpl(new ClusterControllerImplTest.TestProviderModule()));
+      setRenderer(new DefaultRenderer());
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/query/QueryInfoTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/query/QueryInfoTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/query/QueryInfoTest.java
new file mode 100644
index 0000000..4dfdc29
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/query/QueryInfoTest.java
@@ -0,0 +1,50 @@
+/**
+ * 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.ClusterResourceDefinition;
+import org.apache.ambari.server.api.resources.ResourceDefinition;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+/**
+ * QueryInfo unit tests.
+ */
+public class QueryInfoTest {
+  @Test
+  public void testGetProperties() {
+    Set<String> properties = new HashSet<String>();
+    QueryInfo info = new QueryInfo(new ClusterResourceDefinition(), properties);
+
+    assertEquals(properties, info.getProperties());
+  }
+
+  @Test
+  public void testGetResource() {
+    ResourceDefinition resource = new ClusterResourceDefinition();
+    QueryInfo info = new QueryInfo(resource, new HashSet<String>());
+
+    assertSame(resource, info.getResource());
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/query/render/ClusterBlueprintRendererTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/query/render/ClusterBlueprintRendererTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/query/render/ClusterBlueprintRendererTest.java
new file mode 100644
index 0000000..4d2e60e
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/query/render/ClusterBlueprintRendererTest.java
@@ -0,0 +1,225 @@
+/**
+ * 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.ClusterResourceDefinition;
+import org.apache.ambari.server.api.resources.HostComponentResourceDefinition;
+import org.apache.ambari.server.api.resources.HostResourceDefinition;
+import org.apache.ambari.server.api.services.Result;
+import org.apache.ambari.server.api.services.ResultImpl;
+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.junit.Test;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * ClusterBlueprintRenderer unit tests.
+ */
+public class ClusterBlueprintRendererTest {
+  @Test
+  public void testFinalizeProperties__instance() {
+    QueryInfo rootQuery = new QueryInfo(new ClusterResourceDefinition(), new HashSet<String>());
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Cluster");
+    rootQuery.getProperties().add("foo/bar");
+    rootQuery.getProperties().add("prop1");
+
+    QueryInfo hostInfo = new QueryInfo(new HostResourceDefinition(), new HashSet<String>());
+    queryTree.addChild(hostInfo, "Host");
+
+    QueryInfo hostComponentInfo = new QueryInfo(new HostComponentResourceDefinition(), new HashSet<String>());
+    queryTree.getChild("Host").addChild(hostComponentInfo, "HostComponent");
+
+    ClusterBlueprintRenderer renderer = new ClusterBlueprintRenderer();
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, false);
+
+    Set<String> rootProperties = propertyTree.getObject();
+    assertEquals(2, rootProperties.size());
+    assertNotNull(propertyTree.getChild("Host"));
+    assertTrue(propertyTree.getChild("Host").getObject().isEmpty());
+    assertNotNull(propertyTree.getChild("Host/HostComponent"));
+    assertEquals(1, propertyTree.getChild("Host/HostComponent").getObject().size());
+    assertTrue(propertyTree.getChild("Host/HostComponent").getObject().contains("HostRoles/component_name"));
+  }
+
+  @Test
+  public void testFinalizeProperties__instance_noComponentNode() {
+    QueryInfo rootQuery = new QueryInfo(new ClusterResourceDefinition(), new HashSet<String>());
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Cluster");
+    rootQuery.getProperties().add("foo/bar");
+    rootQuery.getProperties().add("prop1");
+
+    ClusterBlueprintRenderer renderer = new ClusterBlueprintRenderer();
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, false);
+
+    Set<String> rootProperties = propertyTree.getObject();
+    assertEquals(2, rootProperties.size());
+    assertNotNull(propertyTree.getChild("Host"));
+    assertTrue(propertyTree.getChild("Host").getObject().isEmpty());
+    assertNotNull(propertyTree.getChild("Host/HostComponent"));
+    assertEquals(1, propertyTree.getChild("Host/HostComponent").getObject().size());
+    assertTrue(propertyTree.getChild("Host/HostComponent").getObject().contains("HostRoles/component_name"));
+  }
+
+  @Test
+  public void testFinalizeResult() throws Exception{
+    Result result = new ResultImpl(true);
+    createClusterResultTree(result.getResultTree());
+
+    ClusterBlueprintRenderer renderer = new ClusterBlueprintRenderer();
+    Result blueprintResult = renderer.finalizeResult(result);
+
+    TreeNode<Resource> blueprintTree = blueprintResult.getResultTree();
+    assertNull(blueprintTree.getProperty("isCollection"));
+    assertEquals(1, blueprintTree.getChildren().size());
+
+    TreeNode<Resource> blueprintNode = blueprintTree.getChildren().iterator().next();
+    assertEquals(0, blueprintNode.getChildren().size());
+    Resource blueprintResource = blueprintNode.getObject();
+    Map<String, Map<String, Object>> properties = blueprintResource.getPropertiesMap();
+
+    assertEquals("blueprint-testCluster", properties.get("Blueprints").get("blueprint_name"));
+    assertEquals("HDP", properties.get("Blueprints").get("stack_name"));
+    assertEquals("1.3.3", properties.get("Blueprints").get("stack_version"));
+
+    Collection<Map<String, Object>> host_groups = (Collection<Map<String, Object>>) properties.get("").get("host_groups");
+    assertEquals(2, host_groups.size());
+
+    for (Map<String, Object> hostGroupProperties : host_groups) {
+      String host_group_name = (String) hostGroupProperties.get("name");
+      if (host_group_name.equals("host_group_1")) {
+        assertEquals("1", hostGroupProperties.get("cardinality"));
+
+        Collection<Map<String, String>> components = (Collection<Map<String, String>>) hostGroupProperties.get("components");
+        // 4 specified components and ambari server
+        assertEquals(5, components.size());
+
+        Set<String> expectedValues = new HashSet<String>(
+            Arrays.asList("JOBTRACKER", "TASKTRACKER", "NAMENODE", "DATANODE", "AMBARI_SERVER"));
+
+        Set<String> actualValues = new HashSet<String>();
+
+
+        for (Map<String, String> componentProperties : components) {
+          assertEquals(1, componentProperties.size());
+          actualValues.add(componentProperties.get("name"));
+        }
+        assertEquals(expectedValues, actualValues);
+      } else if (host_group_name.equals("host_group_2")) {
+        // cardinality is 2 because 2 hosts share same topology
+        assertEquals("2", hostGroupProperties.get("cardinality"));
+
+        Collection<Map<String, String>> components = (Collection<Map<String, String>>) hostGroupProperties.get("components");
+        assertEquals(2, components.size());
+
+        Set<String> expectedValues = new HashSet<String>(
+            Arrays.asList("TASKTRACKER", "DATANODE"));
+
+        Set<String> actualValues = new HashSet<String>();
+
+
+        for (Map<String, String> componentProperties : components) {
+          assertEquals(1, componentProperties.size());
+          actualValues.add(componentProperties.get("name"));
+        }
+        assertEquals(expectedValues, actualValues);
+      }
+    }
+  }
+
+  //todo: collection resource
+
+  private void createClusterResultTree(TreeNode<Resource> resultTree) throws Exception{
+    Resource clusterResource = new ResourceImpl(Resource.Type.Cluster);
+    clusterResource.setProperty("Clusters/cluster_name", "testCluster");
+    clusterResource.setProperty("Clusters/version", "HDP-1.3.3");
+    TreeNode<Resource> clusterTree = resultTree.addChild(clusterResource, "Cluster:1");
+
+    TreeNode<Resource> hostsTree = clusterTree.addChild(null, "hosts");
+    hostsTree.setProperty("isCollection", "true");
+
+    // host 1 : ambari host
+    Resource hostResource = new ResourceImpl(Resource.Type.Host);
+    hostResource.setProperty("Hosts/host_name", getLocalHostName());
+    TreeNode<Resource> hostTree = hostsTree.addChild(hostResource, "Host:1");
+
+    TreeNode<Resource> hostComponentsTree = hostTree.addChild(null, "host_components");
+    hostComponentsTree.setProperty("isCollection", "true");
+
+    // host 1 components
+    Resource nnComponentResource = new ResourceImpl(Resource.Type.HostComponent);
+    nnComponentResource.setProperty("HostRoles/component_name", "NAMENODE");
+
+    Resource dnComponentResource = new ResourceImpl(Resource.Type.HostComponent);
+    dnComponentResource.setProperty("HostRoles/component_name", "DATANODE");
+
+    Resource jtComponentResource = new ResourceImpl(Resource.Type.HostComponent);
+    jtComponentResource.setProperty("HostRoles/component_name", "JOBTRACKER");
+
+    Resource ttComponentResource = new ResourceImpl(Resource.Type.HostComponent);
+    ttComponentResource.setProperty("HostRoles/component_name", "TASKTRACKER");
+
+    hostComponentsTree.addChild(nnComponentResource, "HostComponent:1");
+    hostComponentsTree.addChild(dnComponentResource, "HostComponent:2");
+    hostComponentsTree.addChild(jtComponentResource, "HostComponent:3");
+    hostComponentsTree.addChild(ttComponentResource, "HostComponent:4");
+
+    // host 2
+    Resource host2Resource = new ResourceImpl(Resource.Type.Host);
+    host2Resource.setProperty("Hosts/host_name", "testHost2");
+    TreeNode<Resource> host2Tree = hostsTree.addChild(host2Resource, "Host:2");
+
+    TreeNode<Resource> host2ComponentsTree = host2Tree.addChild(null, "host_components");
+    host2ComponentsTree.setProperty("isCollection", "true");
+
+    // host 2 components
+    host2ComponentsTree.addChild(dnComponentResource, "HostComponent:1");
+    host2ComponentsTree.addChild(ttComponentResource, "HostComponent:2");
+
+    // host 3 : same topology as host 2
+    Resource host3Resource = new ResourceImpl(Resource.Type.Host);
+    host3Resource.setProperty("Hosts/host_name", "testHost3");
+    TreeNode<Resource> host3Tree = hostsTree.addChild(host3Resource, "Host:3");
+
+    TreeNode<Resource> host3ComponentsTree = host3Tree.addChild(null, "host_components");
+    host3ComponentsTree.setProperty("isCollection", "true");
+
+    // host 3 components
+    host3ComponentsTree.addChild(dnComponentResource, "HostComponent:1");
+    host3ComponentsTree.addChild(ttComponentResource, "HostComponent:2");
+  }
+
+  private String getLocalHostName() throws UnknownHostException {
+    return InetAddress.getLocalHost().getHostName();
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/7eb4bc6b/ambari-server/src/test/java/org/apache/ambari/server/api/query/render/DefaultRendererTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/query/render/DefaultRendererTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/query/render/DefaultRendererTest.java
new file mode 100644
index 0000000..eb0f28f
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/query/render/DefaultRendererTest.java
@@ -0,0 +1,342 @@
+/**
+ * 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.ComponentResourceDefinition;
+import org.apache.ambari.server.api.resources.ServiceResourceDefinition;
+import org.apache.ambari.server.api.services.Result;
+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.spi.Schema;
+import org.apache.ambari.server.controller.spi.SchemaFactory;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * DefaultRenderer unit tests.
+ */
+public class DefaultRendererTest {
+
+  @Test
+  public void testFinalizeProperties__instance_noProperties() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema schema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Component)).andReturn(schema).anyTimes();
+    expect(schema.getKeyPropertyId(Resource.Type.Component)).andReturn("ServiceComponentInfo/component_name").anyTimes();
+    expect(schema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceComponentInfo/service_name").anyTimes();
+
+    replay(schemaFactory, schema);
+
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), new HashSet<String>());
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+
+    DefaultRenderer renderer = new DefaultRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, false);
+    // no properties should have been added
+    assertTrue(propertyTree.getObject().isEmpty());
+    assertEquals(1, propertyTree.getChildren().size());
+
+    TreeNode<Set<String>> componentNode = propertyTree.getChild("Component");
+    assertEquals(2, componentNode.getObject().size());
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/component_name"));
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/service_name"));
+
+    verify(schemaFactory, schema);
+  }
+
+  @Test
+  public void testFinalizeProperties__instance_properties() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema schema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Service)).andReturn(schema).anyTimes();
+    expect(schema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceInfo/service_name").anyTimes();
+    expect(schema.getKeyPropertyId(Resource.Type.Cluster)).andReturn("ServiceInfo/cluster_name").anyTimes();
+
+    replay(schemaFactory, schema);
+
+    HashSet<String> serviceProperties = new HashSet<String>();
+    serviceProperties.add("foo/bar");
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), serviceProperties);
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+
+    DefaultRenderer renderer = new DefaultRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, false);
+
+    assertEquals(3, propertyTree.getObject().size());
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/service_name"));
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/cluster_name"));
+    assertTrue(propertyTree.getObject().contains("foo/bar"));
+
+    assertEquals(0, propertyTree.getChildren().size());
+
+    verify(schemaFactory, schema);
+  }
+
+  @Test
+  public void testFinalizeProperties__collection_noProperties() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema schema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Service)).andReturn(schema).anyTimes();
+    expect(schema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceInfo/service_name").anyTimes();
+    expect(schema.getKeyPropertyId(Resource.Type.Cluster)).andReturn("ServiceInfo/cluster_name").anyTimes();
+
+    replay(schemaFactory, schema);
+
+    HashSet<String> serviceProperties = new HashSet<String>();
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), serviceProperties);
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+
+    DefaultRenderer renderer = new DefaultRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, true);
+
+    assertEquals(2, propertyTree.getObject().size());
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/service_name"));
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/cluster_name"));
+
+    assertEquals(0, propertyTree.getChildren().size());
+
+    verify(schemaFactory, schema);
+  }
+
+  @Test
+  public void testFinalizeProperties__collection_properties() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema schema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Service)).andReturn(schema).anyTimes();
+    expect(schema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceInfo/service_name").anyTimes();
+    expect(schema.getKeyPropertyId(Resource.Type.Cluster)).andReturn("ServiceInfo/cluster_name").anyTimes();
+
+    replay(schemaFactory, schema);
+
+    HashSet<String> serviceProperties = new HashSet<String>();
+    serviceProperties.add("foo/bar");
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), serviceProperties);
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+
+    DefaultRenderer renderer = new DefaultRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, true);
+
+    assertEquals(3, propertyTree.getObject().size());
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/service_name"));
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/cluster_name"));
+    assertTrue(propertyTree.getObject().contains("foo/bar"));
+
+    assertEquals(0, propertyTree.getChildren().size());
+
+    verify(schemaFactory, schema);
+  }
+
+  @Test
+  public void testFinalizeProperties__instance_subResource_noProperties() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema serviceSchema = createNiceMock(Schema.class);
+    Schema componentSchema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Service)).andReturn(serviceSchema).anyTimes();
+    expect(serviceSchema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceInfo/service_name").anyTimes();
+    expect(serviceSchema.getKeyPropertyId(Resource.Type.Cluster)).andReturn("ServiceInfo/cluster_name").anyTimes();
+    expect(schemaFactory.getSchema(Resource.Type.Component)).andReturn(componentSchema).anyTimes();
+    expect(componentSchema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceComponentInfo/service_name").anyTimes();
+    expect(componentSchema.getKeyPropertyId(Resource.Type.Component)).andReturn("ServiceComponentInfo/component_name").anyTimes();
+
+    replay(schemaFactory, serviceSchema, componentSchema);
+
+    HashSet<String> serviceProperties = new HashSet<String>();
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), serviceProperties);
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+    queryTree.addChild(new QueryInfo(new ComponentResourceDefinition(), new HashSet<String>()), "Component");
+
+    DefaultRenderer renderer = new DefaultRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, false);
+
+    assertEquals(1, propertyTree.getChildren().size());
+    assertEquals(2, propertyTree.getObject().size());
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/service_name"));
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/cluster_name"));
+
+
+    TreeNode<Set<String>> componentNode = propertyTree.getChild("Component");
+    assertEquals(0, componentNode.getChildren().size());
+    assertEquals(2, componentNode.getObject().size());
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/service_name"));
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/component_name"));
+
+    verify(schemaFactory, serviceSchema, componentSchema);
+  }
+
+  @Test
+  public void testFinalizeProperties__instance_subResource_properties() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema serviceSchema = createNiceMock(Schema.class);
+    Schema componentSchema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Service)).andReturn(serviceSchema).anyTimes();
+    expect(serviceSchema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceInfo/service_name").anyTimes();
+    expect(serviceSchema.getKeyPropertyId(Resource.Type.Cluster)).andReturn("ServiceInfo/cluster_name").anyTimes();
+    expect(schemaFactory.getSchema(Resource.Type.Component)).andReturn(componentSchema).anyTimes();
+    expect(componentSchema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceComponentInfo/service_name").anyTimes();
+    expect(componentSchema.getKeyPropertyId(Resource.Type.Component)).andReturn("ServiceComponentInfo/component_name").anyTimes();
+
+    replay(schemaFactory, serviceSchema, componentSchema);
+
+    HashSet<String> serviceProperties = new HashSet<String>();
+    serviceProperties.add("foo/bar");
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), serviceProperties);
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+    HashSet<String> componentProperties = new HashSet<String>();
+    componentProperties.add("goo/car");
+    queryTree.addChild(new QueryInfo(new ComponentResourceDefinition(), componentProperties), "Component");
+
+    DefaultRenderer renderer = new DefaultRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, false);
+
+    assertEquals(1, propertyTree.getChildren().size());
+    assertEquals(3, propertyTree.getObject().size());
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/service_name"));
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/cluster_name"));
+    assertTrue(propertyTree.getObject().contains("foo/bar"));
+
+
+    TreeNode<Set<String>> componentNode = propertyTree.getChild("Component");
+    assertEquals(0, componentNode.getChildren().size());
+    assertEquals(3, componentNode.getObject().size());
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/service_name"));
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/component_name"));
+    assertTrue(componentNode.getObject().contains("goo/car"));
+
+    verify(schemaFactory, serviceSchema, componentSchema);
+  }
+
+  @Test
+  public void testFinalizeProperties__collection_subResource_noProperties() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema serviceSchema = createNiceMock(Schema.class);
+    Schema componentSchema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Service)).andReturn(serviceSchema).anyTimes();
+    expect(serviceSchema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceInfo/service_name").anyTimes();
+    expect(serviceSchema.getKeyPropertyId(Resource.Type.Cluster)).andReturn("ServiceInfo/cluster_name").anyTimes();
+    expect(schemaFactory.getSchema(Resource.Type.Component)).andReturn(componentSchema).anyTimes();
+    expect(componentSchema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceComponentInfo/service_name").anyTimes();
+    expect(componentSchema.getKeyPropertyId(Resource.Type.Component)).andReturn("ServiceComponentInfo/component_name").anyTimes();
+
+    replay(schemaFactory, serviceSchema, componentSchema);
+
+    HashSet<String> serviceProperties = new HashSet<String>();
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), serviceProperties);
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+    queryTree.addChild(new QueryInfo(new ComponentResourceDefinition(), new HashSet<String>()), "Component");
+
+    DefaultRenderer renderer = new DefaultRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, true);
+
+    assertEquals(1, propertyTree.getChildren().size());
+    assertEquals(2, propertyTree.getObject().size());
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/service_name"));
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/cluster_name"));
+
+
+    TreeNode<Set<String>> componentNode = propertyTree.getChild("Component");
+    assertEquals(0, componentNode.getChildren().size());
+    assertEquals(2, componentNode.getObject().size());
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/service_name"));
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/component_name"));
+
+    verify(schemaFactory, serviceSchema, componentSchema);
+  }
+
+  @Test
+  public void testFinalizeProperties__collection_subResource_propertiesTopLevelOnly() {
+    SchemaFactory schemaFactory = createNiceMock(SchemaFactory.class);
+    Schema serviceSchema = createNiceMock(Schema.class);
+    Schema componentSchema = createNiceMock(Schema.class);
+
+    // schema expectations
+    expect(schemaFactory.getSchema(Resource.Type.Service)).andReturn(serviceSchema).anyTimes();
+    expect(serviceSchema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceInfo/service_name").anyTimes();
+    expect(serviceSchema.getKeyPropertyId(Resource.Type.Cluster)).andReturn("ServiceInfo/cluster_name").anyTimes();
+    expect(schemaFactory.getSchema(Resource.Type.Component)).andReturn(componentSchema).anyTimes();
+    expect(componentSchema.getKeyPropertyId(Resource.Type.Service)).andReturn("ServiceComponentInfo/service_name").anyTimes();
+    expect(componentSchema.getKeyPropertyId(Resource.Type.Component)).andReturn("ServiceComponentInfo/component_name").anyTimes();
+
+    replay(schemaFactory, serviceSchema, componentSchema);
+
+    HashSet<String> serviceProperties = new HashSet<String>();
+    serviceProperties.add("foo/bar");
+    QueryInfo rootQuery = new QueryInfo(new ServiceResourceDefinition(), serviceProperties);
+    TreeNode<QueryInfo> queryTree = new TreeNodeImpl<QueryInfo>(null, rootQuery, "Service");
+    queryTree.addChild(new QueryInfo(new ComponentResourceDefinition(), new HashSet<String>()), "Component");
+
+    DefaultRenderer renderer = new DefaultRenderer();
+    renderer.init(schemaFactory);
+    TreeNode<Set<String>> propertyTree = renderer.finalizeProperties(queryTree, true);
+
+    assertEquals(1, propertyTree.getChildren().size());
+    assertEquals(3, propertyTree.getObject().size());
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/service_name"));
+    assertTrue(propertyTree.getObject().contains("ServiceInfo/cluster_name"));
+    assertTrue(propertyTree.getObject().contains("foo/bar"));
+
+
+    TreeNode<Set<String>> componentNode = propertyTree.getChild("Component");
+    assertEquals(0, componentNode.getChildren().size());
+    assertEquals(2, componentNode.getObject().size());
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/service_name"));
+    assertTrue(componentNode.getObject().contains("ServiceComponentInfo/component_name"));
+
+    verify(schemaFactory, serviceSchema, componentSchema);
+  }
+
+
+  @Test
+  public void testFinalizeResult() {
+    Result result = createNiceMock(Result.class);
+    DefaultRenderer renderer = new DefaultRenderer();
+
+    assertSame(result, renderer.finalizeResult(result));
+  }
+}


[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

Posted by js...@apache.org.
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('/');