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

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

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));
+  }
+}