Bläddra i källkod

AMBARI-1401 - Provide API support for querying sub-resources

tbeerbower 11 år sedan
förälder
incheckning
e5d225a2db
19 ändrade filer med 1339 tillägg och 52 borttagningar
  1. 205 0
      ambari-server/src/main/java/org/apache/ambari/server/api/query/ExtendedResourcePredicateVisitor.java
  2. 177 0
      ambari-server/src/main/java/org/apache/ambari/server/api/query/ProcessingPredicateVisitor.java
  3. 278 42
      ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryImpl.java
  4. 127 0
      ambari-server/src/main/java/org/apache/ambari/server/api/query/SubResourcePredicateVisitor.java
  5. 8 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/AlwaysPredicate.java
  6. 55 8
      ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/ArrayPredicate.java
  7. 8 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/CategoryIsEmptyPredicate.java
  8. 10 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/ComparisonPredicate.java
  9. 5 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/EqualsPredicate.java
  10. 5 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/GreaterEqualsPredicate.java
  11. 5 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/GreaterPredicate.java
  12. 5 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/LessEqualsPredicate.java
  13. 5 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/LessPredicate.java
  14. 8 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/UnaryPredicate.java
  15. 179 0
      ambari-server/src/test/java/org/apache/ambari/server/api/query/ExtendedResourcePredicateVisitorTest.java
  16. 112 0
      ambari-server/src/test/java/org/apache/ambari/server/api/query/ProcessingPredicateVisitorTest.java
  17. 87 2
      ambari-server/src/test/java/org/apache/ambari/server/api/query/QueryImplTest.java
  18. 59 0
      ambari-server/src/test/java/org/apache/ambari/server/api/query/SubResourcePredicateVisitorTest.java
  19. 1 0
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ClusterControllerImplTest.java

+ 205 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/query/ExtendedResourcePredicateVisitor.java

@@ -0,0 +1,205 @@
+/**
+ * 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.controller.internal.ResourceImpl;
+import org.apache.ambari.server.controller.predicate.AlwaysPredicate;
+import org.apache.ambari.server.controller.predicate.ArrayPredicate;
+import org.apache.ambari.server.controller.predicate.CategoryPredicate;
+import org.apache.ambari.server.controller.predicate.ComparisonPredicate;
+import org.apache.ambari.server.controller.predicate.PredicateVisitor;
+import org.apache.ambari.server.controller.predicate.UnaryPredicate;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.utilities.PredicateHelper;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A predicate visitor used to generate a new predicate so that the resources
+ * referenced by the predicate will be extended to include the joined properties
+ * of their sub-resources.
+ */
+public class ExtendedResourcePredicateVisitor implements PredicateVisitor {
+  /**
+   * The last visited predicate.
+   */
+  private Predicate lastVisited = null;
+
+  /**
+   * The joined resource map.
+   */
+  private final Map<Resource, Set<Map<String, Object>>> joinedResources;
+
+
+  // ----- Constructors ------------------------------------------------------
+
+  /**
+   * Constructor.
+   *
+   * @param extendedProperties the map of sets of extended properties
+   */
+  public ExtendedResourcePredicateVisitor(Map<Resource,
+      Set<Map<String, Object>>> extendedProperties) {
+    this.joinedResources = extendedProperties;
+  }
+
+  // ----- PredicateVisitor --------------------------------------------------
+
+  @Override
+  public void acceptComparisonPredicate(ComparisonPredicate predicate) {
+    lastVisited = new ExtendedResourcePredicate(predicate, joinedResources);
+  }
+
+  @Override
+  public void acceptArrayPredicate(ArrayPredicate arrayPredicate) {
+    List<Predicate> predicateList = new LinkedList<Predicate>();
+
+    Predicate[] predicates = arrayPredicate.getPredicates();
+    if (predicates.length > 0) {
+      for (Predicate predicate : predicates) {
+        PredicateHelper.visit(predicate, this);
+        predicateList.add(lastVisited);
+      }
+    }
+    lastVisited = arrayPredicate.create(predicateList.toArray(new Predicate[predicateList.size()]));
+  }
+
+  @Override
+  public void acceptUnaryPredicate(UnaryPredicate predicate) {
+    lastVisited = new ExtendedResourcePredicate(predicate, joinedResources);
+  }
+
+  @Override
+  public void acceptAlwaysPredicate(AlwaysPredicate predicate) {
+    lastVisited = predicate;
+  }
+
+  @Override
+  public void acceptCategoryPredicate(CategoryPredicate predicate) {
+    lastVisited = new ExtendedResourcePredicate(predicate, joinedResources);
+  }
+
+
+  // ----- utility methods -------------------------------------------------
+
+  /**
+   * Get the extended predicate.
+   *
+   * @return the predicate
+   */
+  public Predicate getExtendedPredicate() {
+    return lastVisited;
+  }
+
+
+  // ----- inner classes -----------------------------------------------------
+
+  // ----- ExtendedResourcePredicate -----------------------------------------
+
+  /**
+   * Predicate implementation used to replace any existing predicate and
+   * extend the resource being evaluated with an extended set of property
+   * values.
+   */
+  private static class ExtendedResourcePredicate implements Predicate {
+
+    /**
+     * The predicate being extended.
+     */
+    private final Predicate predicate;
+
+    /**
+     * The map of extended property sets keyed by resource.
+     */
+    private final Map<Resource, Set<Map<String, Object>>> joinedResources;
+
+
+    // ----- Constructors ----------------------------------------------------
+
+    /**
+     * Constructor
+     *
+     * @param predicate        the predicate being extended
+     * @param joinedResources  the map of extended sets of property values
+     */
+    public ExtendedResourcePredicate(Predicate predicate,
+                                     Map<Resource, Set<Map<String, Object>>> joinedResources) {
+      this.predicate       = predicate;
+      this.joinedResources = joinedResources;
+    }
+
+    // ----- Predicate -------------------------------------------------------
+
+    @Override
+    public boolean evaluate(Resource resource) {
+
+      Set<Map<String, Object>> extendedPropertySet = joinedResources.get(resource);
+
+      if (extendedPropertySet == null) {
+        return predicate.evaluate(resource);
+      }
+
+      for (Map<String, Object> extendedProperties : extendedPropertySet) {
+        Resource extendedResource = new ExtendedResourceImpl(resource, extendedProperties);
+
+        if (predicate.evaluate(extendedResource)) {
+          return true;
+        }
+      }
+      return false;
+    }
+  }
+
+  // ----- ExtendedResourceImpl ----------------------------------------------
+
+  /**
+   * A resource that extends a given resource by copying it and adding additional
+   * properties.
+   */
+  private static class ExtendedResourceImpl extends ResourceImpl {
+
+    // ----- Constructors ----------------------------------------------------
+
+    /**
+     * Constructor
+     *
+     * @param resource            the resource to copy
+     * @param extendedProperties  the map of extended properties
+     */
+    public ExtendedResourceImpl(Resource resource, Map<String, Object> extendedProperties) {
+      super(resource);
+      initProperties(extendedProperties);
+    }
+
+    // ----- utility methods -------------------------------------------------
+
+    /**
+     *  Initialize this resource by setting the extended properties.
+     */
+    private void initProperties(Map<String, Object> extendedProperties) {
+      for (Map.Entry<String, Object> entry : extendedProperties.entrySet()) {
+        setProperty(entry.getKey(), entry.getValue());
+      }
+    }
+  }
+}

+ 177 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/query/ProcessingPredicateVisitor.java

@@ -0,0 +1,177 @@
+/**
+ * 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.controller.predicate.AlwaysPredicate;
+import org.apache.ambari.server.controller.predicate.ArrayPredicate;
+import org.apache.ambari.server.controller.predicate.CategoryPredicate;
+import org.apache.ambari.server.controller.predicate.ComparisonPredicate;
+import org.apache.ambari.server.controller.predicate.PredicateVisitor;
+import org.apache.ambari.server.controller.predicate.UnaryPredicate;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.utilities.PredicateHelper;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A predicate visitor used to process a predicate to do the following :
+ *
+ * 1) Create a new predicate with all sub-resource elements contained in the
+ *    visited predicate removed (converted to AlwaysPredicate).
+ * 2) Create a set of any sub-resource subResourceCategories that were referenced in
+ *    the predicate being visited (i.e. components, host_components, etc).
+ * 3) Create a set of any sub-resource subResourceProperties that were referenced in
+ *    the predicate being visited.
+ *
+ * For example, the service level query the predicate ...
+ *
+ *   ServiceInfo/service_name = HBASE AND
+ *   components/ServiceComponentInfo/category = SLAVE AND
+ *   components/host_components/metrics/cpu/cpu_num >= 1
+ *
+ * ... will produce ...
+ *
+ *   Predicate : ServiceInfo/service_name = HBASE
+ *
+ *   Sub-resource subResourceCategories : {components}
+ *
+ *   Sub-resource subResourceProperties : {components/ServiceComponentInfo/category,
+ *                                         components/host_components/metrics/cpu/cpu_num}
+ */
+public class ProcessingPredicateVisitor implements PredicateVisitor {
+  /**
+   * Associated resource provider.
+   */
+  private final QueryImpl query;
+
+  /**
+   * The last visited predicate.
+   */
+  private Predicate lastVisited = null;
+
+  /**
+   * The set of sub-resource categories.
+   */
+  private final Set<String> subResourceCategories = new HashSet<String>();
+
+  /**
+   * The set of sub-resource properties.
+   */
+  private final Set<String> subResourceProperties = new HashSet<String>();
+
+
+  // ----- Constructors ----------------------------------------------------
+
+  /**
+   * Constructor.
+   *
+   * @param query  associated query
+   */
+  public ProcessingPredicateVisitor(QueryImpl query) {
+    this.query    = query;
+  }
+
+
+  // ----- PredicateVisitor --------------------------------------------------
+
+  @Override
+  public void acceptComparisonPredicate(ComparisonPredicate predicate) {
+
+    String propertyId = predicate.getPropertyId();
+    int    index      = propertyId.indexOf("/");
+    String category   = index == -1 ? propertyId : propertyId.substring(0, index);
+
+    Map<String, QueryImpl> subResources = query.ensureSubResources();
+
+    if (subResources.containsKey(category)) {
+      subResourceCategories.add(category);
+      subResourceProperties.add(propertyId);
+      lastVisited = AlwaysPredicate.INSTANCE;
+    }
+    else {
+      lastVisited = predicate;
+    }
+  }
+
+  @Override
+  public void acceptArrayPredicate(ArrayPredicate arrayPredicate) {
+    List<Predicate> predicateList = new LinkedList<Predicate>();
+
+    Predicate[] predicates = arrayPredicate.getPredicates();
+    if (predicates.length > 0) {
+      for (Predicate predicate : predicates) {
+        PredicateHelper.visit(predicate, this);
+        predicateList.add(lastVisited);
+      }
+    }
+    lastVisited = arrayPredicate.create(predicateList.toArray(new Predicate[predicateList.size()]));
+  }
+
+  @Override
+  public void acceptUnaryPredicate(UnaryPredicate predicate) {
+    lastVisited = predicate;
+  }
+
+  @Override
+  public void acceptAlwaysPredicate(AlwaysPredicate predicate) {
+    lastVisited = predicate;
+  }
+
+  @Override
+  public void acceptCategoryPredicate(CategoryPredicate predicate) {
+    lastVisited = predicate;
+  }
+
+
+  // ----- utility methods -------------------------------------------------
+
+  /**
+   * Get a new predicate with all sub-resource elements contained in the
+   * visited predicate removed.
+   *
+   * @return the new predicate
+   */
+  public Predicate getProcessedPredicate() {
+    return lastVisited;
+  }
+
+  /**
+   * Get a set of any sub-resource subResourceCategories that were referenced
+   * in the predicate that was visited.
+   *
+   * @return the set of sub-resource categories
+   */
+  public Set<String> getSubResourceCategories() {
+    return subResourceCategories;
+  }
+
+  /**
+   * Get a set of any sub-resource subResourceProperties that were referenced
+   * in the predicate that was visited.
+   *
+   * @return the set of sub-resource properties
+   */
+  public Set<String> getSubResourceProperties() {
+    return subResourceProperties;
+  }
+}

+ 278 - 42
ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryImpl.java

@@ -24,6 +24,7 @@ import org.apache.ambari.server.api.resources.ResourceInstanceFactoryImpl;
 import org.apache.ambari.server.api.resources.SubResourceDefinition;
 import org.apache.ambari.server.api.resources.SubResourceDefinition;
 import org.apache.ambari.server.api.services.ResultImpl;
 import org.apache.ambari.server.api.services.ResultImpl;
 import org.apache.ambari.server.controller.internal.ResourceImpl;
 import org.apache.ambari.server.controller.internal.ResourceImpl;
+import org.apache.ambari.server.controller.utilities.PredicateHelper;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.apache.ambari.server.controller.predicate.AndPredicate;
 import org.apache.ambari.server.controller.predicate.AndPredicate;
 import org.apache.ambari.server.controller.predicate.EqualsPredicate;
 import org.apache.ambari.server.controller.predicate.EqualsPredicate;
@@ -102,6 +103,11 @@ public class QueryImpl implements Query, ResourceInstance {
    */
    */
   private boolean minimal;
   private boolean minimal;
 
 
+  /**
+   * The sub resource properties referenced in the user predicate.
+   */
+  private final Set<String> subResourcePredicateProperties = new HashSet<String>();
+
   /**
   /**
    * The logger.
    * The logger.
    */
    */
@@ -263,7 +269,7 @@ public class QueryImpl implements Query, ResourceInstance {
   /**
   /**
    * Get the map of sub-resources.  Lazily create the map if required.  
    * Get the map of sub-resources.  Lazily create the map if required.  
    */
    */
-  private Map<String, QueryImpl> ensureSubResources() {
+  protected Map<String, QueryImpl> ensureSubResources() {
     if (subResourceSet == null) {
     if (subResourceSet == null) {
       subResourceSet = new HashMap<String, QueryImpl>();
       subResourceSet = new HashMap<String, QueryImpl>();
       Set<SubResourceDefinition> setSubResourceDefs =
       Set<SubResourceDefinition> setSubResourceDefs =
@@ -312,29 +318,28 @@ public class QueryImpl implements Query, ResourceInstance {
 
 
     Set<Resource> providerResourceSet = new HashSet<Resource>();
     Set<Resource> providerResourceSet = new HashSet<Resource>();
 
 
-    Resource.Type resourceType = getResourceDefinition().getType();
-    Request       request      = createRequest(!minimal);
-    Request       qRequest     = createRequest(true);
-    Predicate     predicate    = getPredicate();
-
-    Set<Resource> resourceSet = new LinkedHashSet<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, predicate)) {
+    for (Resource queryResource : doQuery(resourceType, qRequest, queryPredicate)) {
       providerResourceSet.add(queryResource);
       providerResourceSet.add(queryResource);
       resourceSet.add(queryResource);
       resourceSet.add(queryResource);
     }
     }
-    queryResults.put(null, new QueryResult(request, predicate, getKeyValueMap(), resourceSet));
+    queryResults.put(null,
+        new QueryResult(request, queryPredicate, userPredicate, getKeyValueMap(), resourceSet));
 
 
-    clusterController.populateResources(resourceType, providerResourceSet, qRequest, predicate);
-    queryForSubResources();
+    clusterController.populateResources(resourceType, providerResourceSet, qRequest, queryPredicate);
+    queryForSubResources(userPredicate, hasSubResourcePredicate());
   }
   }
 
 
   /**
   /**
    * Query the cluster controller for the sub-resources associated with 
    * Query the cluster controller for the sub-resources associated with 
-   * the given resources.  All the sub-resources of the same type should
-   * be acquired with a single query.
+   * this query object.
    */
    */
-  private void queryForSubResources()
+  private void queryForSubResources(Predicate predicate, boolean hasSubResourcePredicate)
       throws UnsupportedPropertyException,
       throws UnsupportedPropertyException,
       SystemException,
       SystemException,
       NoSuchResourceException,
       NoSuchResourceException,
@@ -347,6 +352,11 @@ public class QueryImpl implements Query, ResourceInstance {
       Request       request      = subResource.createRequest(!minimal);
       Request       request      = subResource.createRequest(!minimal);
       Request       qRequest     = subResource.createRequest(true);
       Request       qRequest     = subResource.createRequest(true);
 
 
+      Predicate subResourcePredicate = hasSubResourcePredicate ?
+          getSubResourcePredicate(predicate, entry.getKey()) : null;
+
+      Predicate processedPredicate = hasSubResourcePredicate ? subResource.processUserPredicate(subResourcePredicate) : null;
+
       Set<Resource> providerResourceSet = new HashSet<Resource>();
       Set<Resource> providerResourceSet = new HashSet<Resource>();
 
 
       for (QueryResult queryResult : queryResults.values()) {
       for (QueryResult queryResult : queryResults.values()) {
@@ -354,19 +364,24 @@ public class QueryImpl implements Query, ResourceInstance {
 
 
           Map<Resource.Type, String> map = getKeyValueMap(resource, queryResult.getKeyValueMap());
           Map<Resource.Type, String> map = getKeyValueMap(resource, queryResult.getKeyValueMap());
 
 
-          Predicate predicate = subResource.createPredicate(map);
+          Predicate queryPredicate = subResource.createPredicate(map, processedPredicate);
 
 
           Set<Resource> resourceSet = new LinkedHashSet<Resource>();
           Set<Resource> resourceSet = new LinkedHashSet<Resource>();
 
 
-          for (Resource queryResource : subResource.doQuery(resourceType, qRequest, predicate)) {
-            providerResourceSet.add(queryResource);
-            resourceSet.add(queryResource);
+          try {
+            for (Resource queryResource : subResource.doQuery(resourceType, qRequest, queryPredicate)) {
+              providerResourceSet.add(queryResource);
+              resourceSet.add(queryResource);
+            }
+          } catch (NoSuchResourceException e) {
+            // do nothing ...
           }
           }
-          subResource.queryResults.put(resource, new QueryResult(request, predicate, map, resourceSet));
+          subResource.queryResults.put(resource,
+              new QueryResult(request, queryPredicate, subResourcePredicate, map, resourceSet));
         }
         }
       }
       }
       clusterController.populateResources(resourceType, providerResourceSet, qRequest, null);
       clusterController.populateResources(resourceType, providerResourceSet, qRequest, null);
-      subResource.queryForSubResources();
+      subResource.queryForSubResources(subResourcePredicate, hasSubResourcePredicate);
     }
     }
   }
   }
 
 
@@ -391,8 +406,166 @@ public class QueryImpl implements Query, ResourceInstance {
   }
   }
 
 
   /**
   /**
-   * Get a result from this query.
+   * Get a map of property sets keyed by the resources associated with this query.
+   * The property sets should contain the joined sets of all of the requested
+   * properties from each resource's sub-resources.
+   *
+   * For example, if this query is associated with the resources
+   * AResource1, AResource1 and AResource3 as follows ...
+   *
+   * a_resources
+   * │
+   * └──AResource1 ─────────────AResource1 ─────────────AResource3
+   *      │                       │                       │
+   *      ├── b_resources         ├── b_resources         ├── BResources
+   *      │   ├── BResource1      │   ├── BResource3      │    └── BResource5
+   *      │   │     p1:1          │   │     p1:3          │          p1:5
+   *      │   │     p2:5          │   │     p2:5          │          p2:5
+   *      │   │                   │   │                   │
+   *      │   └── BResource2      │   └── BResource4      └── c_resources
+   *      │         p1:2          │         p1:4              └── CResource4
+   *      │         p2:0          │         p2:0                    p3:4
+   *      │                       │
+   *      └── c_resources         └── c_resources
+   *          ├── CResource1          └── CResource3
+   *          │     p3:1                    p3:3
+   *          │
+   *          └── CResource2
+   *                p3:2
+   *
+   * Given the following query ...
+   *
+   *     api/v1/a_resources?b_resources/p1>3&b_resources/p2=5&c_resources/p3=1
+   *
+   * The caller should pass the following property ids ...
+   *
+   *     b_resources/p1
+   *     b_resources/p2
+   *     c_resources/p3
+   *
+   * getJoinedResourceProperties should produce the following map of property sets
+   * by making recursive calls on the sub-resources of each of this query's resources,
+   * joining the resulting property sets, and adding them to the map keyed by the
+   * resource ...
+   *
+   *  {
+   *    AResource1=[{b_resources/p1=1, b_resources/p2=5, c_resources/p3=1},
+   *                {b_resources/p1=2, b_resources/p2=0, c_resources/p3=1},
+   *                {b_resources/p1=1, b_resources/p2=5, c_resources/p3=2},
+   *                {b_resources/p1=2, b_resources/p2=0, c_resources/p3=2}],
+   *    AResource2=[{b_resources/p1=3, b_resources/p2=5, c_resources/p3=3},
+   *                {b_resources/p1=4, b_resources/p2=0, c_resources/p3=3}],
+   *    AResource3=[{b_resources/p1=5, b_resources/p2=5, c_resources/p3=4}],
+   *  }
+   *
+   * @param propertyIds     the requested properties
+   * @param parentResource  the parent resource; may be null
+   * @param category        the sub-resource category; may be null
+   *
+   * @return a map of property sets keyed by the resources associated with this query
    */
    */
+  protected Map<Resource, Set<Map<String, Object>>> getJoinedResourceProperties(Set<String> propertyIds,
+                                                                                Resource parentResource,
+                                                                                String category)
+      throws SystemException, UnsupportedPropertyException, NoSuchParentResourceException, NoSuchResourceException {
+
+    Map<Resource, Set<Map<String, Object>>> resourcePropertyMaps =
+        new HashMap<Resource, Set<Map<String, Object>>>();
+
+    Map<String, String> categoryPropertyIdMap =
+        getPropertyIdsForCategory(propertyIds, category);
+
+    for (Map.Entry<Resource, QueryResult> queryResultEntry : queryResults.entrySet()) {
+      QueryResult queryResult         = queryResultEntry.getValue();
+      Resource    queryParentResource = queryResultEntry.getKey();
+
+      // for each resource for the given parent ...
+      if (queryParentResource == parentResource) {
+
+        Iterable<Resource> iterResource = clusterController.getIterable(
+            resourceDefinition.getType(), queryResult.getProviderResourceSet(),
+            queryResult.getRequest(), queryResult.getPredicate());
+
+        for (Resource resource : iterResource) {
+          // get the resource properties
+          Map<String, Object> resourcePropertyMap = new HashMap<String, Object>();
+          for (Map.Entry<String, String> categoryPropertyIdEntry : categoryPropertyIdMap.entrySet()) {
+            Object value = resource.getPropertyValue(categoryPropertyIdEntry.getValue());
+            if (value != null) {
+              resourcePropertyMap.put(categoryPropertyIdEntry.getKey(), value);
+            }
+          }
+
+          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()) {
+            String subResourceCategory = category == null ? entry.getKey() : category + "/" + entry.getKey();
+
+            QueryImpl subResource = entry.getValue();
+
+            Map<Resource, Set<Map<String, Object>>> subResourcePropertyMaps =
+                subResource.getJoinedResourceProperties(propertyIds, resource, subResourceCategory);
+
+            Set<Map<String, Object>> combinedSubResourcePropertyMaps = new HashSet<Map<String, Object>>();
+            for (Set<Map<String, Object>> maps : subResourcePropertyMaps.values()) {
+              combinedSubResourcePropertyMaps.addAll(maps);
+            }
+            propertyMaps = joinPropertyMaps(propertyMaps, combinedSubResourcePropertyMaps);
+          }
+          // add parent resource properties to joinedResources
+          if (!resourcePropertyMap.isEmpty()) {
+            if (propertyMaps.isEmpty()) {
+              propertyMaps.add(resourcePropertyMap);
+            } else {
+              for (Map<String, Object> propertyMap : propertyMaps) {
+                propertyMap.putAll(resourcePropertyMap);
+              }
+            }
+          }
+          resourcePropertyMaps.put(resource, propertyMaps);
+        }
+      }
+    }
+    return resourcePropertyMaps;
+  }
+
+  // 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) {
+    Map<String, String> map = new HashMap<String, String>();
+
+    for (String propertyId : propertyIds) {
+      if (category == null || propertyId.startsWith(category)) {
+        map.put(propertyId, category==null ? propertyId : propertyId.substring(category.length() + 1));
+      }
+    }
+    return map;
+  }
+
+  // Join two sets of property maps into one.
+  private static Set<Map<String, Object>> joinPropertyMaps(Set<Map<String, Object>> propertyMaps1,
+                                                           Set<Map<String, Object>> propertyMaps2) {
+    Set<Map<String, Object>> propertyMaps = new HashSet<Map<String, Object>>();
+
+    if (propertyMaps1.isEmpty()) {
+      return propertyMaps2;
+    }
+    if (propertyMaps2.isEmpty()) {
+      return propertyMaps1;
+    }
+
+    for (Map<String, Object> map1 : propertyMaps1) {
+      for (Map<String, Object> map2 : propertyMaps2) {
+        Map<String, Object> joinedMap = new HashMap<String, Object>(map1);
+        joinedMap.putAll(map2);
+        propertyMaps.add(joinedMap);
+      }
+    }
+    return propertyMaps;
+  }
+
+   // Get a result from this query.
   private Result getResult(Resource parentResource)
   private Result getResult(Resource parentResource)
       throws UnsupportedPropertyException, SystemException, NoSuchResourceException, NoSuchParentResourceException {
       throws UnsupportedPropertyException, SystemException, NoSuchResourceException, NoSuchParentResourceException {
 
 
@@ -407,23 +580,28 @@ public class QueryImpl implements Query, ResourceInstance {
     QueryResult queryResult = queryResults.get(parentResource);
     QueryResult queryResult = queryResults.get(parentResource);
 
 
     if (queryResult != null) {
     if (queryResult != null) {
-      Predicate predicate = queryResult.getPredicate();
-      Request   request   = queryResult.getRequest();
-
-      Iterable<Resource> iterResource;
+      Predicate queryPredicate     = queryResult.getPredicate();
+      Predicate queryUserPredicate = queryResult.getUserPredicate();
+      Request   queryRequest       = queryResult.getRequest();
 
 
       Set<Resource> providerResourceSet = queryResult.getProviderResourceSet();
       Set<Resource> providerResourceSet = queryResult.getProviderResourceSet();
 
 
+      if (hasSubResourcePredicate() && queryUserPredicate != null) {
+        queryPredicate = getExtendedPredicate(parentResource, queryUserPredicate);
+      }
+
+      Iterable<Resource> iterResource;
+
       if (pageRequest == null) {
       if (pageRequest == null) {
         iterResource = clusterController.getIterable(
         iterResource = clusterController.getIterable(
-            resourceType, providerResourceSet, request, predicate);
+            resourceType, providerResourceSet, queryRequest, queryPredicate);
       } else {
       } else {
         PageResponse pageResponse = clusterController.getPage(
         PageResponse pageResponse = clusterController.getPage(
-            resourceType, providerResourceSet, request, predicate, pageRequest);
+            resourceType, providerResourceSet, queryRequest, queryPredicate, pageRequest);
         iterResource = pageResponse.getIterable();
         iterResource = pageResponse.getIterable();
       }
       }
 
 
-      Set<String> propertyIds = request.getPropertyIds();
+      Set<String> propertyIds = queryRequest.getPropertyIds();
 
 
       int count = 1;
       int count = 1;
       for (Resource resource : iterResource) {
       for (Resource resource : iterResource) {
@@ -447,6 +625,32 @@ public class QueryImpl implements Query, ResourceInstance {
     return result;
     return result;
   }
   }
 
 
+  // Indicates whether or not this query has sub-resource elements
+  // in its predicate.
+  private boolean hasSubResourcePredicate() {
+    return !subResourcePredicateProperties.isEmpty();
+  }
+
+  // Alter the given predicate so that the resources referenced by
+  // the predicate will be extended to include the joined properties
+  // of their sub-resources.
+  private Predicate getExtendedPredicate(Resource parentResource,
+                                         Predicate predicate)
+      throws SystemException,
+             UnsupportedPropertyException,
+             NoSuchParentResourceException,
+             NoSuchResourceException {
+
+    Map<Resource, Set<Map<String, Object>>> joinedResources =
+        getJoinedResourceProperties(subResourcePredicateProperties, parentResource, null);
+
+    ExtendedResourcePredicateVisitor visitor =
+        new ExtendedResourcePredicateVisitor(joinedResources);
+
+    PredicateHelper.visit(predicate, visitor);
+    return visitor.getExtendedPredicate();
+  }
+
   private void addKeyProperties(Resource.Type resourceType, boolean includeFKs) {
   private void addKeyProperties(Resource.Type resourceType, boolean includeFKs) {
     Schema schema = clusterController.getSchema(resourceType);
     Schema schema = clusterController.getSchema(resourceType);
 
 
@@ -522,21 +726,47 @@ public class QueryImpl implements Query, ResourceInstance {
   }
   }
 
 
   private Predicate createPredicate() {
   private Predicate createPredicate() {
-    return createPredicate(getKeyValueMap());
+    return createPredicate(getKeyValueMap(), userPredicate);
   }
   }
 
 
-  private Predicate createPredicate(Map<Resource.Type, String> keyValueMap) {
-    Predicate predicate = null;
+  private Predicate createPredicate(Map<Resource.Type, String> keyValueMap, Predicate predicate) {
     Predicate internalPredicate = createInternalPredicate(keyValueMap);
     Predicate internalPredicate = createInternalPredicate(keyValueMap);
+
     if (internalPredicate == null) {
     if (internalPredicate == null) {
-      if (userPredicate != null) {
-        predicate = userPredicate;
-      }
-    } else {
-      predicate = (userPredicate == null ? internalPredicate :
-          new AndPredicate(userPredicate, internalPredicate));
+        return predicate;
+    }
+    return (predicate == null ? internalPredicate :
+          new AndPredicate(predicate, internalPredicate));
+  }
+
+  // Get a sub-resource predicate from the given predicate.
+  private Predicate getSubResourcePredicate(Predicate predicate, String category) {
+    if (predicate == null) {
+      return null;
+    }
+
+    SubResourcePredicateVisitor visitor = new SubResourcePredicateVisitor(category);
+    PredicateHelper.visit(predicate, visitor);
+    return visitor.getSubResourcePredicate();
+  }
+
+  // Process the given predicate to remove sub-resource elements.
+  private Predicate processUserPredicate(Predicate predicate) {
+    if (predicate == null) {
+      return null;
     }
     }
-    return predicate;
+    ProcessingPredicateVisitor visitor = new ProcessingPredicateVisitor(this);
+    PredicateHelper.visit(predicate, visitor);
+
+    // add the sub-resource to the request
+    Set<String> categories = visitor.getSubResourceCategories();
+    for (String category : categories) {
+      addPropertyToSubResource(category, null);
+    }
+    // record the sub-resource properties on this query
+    subResourcePredicateProperties.addAll(visitor.getSubResourceProperties());
+
+    return visitor.getProcessedPredicate();
   }
   }
 
 
   private Request createRequest(boolean includeFKs) {
   private Request createRequest(boolean includeFKs) {
@@ -600,17 +830,19 @@ public class QueryImpl implements Query, ResourceInstance {
   private static class QueryResult {
   private static class QueryResult {
     private final Request request;
     private final Request request;
     private final Predicate predicate;
     private final Predicate predicate;
+    private final Predicate userPredicate;
     private final Map<Resource.Type, String> keyValueMap;
     private final Map<Resource.Type, String> keyValueMap;
     private final Set<Resource> providerResourceSet;
     private final Set<Resource> providerResourceSet;
 
 
     // ----- Constructor -----------------------------------------------------
     // ----- Constructor -----------------------------------------------------
 
 
     private QueryResult(Request request, Predicate predicate,
     private QueryResult(Request request, Predicate predicate,
-                        Map<Resource.Type, String> keyValueMap,
+                        Predicate userPredicate, Map<Resource.Type, String> keyValueMap,
                         Set<Resource> providerResourceSet) {
                         Set<Resource> providerResourceSet) {
-      this.request = request;
-      this.predicate = predicate;
-      this.keyValueMap = keyValueMap;
+      this.request             = request;
+      this.predicate           = predicate;
+      this.userPredicate       = userPredicate;
+      this.keyValueMap         = keyValueMap;
       this.providerResourceSet = providerResourceSet;
       this.providerResourceSet = providerResourceSet;
     }
     }
 
 
@@ -624,6 +856,10 @@ public class QueryImpl implements Query, ResourceInstance {
       return predicate;
       return predicate;
     }
     }
 
 
+    public Predicate getUserPredicate() {
+      return userPredicate;
+    }
+
     public Map<Resource.Type, String> getKeyValueMap() {
     public Map<Resource.Type, String> getKeyValueMap() {
       return keyValueMap;
       return keyValueMap;
     }
     }

+ 127 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/query/SubResourcePredicateVisitor.java

@@ -0,0 +1,127 @@
+/**
+ * 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.controller.predicate.AlwaysPredicate;
+import org.apache.ambari.server.controller.predicate.ArrayPredicate;
+import org.apache.ambari.server.controller.predicate.CategoryPredicate;
+import org.apache.ambari.server.controller.predicate.ComparisonPredicate;
+import org.apache.ambari.server.controller.predicate.PredicateVisitor;
+import org.apache.ambari.server.controller.predicate.UnaryPredicate;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.utilities.PredicateHelper;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A predicate visitor used to extract the sub resource elements from a given predicate
+ * and produce a new predicate that can be evaluated with a sub resource of the
+ * given category.  For example, given the category 'components' the predicate ...
+ *
+ *   ServiceInfo/service_name = HBASE AND
+ *   components/ServiceComponentInfo/category = SLAVE AND
+ *   components/host_components/metrics/cpu/cpu_num >= 1
+ *
+ * ... will produce the new predicate ...
+ *
+ *   ServiceComponentInfo/category = SLAVE AND
+ *   host_components/metrics/cpu/cpu_num >= 1
+ */
+public class SubResourcePredicateVisitor implements PredicateVisitor {
+  /**
+   * The last visited predicate.
+   */
+  private Predicate lastVisited = null;
+
+  /**
+   * The sub resource category (i.e components, host_components, etc).
+   */
+  private final String category;
+
+
+  //----- Constructors -------------------------------------------------------
+
+  /**
+   * Constructor.
+   */
+  public SubResourcePredicateVisitor(String category) {
+    this.category = category;
+  }
+
+
+  // ----- PredicateVisitor --------------------------------------------------
+
+  @Override
+  public void acceptComparisonPredicate(ComparisonPredicate predicate) {
+
+    String propertyId = predicate.getPropertyId();
+
+    int    index    = propertyId.indexOf("/");
+    String category = index == -1 ? propertyId : propertyId.substring(0, index);
+
+    if(index > -1 && category.equals(this.category)) {
+      // copy and strip off category
+      lastVisited = predicate.copy(propertyId.substring(index + 1));
+    } else {
+      lastVisited = AlwaysPredicate.INSTANCE;
+    }
+  }
+
+  @Override
+  public void acceptArrayPredicate(ArrayPredicate arrayPredicate) {
+    List<Predicate> predicateList = new LinkedList<Predicate>();
+
+    Predicate[] predicates = arrayPredicate.getPredicates();
+    if (predicates.length > 0) {
+      for (Predicate predicate : predicates) {
+        PredicateHelper.visit(predicate, this);
+        predicateList.add(lastVisited);
+      }
+    }
+    lastVisited = arrayPredicate.create(predicateList.toArray(new Predicate[predicateList.size()]));
+  }
+
+  @Override
+  public void acceptUnaryPredicate(UnaryPredicate predicate) {
+    lastVisited = predicate;
+  }
+
+  @Override
+  public void acceptAlwaysPredicate(AlwaysPredicate predicate) {
+    lastVisited = predicate;
+  }
+
+  @Override
+  public void acceptCategoryPredicate(CategoryPredicate predicate) {
+    lastVisited = predicate;
+  }
+
+  // ----- utility methods -------------------------------------------------
+
+  /**
+   * Obtain a predicate that can be evaluated with sub resources
+   * belonging to the associated category.
+   *
+   * @return a sub resource predicate
+   */
+  public Predicate getSubResourcePredicate() {
+    return lastVisited;
+  }
+}

+ 8 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/AlwaysPredicate.java

@@ -43,4 +43,12 @@ public class AlwaysPredicate implements BasePredicate {
   public void accept(PredicateVisitor visitor) {
   public void accept(PredicateVisitor visitor) {
     visitor.acceptAlwaysPredicate(this);
     visitor.acceptAlwaysPredicate(this);
   }
   }
+
+
+  // ----- Object overrides --------------------------------------------------
+
+  @Override
+  public String toString() {
+    return "TRUE";
+  }
 }
 }

+ 55 - 8
ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/ArrayPredicate.java

@@ -32,6 +32,13 @@ public abstract class ArrayPredicate implements BasePredicate {
   private final Predicate[] predicates;
   private final Predicate[] predicates;
   private final Set<String> propertyIds = new HashSet<String>();
   private final Set<String> propertyIds = new HashSet<String>();
 
 
+  // ----- Constructors -----------------------------------------------------
+
+  /**
+   * Constructor.
+   *
+   * @param predicates  the predicates
+   */
   public ArrayPredicate(Predicate... predicates) {
   public ArrayPredicate(Predicate... predicates) {
     this.predicates = predicates;
     this.predicates = predicates;
     for (Predicate predicate : predicates) {
     for (Predicate predicate : predicates) {
@@ -39,6 +46,27 @@ public abstract class ArrayPredicate implements BasePredicate {
     }
     }
   }
   }
 
 
+
+  // ----- BasePredicate ----------------------------------------------------
+
+  @Override
+  public Set<String> getPropertyIds() {
+    return propertyIds;
+  }
+
+
+  // ----- PredicateVisitorAcceptor -----------------------------------------
+
+  @Override
+  public void accept(PredicateVisitor visitor) {
+    visitor.acceptArrayPredicate(this);
+  }
+
+
+  // ----- ArrayPredicate ---------------------------------------------------
+
+  public abstract String getOperator();
+
   /**
   /**
    * Factory method.
    * Factory method.
    *
    *
@@ -49,14 +77,19 @@ public abstract class ArrayPredicate implements BasePredicate {
   public abstract Predicate create(Predicate... predicates);
   public abstract Predicate create(Predicate... predicates);
 
 
 
 
+  // ----- accessors --------------------------------------------------------
+
+  /**
+   * Get the predicates.
+   *
+   * @return the predicates
+   */
   public Predicate[] getPredicates() {
   public Predicate[] getPredicates() {
     return predicates;
     return predicates;
   }
   }
 
 
-  @Override
-  public Set<String> getPropertyIds() {
-    return propertyIds;
-  }
+
+  // ----- Object overrides --------------------------------------------------
 
 
   @Override
   @Override
   public boolean equals(Object o) {
   public boolean equals(Object o) {
@@ -82,9 +115,23 @@ public abstract class ArrayPredicate implements BasePredicate {
   }
   }
 
 
   @Override
   @Override
-  public void accept(PredicateVisitor visitor) {
-    visitor.acceptArrayPredicate(this);
-  }
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
 
 
-  public abstract String getOperator();
+    for (Predicate predicate : predicates) {
+
+      boolean arrayPredicate = predicate instanceof ArrayPredicate;
+
+      if (sb.length() > 0) {
+        sb.append(" ").append(getOperator()).append(" ");
+      }
+
+      if (arrayPredicate) {
+        sb.append("(").append(predicate).append(")");
+      } else {
+        sb.append(predicate);
+      }
+    }
+    return sb.toString();
+  }
 }
 }

+ 8 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/CategoryIsEmptyPredicate.java

@@ -46,4 +46,12 @@ public class CategoryIsEmptyPredicate extends CategoryPredicate {
     Map<String, Object> properties = resource.getPropertiesMap().get(propertyId);
     Map<String, Object> properties = resource.getPropertiesMap().get(propertyId);
     return properties == null ? true : properties.isEmpty();
     return properties == null ? true : properties.isEmpty();
   }
   }
+
+
+  // ----- Object overrides --------------------------------------------------
+
+  @Override
+  public String toString() {
+    return "isEmpty(" + getPropertyId() + ")";
+  }
 }
 }

+ 10 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/ComparisonPredicate.java

@@ -105,4 +105,14 @@ public abstract class ComparisonPredicate<T> extends PropertyPredicate implement
   }
   }
 
 
   public abstract String getOperator();
   public abstract String getOperator();
+
+  public abstract ComparisonPredicate<T> copy(String propertyId);
+
+
+  // ----- Object overrides --------------------------------------------------
+
+  @Override
+  public String toString() {
+    return getPropertyId() + getOperator() + getValue();
+  }
 }
 }

+ 5 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/EqualsPredicate.java

@@ -43,4 +43,9 @@ public class EqualsPredicate<T> extends ComparisonPredicate<T> {
   public String getOperator() {
   public String getOperator() {
     return "=";
     return "=";
   }
   }
+
+  @Override
+  public ComparisonPredicate<T> copy(String propertyId) {
+    return new EqualsPredicate<T>(propertyId, getValue());
+  }
 }
 }

+ 5 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/GreaterEqualsPredicate.java

@@ -50,4 +50,9 @@ public class GreaterEqualsPredicate<T> extends ComparisonPredicate<T> {
   public String getOperator() {
   public String getOperator() {
     return ">=";
     return ">=";
   }
   }
+
+  @Override
+  public ComparisonPredicate<T> copy(String propertyId) {
+    return new GreaterEqualsPredicate<T>(propertyId, getValue());
+  }
 }
 }

+ 5 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/GreaterPredicate.java

@@ -49,4 +49,9 @@ public class GreaterPredicate<T> extends ComparisonPredicate<T> {
   public String getOperator() {
   public String getOperator() {
     return ">";
     return ">";
   }
   }
+
+  @Override
+  public ComparisonPredicate<T> copy(String propertyId) {
+    return new GreaterPredicate<T>(propertyId, getValue());
+  }
 }
 }

+ 5 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/LessEqualsPredicate.java

@@ -50,4 +50,9 @@ public class LessEqualsPredicate<T> extends ComparisonPredicate<T> {
   public String getOperator() {
   public String getOperator() {
     return "<=";
     return "<=";
   }
   }
+
+  @Override
+  public ComparisonPredicate<T> copy(String propertyId) {
+    return new LessEqualsPredicate<T>(propertyId, getValue());
+  }
 }
 }

+ 5 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/LessPredicate.java

@@ -49,4 +49,9 @@ public class LessPredicate<T> extends ComparisonPredicate<T> {
   public String getOperator() {
   public String getOperator() {
     return "<";
     return "<";
   }
   }
+
+  @Override
+  public ComparisonPredicate<T> copy(String propertyId) {
+    return new LessPredicate<T>(propertyId, getValue());
+  }
 }
 }

+ 8 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/predicate/UnaryPredicate.java

@@ -63,4 +63,12 @@ public abstract class UnaryPredicate implements BasePredicate {
   }
   }
 
 
   public abstract String getOperator();
   public abstract String getOperator();
+
+
+  // ----- Object overrides --------------------------------------------------
+
+  @Override
+  public String toString() {
+    return getOperator() + "(" + getPredicate() + ")";
+  }
 }
 }

+ 179 - 0
ambari-server/src/test/java/org/apache/ambari/server/api/query/ExtendedResourcePredicateVisitorTest.java

@@ -0,0 +1,179 @@
+/**
+ * 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.controller.internal.ResourceImpl;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.utilities.PredicateBuilder;
+import org.apache.ambari.server.controller.utilities.PredicateHelper;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * ExtendedResourcePredicateVisitor tests.
+ */
+public class ExtendedResourcePredicateVisitorTest {
+  @Test
+  public void testGetExtendedPredicate() throws Exception {
+
+    Resource resource1 = new ResourceImpl(Resource.Type.Service);
+    resource1.setProperty("name", "service1");
+    Resource resource2 = new ResourceImpl(Resource.Type.Service);
+    resource2.setProperty("name", "service2");
+
+    Map<String, Object> resource1SubProperties1 = new HashMap<String, Object>();
+    resource1SubProperties1.put("sub1/category/p1", 1);
+    resource1SubProperties1.put("sub1/category/p2", 2);
+    resource1SubProperties1.put("sub1/category/p3", 3);
+
+    Map<String, Object> resource1SubProperties2 = new HashMap<String, Object>();
+    resource1SubProperties2.put("sub1/category/p1", 1);
+    resource1SubProperties2.put("sub1/category/p2", 4);
+    resource1SubProperties2.put("sub1/category/p3", 6);
+
+    Map<String, Object> resource1SubProperties3 = new HashMap<String, Object>();
+    resource1SubProperties3.put("sub1/category/p1", 1);
+    resource1SubProperties3.put("sub1/category/p2", 8);
+    resource1SubProperties3.put("sub1/category/p3", 12);
+
+    Set<Map<String, Object>> resource1SubPropertiesSet = new HashSet<Map<String, Object>>();
+
+    resource1SubPropertiesSet.add(resource1SubProperties1);
+    resource1SubPropertiesSet.add(resource1SubProperties2);
+    resource1SubPropertiesSet.add(resource1SubProperties3);
+
+    Map<String, Object> resource2SubProperties1 = new HashMap<String, Object>();
+    resource2SubProperties1.put("sub1/category/p1", 2);
+    resource2SubProperties1.put("sub1/category/p2", 2);
+    resource2SubProperties1.put("sub1/category/p3", 3);
+
+    Map<String, Object> resource2SubProperties2 = new HashMap<String, Object>();
+    resource2SubProperties2.put("sub1/category/p1", 2);
+    resource2SubProperties2.put("sub1/category/p2", 4);
+    resource2SubProperties2.put("sub1/category/p3", 6);
+
+    Map<String, Object> resource2SubProperties3 = new HashMap<String, Object>();
+    resource2SubProperties3.put("sub1/category/p1", 2);
+    resource2SubProperties3.put("sub1/category/p2", 8);
+    resource2SubProperties3.put("sub1/category/p3", 12);
+
+    Set<Map<String, Object>> resource2SubPropertiesSet = new HashSet<Map<String, Object>>();
+
+    resource2SubPropertiesSet.add(resource2SubProperties1);
+    resource2SubPropertiesSet.add(resource2SubProperties2);
+    resource2SubPropertiesSet.add(resource2SubProperties3);
+
+    Map<Resource, Set<Map<String, Object>>> extendedPropertyMap = new HashMap<Resource, Set<Map<String, Object>>>();
+
+    extendedPropertyMap.put(resource1, resource1SubPropertiesSet);
+    extendedPropertyMap.put(resource2, resource2SubPropertiesSet);
+
+    Predicate predicate = new PredicateBuilder().
+        property("sub1/category/p1").equals(1).toPredicate();
+
+    ExtendedResourcePredicateVisitor visitor = new ExtendedResourcePredicateVisitor(extendedPropertyMap);
+    PredicateHelper.visit(predicate, visitor);
+
+    Predicate extendedPredicate = visitor.getExtendedPredicate();
+
+    Assert.assertTrue(extendedPredicate.evaluate(resource1));
+    Assert.assertFalse(extendedPredicate.evaluate(resource2));
+
+
+    predicate = new PredicateBuilder().
+        property("sub1/category/p1").equals(2).toPredicate();
+
+    visitor = new ExtendedResourcePredicateVisitor(extendedPropertyMap);
+    PredicateHelper.visit(predicate, visitor);
+
+    extendedPredicate = visitor.getExtendedPredicate();
+
+    Assert.assertFalse(extendedPredicate.evaluate(resource1));
+    Assert.assertTrue(extendedPredicate.evaluate(resource2));
+
+
+
+    predicate = new PredicateBuilder().
+        property("sub1/category/p2").equals(4).toPredicate();
+
+    visitor = new ExtendedResourcePredicateVisitor(extendedPropertyMap);
+    PredicateHelper.visit(predicate, visitor);
+
+    extendedPredicate = visitor.getExtendedPredicate();
+
+    Assert.assertTrue(extendedPredicate.evaluate(resource1));
+    Assert.assertTrue(extendedPredicate.evaluate(resource2));
+
+
+
+    predicate = new PredicateBuilder().
+        property("sub1/category/p2").equals(5).toPredicate();
+
+    visitor = new ExtendedResourcePredicateVisitor(extendedPropertyMap);
+    PredicateHelper.visit(predicate, visitor);
+
+    extendedPredicate = visitor.getExtendedPredicate();
+
+    Assert.assertFalse(extendedPredicate.evaluate(resource1));
+    Assert.assertFalse(extendedPredicate.evaluate(resource2));
+
+
+    predicate = new PredicateBuilder().not().
+        property("sub1/category/p2").equals(5).toPredicate();
+
+    visitor = new ExtendedResourcePredicateVisitor(extendedPropertyMap);
+    PredicateHelper.visit(predicate, visitor);
+
+    extendedPredicate = visitor.getExtendedPredicate();
+
+    Assert.assertTrue(extendedPredicate.evaluate(resource1));
+    Assert.assertTrue(extendedPredicate.evaluate(resource2));
+
+
+    predicate = new PredicateBuilder().
+        property("sub1/category/p1").equals(1).and().
+        property("sub1/category/p2").equals(4).toPredicate();
+
+    visitor = new ExtendedResourcePredicateVisitor(extendedPropertyMap);
+    PredicateHelper.visit(predicate, visitor);
+
+    extendedPredicate = visitor.getExtendedPredicate();
+
+    Assert.assertTrue(extendedPredicate.evaluate(resource1));
+    Assert.assertFalse(extendedPredicate.evaluate(resource2));
+
+
+    predicate = new PredicateBuilder().
+        property("sub1/category/p1").equals(1).or().
+        property("sub1/category/p2").equals(4).toPredicate();
+
+    visitor = new ExtendedResourcePredicateVisitor(extendedPropertyMap);
+    PredicateHelper.visit(predicate, visitor);
+
+    extendedPredicate = visitor.getExtendedPredicate();
+
+    Assert.assertTrue(extendedPredicate.evaluate(resource1));
+    Assert.assertTrue(extendedPredicate.evaluate(resource2));
+  }
+}

+ 112 - 0
ambari-server/src/test/java/org/apache/ambari/server/api/query/ProcessingPredicateVisitorTest.java

@@ -0,0 +1,112 @@
+/**
+ * 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 org.apache.ambari.server.api.resources.StackResourceDefinition;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.utilities.PredicateBuilder;
+import org.apache.ambari.server.controller.utilities.PredicateHelper;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * ProcessingPredicateVisitor tests.
+ */
+public class ProcessingPredicateVisitorTest {
+  @Test
+  public void testGetProcessedPredicate() throws Exception {
+    ResourceDefinition resourceDefinition = new StackResourceDefinition();
+
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Stack, "HDP");
+
+    //test
+    QueryImpl instance = new QueryImplTest.TestQuery(mapIds, resourceDefinition);
+
+    Predicate predicate = new PredicateBuilder().property("Stacks/stack_name").equals("HDP").and().
+        property("versions/stackServices/StackServices/service_name").equals("HBASE").and().
+        property("versions/operatingSystems/OperatingSystems/os_type").equals("centos5").toPredicate();
+
+    ProcessingPredicateVisitor visitor = new ProcessingPredicateVisitor(instance);
+    PredicateHelper.visit(predicate, visitor);
+
+    Predicate processedPredicate = visitor.getProcessedPredicate();
+
+    Predicate expectedPredicate = new PredicateBuilder().property("Stacks/stack_name").equals("HDP").toPredicate();
+
+    Assert.assertEquals(expectedPredicate, processedPredicate);
+  }
+
+  @Test
+  public void testGetSubResourceCategories() throws Exception {
+    ResourceDefinition resourceDefinition = new StackResourceDefinition();
+
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Stack, "HDP");
+
+    //test
+    QueryImpl instance = new QueryImplTest.TestQuery(mapIds, resourceDefinition);
+
+    Predicate predicate = new PredicateBuilder().property("Stacks/stack_name").equals("HDP").and().
+        property("versions/stackServices/StackServices/service_name").equals("HBASE").and().
+        property("versions/operatingSystems/OperatingSystems/os_type").equals("centos5").toPredicate();
+
+    ProcessingPredicateVisitor visitor = new ProcessingPredicateVisitor(instance);
+    PredicateHelper.visit(predicate, visitor);
+
+    Set<String> categories = visitor.getSubResourceCategories();
+
+    Set<String> expected = new HashSet<String>();
+    expected.add("versions");
+
+    Assert.assertEquals(expected, categories);
+  }
+
+  @Test
+  public void testGetSubResourceProperties() throws Exception {
+    ResourceDefinition resourceDefinition = new StackResourceDefinition();
+
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Stack, "HDP");
+
+    //test
+    QueryImpl instance = new QueryImplTest.TestQuery(mapIds, resourceDefinition);
+
+    Predicate predicate = new PredicateBuilder().property("Stacks/stack_name").equals("HDP").and().
+        property("versions/stackServices/StackServices/service_name").equals("HBASE").and().
+        property("versions/operatingSystems/OperatingSystems/os_type").equals("centos5").toPredicate();
+
+    ProcessingPredicateVisitor visitor = new ProcessingPredicateVisitor(instance);
+    PredicateHelper.visit(predicate, visitor);
+
+    Set<String> properties = visitor.getSubResourceProperties();
+
+    Set<String> expected = new HashSet<String>();
+    expected.add("versions/stackServices/StackServices/service_name");
+    expected.add("versions/operatingSystems/OperatingSystems/os_type");
+
+    Assert.assertEquals(expected, properties);
+  }
+}

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

@@ -43,6 +43,7 @@ import java.util.Set;
 import static org.easymock.EasyMock.*;
 import static org.easymock.EasyMock.*;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 
 
 
 /**
 /**
@@ -150,6 +151,90 @@ public class QueryImplTest {
     Assert.assertEquals(3, versionsNode.getChildren().size());
     Assert.assertEquals(3, versionsNode.getChildren().size());
   }
   }
 
 
+  @Test
+  public void testGetJoinedResourceProperties() throws Exception {
+    ResourceDefinition resourceDefinition = new StackResourceDefinition();
+
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Stack, "HDP");
+
+    //test
+    QueryImpl instance = new TestQuery(mapIds, resourceDefinition);
+
+    instance.addProperty("versions/*", null);
+    instance.addProperty("versions/operatingSystems/*", null);
+    instance.addProperty("versions/operatingSystems/repositories/*", null);
+
+    instance.execute();
+
+    Set<String> propertyIds = new HashSet<String>();
+    propertyIds.add("versions/operatingSystems/repositories/Repositories/repo_id");
+    propertyIds.add("versions/operatingSystems/OperatingSystems/os_type");
+
+    Map<Resource, Set<Map<String, Object>>> resourcePropertiesMap = instance.getJoinedResourceProperties(propertyIds, null, null);
+
+    Set<Map<String, Object>> propertyMaps = null;
+
+    for (Map.Entry<Resource, Set<Map<String, Object>>> resourceSetEntry : resourcePropertiesMap.entrySet()) {
+      Assert.assertEquals(Resource.Type.Stack, resourceSetEntry.getKey().getType());
+      propertyMaps = resourceSetEntry.getValue();
+    }
+    if (propertyMaps == null) {
+      fail("No property maps found!");
+    }
+
+    Assert.assertEquals(6, propertyMaps.size());
+
+    for (Map<String, Object> map : propertyMaps) {
+      Assert.assertEquals(2, map.size());
+      Assert.assertTrue(map.containsKey("versions/operatingSystems/OperatingSystems/os_type"));
+      Assert.assertTrue(map.containsKey("versions/operatingSystems/repositories/Repositories/repo_id"));
+    }
+  }
+
+  @Test
+  public void testExecute_subResourcePredicate() throws Exception {
+    ResourceDefinition resourceDefinition = new StackResourceDefinition();
+
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Stack, "HDP");
+
+    //test
+    QueryImpl instance = new TestQuery(mapIds, resourceDefinition);
+
+    PredicateBuilder pb = new PredicateBuilder();
+    Predicate predicate = pb.property("versions/operatingSystems/OperatingSystems/os_type").equals("centos5").toPredicate();
+
+    instance.setUserPredicate(predicate);
+
+    Result result = instance.execute();
+
+    TreeNode<Resource> tree = result.getResultTree();
+
+    Assert.assertEquals(1, tree.getChildren().size());
+    TreeNode<Resource> stackNode = tree.getChild("Stack:1");
+    Assert.assertEquals("Stack:1", stackNode.getName());
+    Assert.assertEquals(Resource.Type.Stack, stackNode.getObject().getType());
+    Assert.assertEquals(1, stackNode.getChildren().size());
+    TreeNode<Resource> versionsNode = stackNode.getChild("versions");
+    Assert.assertEquals(3, versionsNode.getChildren().size());
+
+    TreeNode<Resource> versionNode = versionsNode.getChild("StackVersion:1");
+    Assert.assertEquals("StackVersion:1", versionNode.getName());
+    Assert.assertEquals(Resource.Type.StackVersion, versionNode.getObject().getType());
+
+    Assert.assertEquals(1, versionNode.getChildren().size());
+    TreeNode<Resource> opSystemsNode = versionNode.getChild("operatingSystems");
+    Assert.assertEquals(1, opSystemsNode.getChildren().size());
+
+    TreeNode<Resource> opSystemNode = opSystemsNode.getChild("OperatingSystem:1");
+    Assert.assertEquals("OperatingSystem:1", opSystemNode.getName());
+    Resource osResource = opSystemNode.getObject();
+    Assert.assertEquals(Resource.Type.OperatingSystem, opSystemNode.getObject().getType());
+
+    Assert.assertEquals("centos5", osResource.getPropertyValue("OperatingSystems/os_type"));
+  }
+
   @Test
   @Test
   public void testExecute__Stack_instance_specifiedSubResources() throws Exception {
   public void testExecute__Stack_instance_specifiedSubResources() throws Exception {
     ResourceDefinition resourceDefinition = new StackResourceDefinition();
     ResourceDefinition resourceDefinition = new StackResourceDefinition();
@@ -190,7 +275,7 @@ public class QueryImplTest {
 
 
     Assert.assertEquals(1, opSystemNode.getChildren().size());
     Assert.assertEquals(1, opSystemNode.getChildren().size());
     TreeNode<Resource> repositoriesNode = opSystemNode.getChild("repositories");
     TreeNode<Resource> repositoriesNode = opSystemNode.getChild("repositories");
-    Assert.assertEquals(1, repositoriesNode.getChildren().size());
+    Assert.assertEquals(2, repositoriesNode.getChildren().size());
 
 
     TreeNode<Resource> repositoryNode = repositoriesNode.getChild("Repository:1");
     TreeNode<Resource> repositoryNode = repositoriesNode.getChild("Repository:1");
     Assert.assertEquals("Repository:1", repositoryNode.getName());
     Assert.assertEquals("Repository:1", repositoryNode.getName());
@@ -448,7 +533,7 @@ public class QueryImplTest {
     Assert.assertNotNull(hostNode.getObject().getPropertyValue("c1/p3"));
     Assert.assertNotNull(hostNode.getObject().getPropertyValue("c1/p3"));
   }
   }
 
 
-  private class TestQuery extends QueryImpl {
+  public static class TestQuery extends QueryImpl {
     public TestQuery(Map<Resource.Type, String> mapIds, ResourceDefinition resourceDefinition) {
     public TestQuery(Map<Resource.Type, String> mapIds, ResourceDefinition resourceDefinition) {
       super(mapIds, resourceDefinition, new ClusterControllerImpl(new ClusterControllerImplTest.TestProviderModule()));
       super(mapIds, resourceDefinition, new ClusterControllerImpl(new ClusterControllerImplTest.TestProviderModule()));
     }
     }

+ 59 - 0
ambari-server/src/test/java/org/apache/ambari/server/api/query/SubResourcePredicateVisitorTest.java

@@ -0,0 +1,59 @@
+/**
+ * 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.controller.predicate.AndPredicate;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.utilities.PredicateBuilder;
+import org.apache.ambari.server.controller.utilities.PredicateHelper;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * SubResourcePredicateVisitor tests.
+ */
+public class SubResourcePredicateVisitorTest {
+  @Test
+  public void testGetSubResourcePredicate() throws Exception {
+
+    Predicate predicate = new PredicateBuilder().property("ServiceInfo/service_name").equals("HBASE").and().
+        property("components/ServiceComponentInfo/category").equals("SLAVE").and().
+        property("components/host_components/metrics/cpu/cpu_num").greaterThanEqualTo(1).toPredicate();
+
+    SubResourcePredicateVisitor visitor = new SubResourcePredicateVisitor("components");
+    PredicateHelper.visit(predicate, visitor);
+
+    Predicate subResourcePredicate = visitor.getSubResourcePredicate();
+
+    Predicate expectedPredicate = new PredicateBuilder().property("ServiceComponentInfo/category").equals("SLAVE").and().
+        property("host_components/metrics/cpu/cpu_num").greaterThanEqualTo(1).toPredicate();
+
+    Assert.assertEquals(expectedPredicate, subResourcePredicate);
+
+
+    predicate = new PredicateBuilder().property("ServiceInfo/service_name").equals("HBASE").and().
+        property("ServiceInfo/component_name").equals("HBASE_MASTER").toPredicate();
+
+    visitor = new SubResourcePredicateVisitor("components");
+    PredicateHelper.visit(predicate, visitor);
+
+    subResourcePredicate = visitor.getSubResourcePredicate();
+
+    Assert.assertEquals(new AndPredicate(), subResourcePredicate);
+  }
+}

+ 1 - 0
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ClusterControllerImplTest.java

@@ -926,6 +926,7 @@ public class ClusterControllerImplTest {
       Set<String> keyPropertyValues = new LinkedHashSet<String>();
       Set<String> keyPropertyValues = new LinkedHashSet<String>();
 
 
       keyPropertyValues.add("repo1");
       keyPropertyValues.add("repo1");
+      keyPropertyValues.add("repo2");
 
 
       return getResources(Resource.Type.Repository, predicate, "Repositories/repo_id", keyPropertyValues);
       return getResources(Resource.Type.Repository, predicate, "Repositories/repo_id", keyPropertyValues);
     }
     }