Selaa lähdekoodia

AMBARI-7817 - Make paging parameters available to individual resource handlers (jonathanhurley)

Jonathan Hurley 10 vuotta sitten
vanhempi
commit
30e5d2d124
21 muutettua tiedostoa jossa 988 lisäystä ja 145 poistoa
  1. 9 0
      ambari-server/src/main/java/org/apache/ambari/server/api/query/JpaPredicateVisitor.java
  2. 107 0
      ambari-server/src/main/java/org/apache/ambari/server/api/query/JpaSortBuilder.java
  3. 75 30
      ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryImpl.java
  4. 12 1
      ambari-server/src/main/java/org/apache/ambari/server/controller/AlertHistoryRequest.java
  5. 13 1
      ambari-server/src/main/java/org/apache/ambari/server/controller/AlertNoticeRequest.java
  6. 38 32
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractProviderModule.java
  7. 16 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertHistoryResourceProvider.java
  8. 15 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertNoticeResourceProvider.java
  9. 60 40
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterControllerImpl.java
  10. 2 2
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/PageRequestImpl.java
  11. 68 6
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RequestImpl.java
  12. 159 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Request.java
  13. 31 10
      ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/PropertyHelper.java
  14. 26 0
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java
  15. 30 2
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertsDAO.java
  16. 3 0
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertHistoryEntity_.java
  17. 88 11
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ClusterControllerImplTest.java
  18. 7 6
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/RequestResourceProviderTest.java
  19. 9 0
      ambari-server/src/test/java/org/apache/ambari/server/orm/AlertDaoHelper.java
  20. 112 4
      ambari-server/src/test/java/org/apache/ambari/server/orm/dao/AlertDispatchDAOTest.java
  21. 108 0
      ambari-server/src/test/java/org/apache/ambari/server/orm/dao/AlertsDAOTest.java

+ 9 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/query/JpaPredicateVisitor.java

@@ -129,6 +129,15 @@ public abstract class JpaPredicateVisitor<T> implements PredicateVisitor {
     return m_query;
   }
 
+  /**
+   * Gets the criteria builder used to construct the query and predicates.
+   *
+   * @return the builder (never {@code null}).
+   */
+  public CriteriaBuilder getCriteriaBuilder() {
+    return m_builder;
+  }
+
   /**
    * {@inheritDoc}
    */

+ 107 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/query/JpaSortBuilder.java

@@ -0,0 +1,107 @@
+/**
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Order;
+import javax.persistence.criteria.Path;
+import javax.persistence.metamodel.SingularAttribute;
+
+import org.apache.ambari.server.controller.spi.SortRequest;
+import org.apache.ambari.server.controller.spi.SortRequestProperty;
+
+/**
+ * The {@link JpaSortBuilder} class is used to convert and Ambari
+ * {@link SortRequest} and list of {@link SortRequestProperty} into a JPA
+ * {@link Order} list. This can then be given to a {@link CriteriaBuilder} to
+ * provide sorting in the JPA layer during a query.
+ * <p/>
+ * This classes uses the {@link JpaPredicateVisitor} for the entity being
+ * queried in order to convert the entity fields into {@link Path} expressions.
+ */
+public class JpaSortBuilder<T> {
+
+  /**
+   * Constructor.
+   *
+   */
+  public JpaSortBuilder() {
+  }
+
+  /**
+   * Builds the list of sort orders based on the supplied request and JPA
+   * predicate visitor.
+   *
+   * @param sortRequests
+   *          the Ambari sort request properties to turn into a JPA sort
+   *          request. If {@code null} or the {@link SortRequestProperty} list
+   *          is null, an empty list is returned.
+   * @param visitor
+   *          a visitor that knows how to convert the Ambari properties into
+   *          {@link SingularAttribute} (not {@code null}).
+   * @return a list of sorts or an empty list if none (never {@code null}).
+   */
+  public List<Order> buildSortOrders(SortRequest sortRequest,
+      JpaPredicateVisitor<T> visitor) {
+
+    if (null == sortRequest || null == sortRequest.getProperties()) {
+      return Collections.emptyList();
+    }
+
+    CriteriaBuilder builder = visitor.getCriteriaBuilder();
+    List<SortRequestProperty> sortProperties = sortRequest.getProperties();
+    List<Order> sortOrders = new ArrayList<Order>(sortProperties.size());
+
+    for (SortRequestProperty sort : sortProperties) {
+      String propertyId = sort.getPropertyId();
+
+      List<? extends SingularAttribute<?, ?>> singularAttributes = visitor.getPredicateMapping(propertyId);
+
+      if (null == singularAttributes || singularAttributes.size() == 0) {
+        continue;
+      }
+
+      Path<?> path = null;
+      for (SingularAttribute<?, ?> singularAttribute : singularAttributes) {
+        if (null == path) {
+          CriteriaQuery<T> query = visitor.getCriteriaQuery();
+          path = query.from(visitor.getEntityClass()).get(
+              singularAttribute.getName());
+        } else {
+          path = path.get(singularAttribute.getName());
+        }
+      }
+
+      Order sortOrder = null;
+      if (sort.getOrder() == org.apache.ambari.server.controller.spi.SortRequest.Order.ASC) {
+        sortOrder = builder.asc(path);
+      } else {
+        sortOrder = builder.desc(path);
+      }
+
+      sortOrders.add(sortOrder);
+    }
+
+    return sortOrders;
+  }
+}

+ 75 - 30
ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryImpl.java

@@ -18,6 +18,14 @@
 
 package org.apache.ambari.server.api.query;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
 import org.apache.ambari.server.api.query.render.DefaultRenderer;
 import org.apache.ambari.server.api.query.render.Renderer;
 import org.apache.ambari.server.api.resources.ResourceDefinition;
@@ -25,21 +33,34 @@ import org.apache.ambari.server.api.resources.ResourceInstance;
 import org.apache.ambari.server.api.resources.ResourceInstanceFactoryImpl;
 import org.apache.ambari.server.api.resources.SubResourceDefinition;
 import org.apache.ambari.server.api.services.BaseRequest;
+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.utilities.PredicateHelper;
-import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.apache.ambari.server.controller.predicate.AndPredicate;
 import org.apache.ambari.server.controller.predicate.EqualsPredicate;
-import org.apache.ambari.server.api.services.Result;
-import org.apache.ambari.server.controller.spi.*;
+import org.apache.ambari.server.controller.spi.ClusterController;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.NoSuchResourceException;
+import org.apache.ambari.server.controller.spi.PageRequest;
 import org.apache.ambari.server.controller.spi.PageRequest.StartingPoint;
+import org.apache.ambari.server.controller.spi.PageResponse;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.Request.PageInfo;
+import org.apache.ambari.server.controller.spi.Request.SortInfo;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.ResourceProvider;
+import org.apache.ambari.server.controller.spi.Schema;
+import org.apache.ambari.server.controller.spi.SortRequest;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.TemporalInfo;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
+import org.apache.ambari.server.controller.utilities.PredicateHelper;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.*;
-
 /**
  * Default read query.
  */
@@ -299,7 +320,7 @@ public class QueryImpl implements Query, ResourceInstance {
   // ----- helper methods ----------------------------------------------------
 
   /**
-   * Get the map of sub-resources.  Lazily create the map if required.  
+   * Get the map of sub-resources.  Lazily create the map if required.
    */
   protected Map<String, QueryImpl> ensureSubResources() {
     if (availableSubResources == null) {
@@ -343,15 +364,19 @@ public class QueryImpl implements Query, ResourceInstance {
       NoSuchResourceException,
       NoSuchParentResourceException {
 
-    Set<Resource> providerResourceSet = new HashSet<Resource>();
     Resource.Type resourceType    = getResourceDefinition().getType();
     Predicate     queryPredicate  = createPredicate(getKeyValueMap(), processUserPredicate(userPredicate));
 
     // must occur after processing user predicate and prior to creating request
     finalizeProperties();
 
-    Request       request     = createRequest();
+    Request request = createRequest();
+
+    // use linked hashsets so that we maintain inseration and traversal order
+    // in the event that the resource provider already gave us a sorted set
+    // back
     Set<Resource> resourceSet = new LinkedHashSet<Resource>();
+    Set<Resource> providerResourceSet = new LinkedHashSet<Resource>();
 
     Set<Resource> queryResources = doQuery(resourceType, request, queryPredicate);
     // If there is a page request and the predicate does not contain properties
@@ -382,7 +407,7 @@ public class QueryImpl implements Query, ResourceInstance {
   }
 
   /**
-   * Query the cluster controller for the sub-resources associated with 
+   * Query the cluster controller for the sub-resources associated with
    * this query object.
    */
   private void queryForSubResources()
@@ -439,13 +464,14 @@ public class QueryImpl implements Query, ResourceInstance {
   }
 
   /**
-   * 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.
+   * 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 ...
+   * For example, if this query is associated with the resources AResource1,
+   * AResource1 and AResource3 as follows ...
    *
+   * <pre>
    * a_resources
    * │
    * └──AResource1 ─────────────AResource1 ─────────────AResource3
@@ -465,22 +491,22 @@ public class QueryImpl implements Query, ResourceInstance {
    *          │
    *          └── 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},
@@ -490,12 +516,17 @@ public class QueryImpl implements Query, ResourceInstance {
    *                {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}],
    *  }
+   * </pre>
    *
-   * @param propertyIds     the requested properties
-   * @param parentResource  the parent resource; may be null
-   * @param category        the sub-resource category; may be null
+   * @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
+   * @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,
@@ -567,9 +598,9 @@ public class QueryImpl implements Query, ResourceInstance {
    * Finalize properties for entire query tree before executing query.
    */
   private void finalizeProperties() {
-    ResourceDefinition rootDefinition = this.resourceDefinition;
+    ResourceDefinition rootDefinition = resourceDefinition;
 
-    QueryInfo rootQueryInfo = new QueryInfo(rootDefinition, this.requestedProperties);
+    QueryInfo rootQueryInfo = new QueryInfo(rootDefinition, requestedProperties);
     TreeNode<QueryInfo> rootNode = new TreeNodeImpl<QueryInfo>(
         null, rootQueryInfo, rootDefinition.getType().name());
 
@@ -868,9 +899,20 @@ public class QueryImpl implements Query, ResourceInstance {
   private Request createRequest() {
     Map<String, String> requestInfoProperties = new HashMap<String, String>();
 
+    PageInfo pageInfo = null;
+    if (null != pageRequest) {
+      pageInfo = new PageInfo(pageRequest);
+    }
+
+    SortInfo sortInfo = null;
+    if (null != sortRequest) {
+      sortInfo = new SortInfo(sortRequest);
+    }
+
     if (pageRequest != null) {
       requestInfoProperties.put(BaseRequest.PAGE_SIZE_PROPERTY_KEY,
           Integer.toString(pageRequest.getPageSize() + pageRequest.getOffset()));
+
       requestInfoProperties.put(
         BaseRequest.ASC_ORDER_PROPERTY_KEY,
           Boolean.toString(pageRequest.getStartingPoint() == StartingPoint.Beginning
@@ -879,7 +921,7 @@ public class QueryImpl implements Query, ResourceInstance {
 
     if (allProperties) {
       return PropertyHelper.getReadRequest(Collections.<String> emptySet(),
-          requestInfoProperties, null);
+          requestInfoProperties, null, pageInfo, sortInfo);
     }
 
     Map<String, TemporalInfo> mapTemporalInfo    = new HashMap<String, TemporalInfo>();
@@ -895,7 +937,9 @@ public class QueryImpl implements Query, ResourceInstance {
         mapTemporalInfo.put(propertyId, globalTemporalInfo);
       }
     }
-    return PropertyHelper.getReadRequest(setProperties, requestInfoProperties, mapTemporalInfo);
+
+    return PropertyHelper.getReadRequest(setProperties, requestInfoProperties,
+        mapTemporalInfo, pageInfo, sortInfo);
   }
 
 
@@ -921,8 +965,9 @@ public class QueryImpl implements Query, ResourceInstance {
     for (Resource.Type type : types) {
       String resourceKeyProp = schema.getKeyPropertyId(type);
       Object resourceValue = resource.getPropertyValue(resourceKeyProp);
-      if (null != resourceValue)
+      if (null != resourceValue) {
         resourceKeyValueMap.put(type, resourceValue.toString());
+      }
     }
     return resourceKeyValueMap;
   }

+ 12 - 1
ambari-server/src/main/java/org/apache/ambari/server/controller/AlertHistoryRequest.java

@@ -17,7 +17,9 @@
  */
 package org.apache.ambari.server.controller;
 
+import org.apache.ambari.server.controller.spi.PageRequest;
 import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.SortRequest;
 import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
 
 /**
@@ -27,8 +29,17 @@ import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
 public class AlertHistoryRequest {
 
   /**
-   * An Ambari predicate.
+   * An Ambari predicate, or {@code null} for none.
    */
   public Predicate Predicate;
 
+  /**
+   * Pagination information, or {@code null} for none.
+   */
+  public PageRequest Pagination;
+
+  /**
+   * Sort information, or {@code null} for none.
+   */
+  public SortRequest Sort;
 }

+ 13 - 1
ambari-server/src/main/java/org/apache/ambari/server/controller/AlertNoticeRequest.java

@@ -17,7 +17,9 @@
  */
 package org.apache.ambari.server.controller;
 
+import org.apache.ambari.server.controller.spi.PageRequest;
 import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.SortRequest;
 import org.apache.ambari.server.orm.entities.AlertNoticeEntity;
 
 /**
@@ -27,8 +29,18 @@ import org.apache.ambari.server.orm.entities.AlertNoticeEntity;
 public class AlertNoticeRequest {
 
   /**
-   * An Ambari predicate.
+   * An Ambari predicate, or {@code null} for none.
    */
   public Predicate Predicate;
 
+  /**
+   * Pagination information, or {@code null} for none.
+   */
+  public PageRequest Pagination;
+
+  /**
+   * Sort information, or {@code null} for none.
+   */
+  public SortRequest Sort;
+
 }

+ 38 - 32
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractProviderModule.java

@@ -90,18 +90,18 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
   private static final Map<Service.Type, String> serviceConfigTypes = new EnumMap<Service.Type, String>(Service.Type.class);
   private static final Map<Service.Type, Map<String, String[]>> serviceDesiredProperties = new EnumMap<Service.Type, Map<String, String[]>>(Service.Type.class);
   private static final Map<String, Service.Type> componentServiceMap = new HashMap<String, Service.Type>();
-  
+
   private static final Map<String, Map<String, String[]>> jmxDesiredProperties = new HashMap<String, Map<String,String[]>>();
   private volatile Map<String, String> clusterCoreSiteConfigVersionMap = new HashMap<String, String>();
   private volatile Map<String, String> clusterJmxProtocolMap = new HashMap<String, String>();
-  
+
   static {
     serviceConfigTypes.put(Service.Type.HDFS, "hdfs-site");
     serviceConfigTypes.put(Service.Type.MAPREDUCE, "mapred-site");
     serviceConfigTypes.put(Service.Type.HBASE, "hbase-site");
     serviceConfigTypes.put(Service.Type.YARN, "yarn-site");
     serviceConfigTypes.put(Service.Type.MAPREDUCE2, "mapred-site");
- 
+
     componentServiceMap.put("NAMENODE", Service.Type.HDFS);
     componentServiceMap.put("DATANODE", Service.Type.HDFS);
     componentServiceMap.put("JOBTRACKER", Service.Type.MAPREDUCE);
@@ -115,12 +115,12 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
     initPropMap.put("NAMENODE", new String[] {"dfs.http.address", "dfs.namenode.http-address"});
     initPropMap.put("DATANODE", new String[] {"dfs.datanode.http.address"});
     serviceDesiredProperties.put(Service.Type.HDFS, initPropMap);
-    
+
     initPropMap = new HashMap<String, String[]>();
     initPropMap.put("JOBTRACKER", new String[] {"mapred.job.tracker.http.address"});
     initPropMap.put("TASKTRACKER", new String[] {"mapred.task.tracker.http.address"});
     serviceDesiredProperties.put(Service.Type.MAPREDUCE, initPropMap);
-    
+
     initPropMap = new HashMap<String, String[]>();
     initPropMap.put("HBASE_MASTER", new String[] {"hbase.master.info.port"});
     serviceDesiredProperties.put(Service.Type.HBASE, initPropMap);
@@ -257,7 +257,7 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
       }
     }
     Service.Type service = componentServiceMap.get(componentName);
-    
+
     if (service != null) {
       try {
         String currVersion = getDesiredConfigVersion(clusterName, serviceConfigTypes.get(service));
@@ -271,17 +271,18 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
           !clusterJmxPorts.containsKey(componentName)) {
 
           serviceConfigVersions.put(service, currVersion);
-          
+
           Map<String, String> portMap = getDesiredConfigMap(clusterName,
             currVersion, serviceConfigTypes.get(service),
             serviceDesiredProperties.get(service));
-          
+
           for (Entry<String, String> entry : portMap.entrySet()) {
             // portString will be null if the property defined for the component doesn't exist
             // this will trigger using the default port for the component
             String portString = getPortString(entry.getValue());
-            if (null != portString)
+            if (null != portString) {
               clusterJmxPorts.put(entry.getKey(), portString);
+            }
           }
         }
       } catch (Exception e) {
@@ -302,7 +303,9 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
    */
   private String postProcessPropertyValue(String key, String value, Map<String, String> properties, Set<String> prevProps) {
       if (value != null && key != null && value.contains("${")){
-          if (prevProps == null) prevProps = new HashSet<String>();
+          if (prevProps == null) {
+            prevProps = new HashSet<String>();
+          }
           if (prevProps.contains(key)){
             return value;
           }
@@ -317,7 +320,7 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
               String trueValue = postProcessPropertyValue(valueRef, properties.get(valueRef), properties, prevProps);
               if (trueValue != null){
                refMap.put("${"+valueRef+ '}', trueValue);
-              } 
+              }
           }
           for (Entry<String, String> entry : refMap.entrySet()){
             refValueString = entry.getValue();
@@ -326,8 +329,8 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
           properties.put(key, value);
     }
     return value;
-  } 
-  
+  }
+
   // ----- GangliaHostProvider -----------------------------------------------
 
   @Override
@@ -335,7 +338,7 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
     checkInit();
     return clusterGangliaCollectorMap.get(clusterName);
   }
-  
+
   @Override
   public boolean isGangliaCollectorHostLive(String clusterName) throws SystemException {
 
@@ -357,12 +360,12 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
       LOG.debug("Error checking of Ganglia server host live status: ", e);
       return false;
     }
-    
+
     //Cluster without Ganglia
     return gangliaCollectorHost != null &&
         !gangliaCollectorHost.getHostState().equals(HostState.HEARTBEAT_LOST.name());
   }
-  
+
   @Override
   public boolean isGangliaCollectorComponentLive(String clusterName) throws SystemException {
     if (clusterName == null) {
@@ -378,7 +381,7 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
                                                                                      Role.GANGLIA_SERVER.name(),
                                                                                      gangliaCollectorHostName,
                                                                                      null);
-      
+
       Set<ServiceComponentHostResponse> hostComponents =
           managementController.getHostComponents(Collections.singleton(componentRequest));
 
@@ -387,7 +390,7 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
       LOG.debug("Error checking of Ganglia server host component state: ", e);
       return false;
     }
-    
+
     //Cluster without Ganglia
     return gangliaCollectorHostComponent != null &&
         gangliaCollectorHostComponent.getLiveState().equals(State.STARTED.name());
@@ -575,7 +578,8 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
     Map<String, String> requestInfoProperties = new HashMap<String, String>();
     requestInfoProperties.put(ClusterResourceProvider.GET_IGNORE_PERMISSIONS_PROPERTY_ID, "true");
 
-    Request request = PropertyHelper.getReadRequest(propertyIds, requestInfoProperties, null);
+    Request request = PropertyHelper.getReadRequest(propertyIds,
+        requestInfoProperties, null, null, null);
 
     try {
       jmxPortMap.clear();
@@ -662,7 +666,8 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
       Map<String, String> requestInfoProperties = new HashMap<String, String>();
       requestInfoProperties.put(ClusterResourceProvider.GET_IGNORE_PERMISSIONS_PROPERTY_ID, "true");
 
-      Request readRequest = PropertyHelper.getReadRequest(propertyIds, requestInfoProperties, null);
+      Request readRequest = PropertyHelper.getReadRequest(propertyIds,
+          requestInfoProperties, null, null, null);
 
       clusterResource = clusterResourceProvider.getResources(readRequest, basePredicate);
     } catch (NoSuchResourceException e) {
@@ -706,7 +711,7 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
       LOG.info("Resource for the desired config not found. " + e);
       return Collections.emptyMap();
     }
-    
+
     Map<String, String> mConfigs = new HashMap<String, String>();
     if (configResources != null) {
       for (Resource res : configResources) {
@@ -714,15 +719,16 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
         for (Entry<String,String[]> entry : keys.entrySet()) {
           String propName = null;
           String value = null;
-          
+
           for (String pname : entry.getValue()) {
             propName = pname;
             value = (String) res.getPropertyValue(PropertyHelper.getPropertyId(
                 PROPERTIES_CATEGORY, pname));
-            if (null != value)
+            if (null != value) {
               break;
+            }
           }
-          
+
           if (value != null && value.contains("${")) {
             if (evaluatedProperties == null){
               evaluatedProperties = new HashMap<String, String>();
@@ -736,12 +742,12 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
                   evaluatedProperties.put(keyString, valueString);
                   postProcessPropertyValue(keyString, valueString, evaluatedProperties, null);
                 }
-              }              
+              }
             }
           }
           value = postProcessPropertyValue(propName, value, evaluatedProperties, null);
           LOG.debug("PROPERTY -> key: " + propName + ", " + "value: " + value);
-          
+
           mConfigs.put(entry.getKey(), value);
         }
       }
@@ -759,7 +765,7 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
                                                      String hostNamePropertyId,
                                                      String componentNamePropertyId,
                                                      String statePropertyId) {
-    
+
     return new JMXPropertyProvider(PropertyHelper.getJMXPropertyIds(type), streamProvider,
         jmxHostProvider, metricsHostProvider, clusterNamePropertyId, hostNamePropertyId,
                     componentNamePropertyId, statePropertyId);
@@ -772,7 +778,7 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
                                                                 ComponentSSLConfiguration configuration,
                                                                 GangliaHostProvider hostProvider,
                                                                 String clusterNamePropertyId) {
-    
+
     return new GangliaReportPropertyProvider(PropertyHelper.getGangliaPropertyIds(type), streamProvider,
           configuration, hostProvider, clusterNamePropertyId);
   }
@@ -815,7 +821,7 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
     return new GangliaHostComponentPropertyProvider(PropertyHelper.getGangliaPropertyIds(type), streamProvider,
           configuration, hostProvider, clusterNamePropertyId, hostNamePropertyId, componentNamePropertyId);
   }
-  
+
   @Override
   public String getJMXProtocol(String clusterName, String componentName) {
     String jmxProtocolString = clusterJmxProtocolMap.get(clusterName);
@@ -824,12 +830,12 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
       String cachedCoreSiteConfigVersion = clusterCoreSiteConfigVersionMap.get(clusterName);
       if (!newCoreSiteConfigVersion.equals(cachedCoreSiteConfigVersion)) {
         clusterCoreSiteConfigVersionMap.put(clusterName, newCoreSiteConfigVersion);
-        
+
         // Getting protocolMap for NAMENODE as it is the same property hadoop.ssl.enabled for all components
         Map<String, String> protocolMap = getDesiredConfigMap(
             clusterName,
             newCoreSiteConfigVersion, "core-site",
-            jmxDesiredProperties.get("NAMENODE")); 
+            jmxDesiredProperties.get("NAMENODE"));
         jmxProtocolString = getJMXProtocolString(protocolMap.get("NAMENODE"));
         clusterJmxProtocolMap.put(clusterName, jmxProtocolString);
       }
@@ -860,5 +866,5 @@ public abstract class AbstractProviderModule implements ProviderModule, Resource
   private String getJMXProtocolString(String value) {
     return Boolean.valueOf(value) ? "https" : "http";
   }
-  
+
 }

+ 16 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertHistoryResourceProvider.java

@@ -170,9 +170,25 @@ public class AlertHistoryResourceProvider extends AbstractResourceProvider {
 
     Set<Resource> results = new LinkedHashSet<Resource>();
     Set<String> requestPropertyIds = getRequestPropertyIds(request, predicate);
+
+    Request.PageInfo pageInfo = request.getPageInfo();
+    Request.SortInfo sortInfo = request.getSortInfo();
+
     AlertHistoryRequest historyRequest = new AlertHistoryRequest();
     historyRequest.Predicate = predicate;
 
+    if (null != pageInfo) {
+      historyRequest.Pagination = pageInfo.getPageRequest();
+
+      pageInfo.setResponsePaged(true);
+      pageInfo.setTotalCount(s_dao.getCount(predicate));
+    }
+
+    if (null != sortInfo) {
+      sortInfo.setResponseSorted(true);
+      historyRequest.Sort = sortInfo.getSortRequest();
+    }
+
     List<AlertHistoryEntity> entities = s_dao.findAll(historyRequest);
     for (AlertHistoryEntity entity : entities) {
       results.add(toResource(entity, requestPropertyIds));

+ 15 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertNoticeResourceProvider.java

@@ -163,9 +163,24 @@ public class AlertNoticeResourceProvider extends AbstractResourceProvider {
     Set<String> requestPropertyIds = getRequestPropertyIds(request, predicate);
     Set<Resource> results = new LinkedHashSet<Resource>();
 
+    Request.PageInfo pageInfo = request.getPageInfo();
+    Request.SortInfo sortInfo = request.getSortInfo();
+
     AlertNoticeRequest noticeRequest = new AlertNoticeRequest();
     noticeRequest.Predicate = predicate;
 
+    if (null != pageInfo) {
+      noticeRequest.Pagination = pageInfo.getPageRequest();
+
+      pageInfo.setResponsePaged(true);
+      pageInfo.setTotalCount(s_dao.getNoticesCount(predicate));
+    }
+
+    if (null != sortInfo) {
+      sortInfo.setResponseSorted(true);
+      noticeRequest.Sort = sortInfo.getSortRequest();
+    }
+
     List<AlertNoticeEntity> entities = s_dao.findAllNotices(noticeRequest);
     for (AlertNoticeEntity entity : entities) {
       results.add(toResource(entity, requestPropertyIds));

+ 60 - 40
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterControllerImpl.java

@@ -18,10 +18,22 @@
 
 package org.apache.ambari.server.controller.internal;
 
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.TreeSet;
+
 import org.apache.ambari.server.controller.spi.ClusterController;
 import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
 import org.apache.ambari.server.controller.spi.NoSuchResourceException;
-import org.apache.ambari.server.controller.spi.SortRequest;
 import org.apache.ambari.server.controller.spi.PageRequest;
 import org.apache.ambari.server.controller.spi.PageResponse;
 import org.apache.ambari.server.controller.spi.Predicate;
@@ -30,10 +42,12 @@ import org.apache.ambari.server.controller.spi.ProviderModule;
 import org.apache.ambari.server.controller.spi.Request;
 import org.apache.ambari.server.controller.spi.RequestStatus;
 import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.Resource.Type;
 import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
 import org.apache.ambari.server.controller.spi.ResourcePredicateEvaluator;
 import org.apache.ambari.server.controller.spi.ResourceProvider;
 import org.apache.ambari.server.controller.spi.Schema;
+import org.apache.ambari.server.controller.spi.SortRequest;
 import org.apache.ambari.server.controller.spi.SortRequestProperty;
 import org.apache.ambari.server.controller.spi.SystemException;
 import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
@@ -42,21 +56,6 @@ import org.apache.ambari.server.controller.utilities.PredicateHelper;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.NavigableSet;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.TreeSet;
-
-import static org.apache.ambari.server.controller.spi.Resource.Type;
 
 /**
  * Default cluster controller implementation.
@@ -175,47 +174,68 @@ public class ClusterControllerImpl implements ClusterController {
       NoSuchResourceException,
       NoSuchParentResourceException {
 
-    Set<Resource> resources;
     ResourceProvider provider = ensureResourceProvider(type);
 
     ResourcePredicateEvaluator evaluator = provider instanceof ResourcePredicateEvaluator ?
         (ResourcePredicateEvaluator) provider : DEFAULT_RESOURCE_PREDICATE_EVALUATOR;
 
     int totalCount = 0;
+    Set<Resource> resources = providerResources;
+
     if (!providerResources.isEmpty()) {
+      Request.PageInfo pageInfo = request.getPageInfo();
+      Request.SortInfo sortInfo = request.getSortInfo();
+
+      // determine if the provider has already paged & sorted the results
+      boolean providerAlreadyPaged = (null != pageInfo && pageInfo.isResponsePaged());
+      boolean providerAlreadySorted = (null != sortInfo && sortInfo.isResponseSorted());
+
+      // conditionally create a comparator if there is a sort
       Comparator<Resource> resourceComparator = comparator;
-      if (sortRequest != null) {
+      if (null != sortRequest) {
         checkSortRequestProperties(sortRequest, type, provider);
         resourceComparator = new ResourceComparator(sortRequest);
       }
 
-      TreeSet<Resource> sortedResources = new TreeSet<Resource>(resourceComparator);
-      sortedResources.addAll(providerResources);
-      totalCount = sortedResources.size();
+      // if the provider did not already sort the set, then sort it based
+      // on the comparator
+      if (!providerAlreadySorted) {
+        TreeSet<Resource> sortedResources = new TreeSet<Resource>(
+            resourceComparator);
 
-      if (pageRequest != null) {
+        sortedResources.addAll(providerResources);
+        resources = sortedResources;
+      }
+
+      // start out assuming that the results are not paged and that
+      // the total count is the size of the provider resources
+      totalCount = resources.size();
+
+      // conditionally page the results
+      if (null != pageRequest && !providerAlreadyPaged) {
         switch (pageRequest.getStartingPoint()) {
           case Beginning:
-            return getPageFromOffset(pageRequest.getPageSize(), 0,
-              sortedResources, predicate, evaluator);
+            return getPageFromOffset(pageRequest.getPageSize(), 0, resources,
+                predicate, evaluator);
           case End:
-            return getPageToOffset(pageRequest.getPageSize(), -1,
-                sortedResources, predicate, evaluator);
+            return getPageToOffset(pageRequest.getPageSize(), -1, resources,
+                predicate, evaluator);
           case OffsetStart:
             return getPageFromOffset(pageRequest.getPageSize(),
-              pageRequest.getOffset(), sortedResources, predicate, evaluator);
+                pageRequest.getOffset(), resources, predicate, evaluator);
           case OffsetEnd:
             return getPageToOffset(pageRequest.getPageSize(),
-              pageRequest.getOffset(), sortedResources, predicate, evaluator);
-          // TODO : need to support the following cases for pagination
-//          case PredicateStart:
-//          case PredicateEnd:
+                pageRequest.getOffset(), resources, predicate, evaluator);
+          case PredicateStart:
+          case PredicateEnd:
+            // TODO : need to support the following cases for pagination
+            break;
+          default:
+            break;
         }
+      } else if (providerAlreadyPaged) {
+        totalCount = pageInfo.getTotalCount();
       }
-      resources = sortedResources;
-    } else {
-      resources = providerResources;
-      totalCount = providerResources.size();
     }
 
     return new PageResponseImpl(new ResourceIterable(resources, predicate,
@@ -562,7 +582,7 @@ public class ClusterControllerImpl implements ClusterController {
    * @return a page response containing a page of resources
    */
   private PageResponse getPageFromOffset(int pageSize, int offset,
-                                         NavigableSet<Resource> resources,
+      Set<Resource> resources,
                                          Predicate predicate,
                                          ResourcePredicateEvaluator evaluator) {
 
@@ -603,7 +623,7 @@ public class ClusterControllerImpl implements ClusterController {
    * @return a page response containing a page of resources
    */
   private PageResponse getPageToOffset(int pageSize, int offset,
-                                       NavigableSet<Resource> resources,
+      Set<Resource> resources,
                                        Predicate predicate,
                                        ResourcePredicateEvaluator evaluator) {
 
@@ -726,10 +746,10 @@ public class ClusterControllerImpl implements ClusterController {
      */
     private ResourceIterator(Set<Resource> resources, Predicate predicate,
                              ResourcePredicateEvaluator evaluator) {
-      this.iterator     = resources.iterator();
+      iterator     = resources.iterator();
       this.predicate    = predicate;
       this.evaluator    = evaluator;
-      this.nextResource = getNextResource();
+      nextResource = getNextResource();
     }
 
     // ----- Iterator --------------------------------------------------------
@@ -746,7 +766,7 @@ public class ClusterControllerImpl implements ClusterController {
       }
 
       Resource currentResource = nextResource;
-      this.nextResource = getNextResource();
+      nextResource = getNextResource();
 
       return currentResource;
     }

+ 2 - 2
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/PageRequestImpl.java

@@ -18,12 +18,12 @@
 
 package org.apache.ambari.server.controller.internal;
 
+import java.util.Comparator;
+
 import org.apache.ambari.server.controller.spi.PageRequest;
 import org.apache.ambari.server.controller.spi.Predicate;
 import org.apache.ambari.server.controller.spi.Resource;
 
-import java.util.Comparator;
-
 /**
  * Basic page request implementation.
  */

+ 68 - 6
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RequestImpl.java

@@ -18,9 +18,6 @@
 
 package org.apache.ambari.server.controller.internal;
 
-import org.apache.ambari.server.controller.spi.Request;
-import org.apache.ambari.server.controller.spi.TemporalInfo;
-
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -28,6 +25,10 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.ResourceProvider;
+import org.apache.ambari.server.controller.spi.TemporalInfo;
+
 /**
  * Default request implementation.
  */
@@ -56,6 +57,17 @@ public class RequestImpl implements Request {
    */
   private Map<String, TemporalInfo> m_mapTemporalInfo = new HashMap<String, TemporalInfo>();
 
+  /**
+   * An optional page request which a concrete {@link ResourceProvider} can use
+   * to return a slice of results.
+   */
+  private PageInfo m_pageInfo = null;
+
+  /**
+   * An optional sort request which a concrete {@link ResourceProvider} can use
+   * to return sorted results.
+   */
+  private SortInfo m_sortInfo = null;
 
   // ----- Constructors ------------------------------------------------------
 
@@ -112,10 +124,60 @@ public class RequestImpl implements Request {
     m_mapTemporalInfo = mapTemporalInfo;
   }
 
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public PageInfo getPageInfo() {
+    return m_pageInfo;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public SortInfo getSortInfo() {
+    return m_sortInfo;
+  }
+
+  /**
+   * Sets the pagination request that a {@link ResourceProvider} can optionally
+   * use when creating its result set.
+   * <p/>
+   * If the result set is being paginated by the {@link ResourceProvider}, then
+   * {@link PageInfo#isResponsePaged()} must be invoked with {@code true} by the
+   * provider.
+   *
+   * @param pageInfo
+   *          the page request, or {@code null} if none.
+   */
+  public void setPageInfo(PageInfo pageInfo) {
+    m_pageInfo = pageInfo;
+  }
+
+  /**
+   * Sets the sorting request that a {@link ResourceProvider} can optionally use
+   * when creating its result set.
+   * <p/>
+   * If the result set is being paginated by the {@link ResourceProvider}, then
+   * {@link SortInfo#isResponseSorted()} must be invoked with {@code true} by
+   * the provider.
+   *
+   * @param sortInfo
+   *          the sort request, or {@code null} if none.
+   */
+  public void setSortInfo(SortInfo sortInfo) {
+    m_sortInfo = sortInfo;
+  }
+
   @Override
   public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
 
     RequestImpl request = (RequestImpl) o;
 
@@ -130,6 +192,7 @@ public class RequestImpl implements Request {
     return result;
   }
 
+  @Override
   public String toString() {
     StringBuilder sb = new StringBuilder();
     sb.append("Request:"
@@ -157,5 +220,4 @@ public class RequestImpl implements Request {
     sb.append(" ]");
     return sb.toString();
   }
-
 }

+ 159 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Request.java

@@ -61,4 +61,163 @@ public interface Request {
    * @return the temporal information for the given property id; null if noe exists
    */
   public TemporalInfo getTemporalInfo(String id);
+
+  /**
+   * Obtain the pagination request information. This structure can be used for
+   * the concrete {@link ResourceProvider} to instruct the caller that the
+   * result set has already been paginated.
+   *
+   * @return the page request information.
+   */
+  public PageInfo getPageInfo();
+
+  /**
+   * Obtain information to order the results by. This structure can be used for
+   * the concrete {@link ResourceProvider} to instruct the caller that the
+   * result set has already been sorted.
+   *
+   * @return the sort request information.
+   */
+  public SortInfo getSortInfo();
+
+  /**
+   * The {@link PageInfo} class encapsulates the page request and optional
+   * provider page response data. It is used so that a {@link ResourceProvider}
+   * can indicate whether the provided {@link PageRequest} was already applied
+   * to the result set.
+   */
+  public final class PageInfo {
+    /**
+     * {@code true} if the reponse is already paginated.
+     */
+    private boolean m_pagedResponse = false;
+
+    /**
+     * If {@link #m_pagedResponse} is {@code true} then this value will
+     * represent the total items that the page slice originated from.
+     */
+    private int m_totalCount = 0;
+
+    /**
+     * The initial page request, not {@code null}.
+     */
+    private final PageRequest m_pageRequest;
+
+    /**
+     * Constructor.
+     *
+     * @param pageRequest
+     */
+    public PageInfo(PageRequest pageRequest) {
+      m_pageRequest = pageRequest;
+    }
+
+    /**
+     * Gets the page request.
+     *
+     * @return the page request (never {@code null}).
+     */
+    public PageRequest getPageRequest() {
+      return m_pageRequest;
+    }
+
+    /**
+     * Gets whether the response has already been paginated from a larger result
+     * set.
+     *
+     * @return {@code true} if the response is already paged, {@code false}
+     *         otherwise.
+     */
+    public boolean isResponsePaged() {
+      return m_pagedResponse;
+    }
+
+    /**
+     * Gets whether the response has already been paginated from a larger result
+     * set.
+     *
+     * @param responsePaged
+     *          {@code true} if the response is already paged, {@code false}
+     *          otherwise.
+     */
+    public void setResponsePaged(boolean responsePaged) {
+      m_pagedResponse = responsePaged;
+    }
+
+    /**
+     * Gets the total count of items from which the paged result set was taken.
+     *
+     * @return the totalCount the total count of items in the un-paginated set.
+     */
+    public int getTotalCount() {
+      return m_totalCount;
+    }
+
+    /**
+     * Sets the total count of items from which the paged result set was taken.
+     *
+     * @param totalCount
+     *          the totalCount the total count of items in the un-paginated set.
+     */
+    public void setTotalCount(int totalCount) {
+      m_totalCount = totalCount;
+    }
+  }
+
+  /**
+   * The {@link SortInfo} class encapsulates the sort request and optional
+   * provider sort response data. It is used so that a {@link ResourceProvider}
+   * can indicate whether the provided {@link SortRequest} was already applied
+   * to the result set.
+   */
+  public final class SortInfo {
+    /**
+     * {@code true} if the response is already sorted.
+     */
+    private boolean m_sortedResponse = false;
+
+    /**
+     * The initial sort request, not {@code null}.
+     */
+    private final SortRequest m_sortRequest;
+
+    /**
+     * Constructor.
+     *
+     * @param sortRequest
+     */
+    public SortInfo(SortRequest sortRequest) {
+      m_sortRequest = sortRequest;
+    }
+
+    /**
+     * Gets the sort request.
+     *
+     * @return the sort request (never {@code null}).
+     */
+    public SortRequest getSortRequest() {
+      return m_sortRequest;
+    }
+
+    /**
+     * Sets whether the response is already sorted.
+     *
+     * @param responseSorted
+     *          {@code true} if the response is already sorted, {@code false}
+     *          otherwise.
+     */
+    public void setResponseSorted(boolean responseSorted) {
+      m_sortedResponse = responseSorted;
+    }
+
+    /**
+     * Gets whether the response is already sorted.
+     *
+     * @return {@code true} if the response is already sorted, {@code false}
+     *         otherwise.
+     */
+    public boolean isResponseSorted() {
+      return m_sortedResponse;
+    }
+  }
 }

+ 31 - 10
ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/PropertyHelper.java

@@ -30,6 +30,8 @@ import java.util.regex.Pattern;
 import org.apache.ambari.server.controller.internal.PropertyInfo;
 import org.apache.ambari.server.controller.internal.RequestImpl;
 import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.Request.PageInfo;
+import org.apache.ambari.server.controller.spi.Request.SortInfo;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.spi.TemporalInfo;
 import org.codehaus.jackson.map.ObjectMapper;
@@ -338,7 +340,7 @@ public class PropertyHelper {
    * @param propertyIds  the property ids associated with the request; may be null
    */
   public static Request getReadRequest(Set<String> propertyIds) {
-    return new RequestImpl(propertyIds,  null, null, null);
+    return PropertyHelper.getReadRequest(propertyIds, null);
   }
 
   /**
@@ -350,20 +352,38 @@ public class PropertyHelper {
    */
   public static Request getReadRequest(Set<String> propertyIds, Map<String,
       TemporalInfo> mapTemporalInfo) {
-    return new RequestImpl(propertyIds,  null, null, mapTemporalInfo);
+    return PropertyHelper.getReadRequest(propertyIds, null, mapTemporalInfo,
+        null, null);
   }
 
   /**
-   * Factory method to create a read request from the given set of property ids.  The set of
-   * property ids represents the properties of interest for the query.
+   * Factory method to create a read request from the given set of property ids.
+   * The set of property ids represents the properties of interest for the
+   * query.
    *
-   * @param propertyIds      the property ids associated with the request; may be null
-   * @param requestInfoProperties request info properties
-   * @param mapTemporalInfo  the temporal info
+   * @param propertyIds
+   *          the property ids associated with the request; may be null
+   * @param requestInfoProperties
+   *          request info properties
+   * @param mapTemporalInfo
+   *          the temporal info
+   * @param pageInfo
+   *          an optional page request, or {@code null} for none.
+   * @param sortInfo
+   *          an optional sort request, or {@code null} for none.
    */
   public static Request getReadRequest(Set<String> propertyIds,
-      Map<String, String> requestInfoProperties, Map<String, TemporalInfo> mapTemporalInfo) {
-    return new RequestImpl(propertyIds, null, requestInfoProperties, mapTemporalInfo);
+      Map<String, String> requestInfoProperties,
+      Map<String, TemporalInfo> mapTemporalInfo, PageInfo pageInfo,
+      SortInfo sortInfo) {
+
+    RequestImpl request = new RequestImpl(propertyIds, null,
+        requestInfoProperties,
+        mapTemporalInfo);
+
+    request.setPageInfo(pageInfo);
+    request.setSortInfo(sortInfo);
+    return request;
   }
 
   /**
@@ -373,7 +393,8 @@ public class PropertyHelper {
    * @param propertyIds  the property ids associated with the request; may be null
    */
   public static Request getReadRequest(String ... propertyIds) {
-    return new RequestImpl(new HashSet<String>(Arrays.asList(propertyIds)),  null, null, null);
+    return PropertyHelper.getReadRequest(new HashSet<String>(
+        Arrays.asList(propertyIds)));
   }
 
   /**

+ 26 - 0
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java

@@ -22,9 +22,11 @@ import java.util.List;
 import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
 import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Order;
 import javax.persistence.metamodel.SingularAttribute;
 
 import org.apache.ambari.server.api.query.JpaPredicateVisitor;
+import org.apache.ambari.server.api.query.JpaSortBuilder;
 import org.apache.ambari.server.controller.AlertNoticeRequest;
 import org.apache.ambari.server.controller.spi.Predicate;
 import org.apache.ambari.server.controller.utilities.PredicateHelper;
@@ -305,10 +307,34 @@ public class AlertDispatchDAO {
       query.where(jpaPredicate);
     }
 
+    // sorting
+    JpaSortBuilder<AlertNoticeEntity> sortBuilder = new JpaSortBuilder<AlertNoticeEntity>();
+    List<Order> sortOrders = sortBuilder.buildSortOrders(request.Sort, visitor);
+    query.orderBy(sortOrders);
+
+    // pagination
     TypedQuery<AlertNoticeEntity> typedQuery = entityManager.createQuery(query);
+    if (null != request.Pagination) {
+      typedQuery.setFirstResult(request.Pagination.getOffset());
+      typedQuery.setMaxResults(request.Pagination.getPageSize());
+    }
+
     return daoUtils.selectList(typedQuery);
   }
 
+  /**
+   * Gets the total count of all {@link AlertNoticeEntity} rows that match the
+   * specified {@link Predicate}.
+   *
+   * @param predicate
+   *          the predicate to apply, or {@code null} for none.
+   * @return the total count of rows that would be returned in a result set.
+   */
+  @Transactional
+  public int getNoticesCount(Predicate predicate) {
+    return 0;
+  }
+
   /**
    * Persists new alert groups.
    *

+ 30 - 2
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertsDAO.java

@@ -25,9 +25,11 @@ import java.util.List;
 import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
 import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Order;
 import javax.persistence.metamodel.SingularAttribute;
 
 import org.apache.ambari.server.api.query.JpaPredicateVisitor;
+import org.apache.ambari.server.api.query.JpaSortBuilder;
 import org.apache.ambari.server.controller.AlertHistoryRequest;
 import org.apache.ambari.server.controller.spi.Predicate;
 import org.apache.ambari.server.controller.utilities.PredicateHelper;
@@ -56,13 +58,13 @@ public class AlertsDAO {
    * JPA entity manager
    */
   @Inject
-  Provider<EntityManager> entityManagerProvider;
+  private Provider<EntityManager> entityManagerProvider;
 
   /**
    * DAO utilities for dealing mostly with {@link TypedQuery} results.
    */
   @Inject
-  DaoUtils daoUtils;
+  private DaoUtils daoUtils;
 
   /**
    * Gets an alert with the specified ID.
@@ -214,10 +216,36 @@ public class AlertsDAO {
       query.where(jpaPredicate);
     }
 
+    // sorting
+    JpaSortBuilder<AlertHistoryEntity> sortBuilder = new JpaSortBuilder<AlertHistoryEntity>();
+    List<Order> sortOrders = sortBuilder.buildSortOrders(request.Sort, visitor);
+    query.orderBy(sortOrders);
+
+    // pagination
     TypedQuery<AlertHistoryEntity> typedQuery = entityManager.createQuery(query);
+    if( null != request.Pagination ){
+      typedQuery.setFirstResult(request.Pagination.getOffset());
+      typedQuery.setMaxResults(request.Pagination.getPageSize());
+    }
+
+    typedQuery = setQueryRefreshHint(typedQuery);
+
     return daoUtils.selectList(typedQuery);
   }
 
+  /**
+   * Gets the total count of all {@link AlertHistoryEntity} rows that match the
+   * specified {@link Predicate}.
+   *
+   * @param predicate
+   *          the predicate to apply, or {@code null} for none.
+   * @return the total count of rows that would be returned in a result set.
+   */
+  @Transactional
+  public int getCount(Predicate predicate) {
+    return 0;
+  }
+
   /**
    * Gets the current alerts.
    *

+ 3 - 0
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertHistoryEntity_.java

@@ -88,6 +88,9 @@ public class AlertHistoryEntity_ {
     mapping.put(AlertHistoryResourceProvider.ALERT_HISTORY_HOSTNAME,
         Collections.singletonList(hostName));
 
+    mapping.put(AlertHistoryResourceProvider.ALERT_HISTORY_LABEL,
+        Collections.singletonList(alertLabel));
+
     // AlertHistory.alertDefinition.definitionName = foo
     mapping.put(AlertHistoryResourceProvider.ALERT_HISTORY_DEFINITION_NAME,
         Arrays.asList(alertDefinition, AlertDefinitionEntity_.definitionName));

+ 88 - 11
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ClusterControllerImplTest.java

@@ -18,12 +18,11 @@
 
 package org.apache.ambari.server.controller.internal;
 
-import junit.framework.Assert;
-import org.apache.ambari.server.controller.spi.*;
-import org.apache.ambari.server.controller.utilities.PredicateHelper;
-import org.apache.ambari.server.controller.utilities.PropertyHelper;
-import org.apache.ambari.server.controller.utilities.PredicateBuilder;
-import org.junit.Test;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -38,11 +37,32 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
+import junit.framework.Assert;
+
+import org.apache.ambari.server.controller.spi.ClusterController;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.NoSuchResourceException;
+import org.apache.ambari.server.controller.spi.PageRequest;
+import org.apache.ambari.server.controller.spi.PageResponse;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.PropertyProvider;
+import org.apache.ambari.server.controller.spi.ProviderModule;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.Request.PageInfo;
+import org.apache.ambari.server.controller.spi.Request.SortInfo;
+import org.apache.ambari.server.controller.spi.RequestStatus;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
+import org.apache.ambari.server.controller.spi.ResourceProvider;
+import org.apache.ambari.server.controller.spi.SortRequest;
+import org.apache.ambari.server.controller.spi.SortRequestProperty;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
+import org.apache.ambari.server.controller.utilities.PredicateBuilder;
+import org.apache.ambari.server.controller.utilities.PredicateHelper;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.easymock.EasyMock;
+import org.junit.Test;
 
 /**
  * Cluster controller tests
@@ -886,6 +906,63 @@ public class ClusterControllerImplTest {
     }
   }
 
+  /**
+   * Tests that when a {@link ResourceProviderPageResponse} is present on the
+   * {@link Request}, in-memory paging is not performed.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testResourceProviderResponse() throws Exception {
+    ProviderModule providerModule = createNiceMock(ProviderModule.class);
+    ResourceProvider resourceProvider = createNiceMock(ResourceProvider.class);
+
+    expect(providerModule.getResourceProvider(Resource.Type.AlertHistory)).andReturn(
+        resourceProvider).anyTimes();
+
+    expect(
+        resourceProvider.checkPropertyIds(Collections.singleton(AlertHistoryResourceProvider.ALERT_HISTORY_HOSTNAME))).andReturn(
+        Collections.<String> emptySet()).anyTimes();
+
+    expect(
+        resourceProvider.getResources(anyObject(Request.class),
+            anyObject(Predicate.class))).andReturn(
+        Collections.<Resource> emptySet()).anyTimes();
+
+    // strict pageRequest mock to ensure that paging is not performed on
+    // the result set
+    PageRequest pageRequest = EasyMock.createStrictMock(PageRequest.class);
+
+    replay(providerModule, resourceProvider, pageRequest);
+
+    ClusterControllerImpl controller = new ClusterControllerImpl(providerModule);
+
+    Set<String> propertyIds = new HashSet<String>();
+    propertyIds.add(AlertHistoryResourceProvider.ALERT_HISTORY_HOSTNAME);
+
+    // create a result set that we will use to ensure that the contents
+    // were unmodified
+    Set<Resource> providerResources = new LinkedHashSet<Resource>();
+    providerResources.add(createNiceMock(Resource.class));
+
+    PageInfo pageInfo = new PageInfo(pageRequest);
+    pageInfo.setResponsePaged(true);
+
+    SortInfo sortInfo = null;
+
+    Request request = PropertyHelper.getReadRequest(propertyIds, null, null,
+        pageInfo, sortInfo);
+
+    Predicate predicate = new PredicateBuilder().property(
+        AlertHistoryResourceProvider.ALERT_HISTORY_HOSTNAME).equals(
+        "c6401.ambari.apache.org").toPredicate();
+
+    PageResponse pageResponse = controller.getPage(Resource.Type.AlertHistory,
+        providerResources, request, predicate, pageRequest, null);
+
+    verify(providerModule, resourceProvider, pageRequest);
+  }
+
   public static class TestProviderModule implements ProviderModule {
     private Map<Resource.Type, ResourceProvider> providers = new HashMap<Resource.Type, ResourceProvider>();
 

+ 7 - 6
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/RequestResourceProviderTest.java

@@ -18,6 +18,7 @@
 
 package org.apache.ambari.server.controller.internal;
 
+import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.capture;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.createNiceMock;
@@ -25,7 +26,6 @@ import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.reset;
 import static org.easymock.EasyMock.verify;
-import static org.easymock.EasyMock.anyObject;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -137,7 +137,8 @@ public class RequestResourceProviderTest {
       .toPredicate();
 
     request = PropertyHelper.getReadRequest(new HashSet<String>(),
-      requestInfoProperties, null);
+        requestInfoProperties, null, null, null);
+
     expect(actionManager.getRequests(Collections.<Long> emptyList()))
       .andReturn(Collections.<org.apache.ambari.server.actionmanager.Request> emptyList());
     expect(actionManager.getRequestsByStatus(null,
@@ -151,7 +152,7 @@ public class RequestResourceProviderTest {
 
     requestInfoProperties.put(BaseRequest.PAGE_SIZE_PROPERTY_KEY, "20");
     request = PropertyHelper.getReadRequest(new HashSet<String>(),
-      requestInfoProperties, null);
+        requestInfoProperties, null, null, null);
     expect(actionManager.getRequests(Collections.<Long> emptyList()))
       .andReturn(Collections.<org.apache.ambari.server.actionmanager.Request> emptyList());
     expect(actionManager.getRequestsByStatus(null, 20, false))
@@ -163,7 +164,7 @@ public class RequestResourceProviderTest {
 
     requestInfoProperties.put(BaseRequest.ASC_ORDER_PROPERTY_KEY, "true");
     request = PropertyHelper.getReadRequest(new HashSet<String>(),
-      requestInfoProperties, null);
+        requestInfoProperties, null, null, null);
     expect(actionManager.getRequests(Collections.<Long> emptyList()))
       .andReturn(Collections.<org.apache.ambari.server.actionmanager.Request> emptyList());
     expect(actionManager.getRequestsByStatus(null, 20, true))
@@ -1166,7 +1167,7 @@ public class RequestResourceProviderTest {
     Assert.assertEquals(2, capturedResourceFilter.getHostNames().size());
     Assert.assertEquals(0, actionRequest.getValue().getParameters().size());
     }
-  
+
   @Test
   public void testGetResourcesWithoutCluster() throws Exception {
     Resource.Type type = Resource.Type.Request;
@@ -1224,7 +1225,7 @@ public class RequestResourceProviderTest {
     }
 
     // verify
-    verify(managementController, actionManager, hostRoleCommand, clusters);    
+    verify(managementController, actionManager, hostRoleCommand, clusters);
   }
 
 }

+ 9 - 0
ambari-server/src/test/java/org/apache/ambari/server/orm/AlertDaoHelper.java

@@ -40,6 +40,7 @@ import org.apache.ambari.server.state.alert.SourceType;
 
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.google.inject.persist.Transactional;
 
 @Singleton
 public class AlertDaoHelper {
@@ -59,6 +60,7 @@ public class AlertDaoHelper {
   /**
    *
    */
+  @Transactional
   public void populateData(Cluster cluster) throws Exception {
     // remove any definitions and start over
     List<AlertDefinitionEntity> definitions = m_definitionDao.findAll();
@@ -158,6 +160,13 @@ public class AlertDaoHelper {
     nnPendingNotice.setUuid(UUID.randomUUID().toString());
     m_dispatchDAO.create(nnPendingNotice);
 
+    AlertNoticeEntity dnDeliveredNotice = new AlertNoticeEntity();
+    dnDeliveredNotice.setAlertHistory(dnHistory);
+    dnDeliveredNotice.setAlertTarget(administrators);
+    dnDeliveredNotice.setNotifyState(NotificationState.FAILED);
+    dnDeliveredNotice.setUuid(UUID.randomUUID().toString());
+    m_dispatchDAO.create(dnDeliveredNotice);
+
     AlertNoticeEntity aggregateFailedNotice = new AlertNoticeEntity();
     aggregateFailedNotice.setAlertHistory(aggregateHistory);
     aggregateFailedNotice.setAlertTarget(operators);

+ 112 - 4
ambari-server/src/test/java/org/apache/ambari/server/orm/dao/AlertDispatchDAOTest.java

@@ -36,7 +36,13 @@ import junit.framework.Assert;
 
 import org.apache.ambari.server.controller.AlertNoticeRequest;
 import org.apache.ambari.server.controller.internal.AlertNoticeResourceProvider;
+import org.apache.ambari.server.controller.internal.PageRequestImpl;
+import org.apache.ambari.server.controller.internal.SortRequestImpl;
+import org.apache.ambari.server.controller.spi.PageRequest.StartingPoint;
 import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.SortRequest;
+import org.apache.ambari.server.controller.spi.SortRequest.Order;
+import org.apache.ambari.server.controller.spi.SortRequestProperty;
 import org.apache.ambari.server.controller.utilities.PredicateBuilder;
 import org.apache.ambari.server.orm.AlertDaoHelper;
 import org.apache.ambari.server.orm.GuiceJpaInitializer;
@@ -499,11 +505,11 @@ public class AlertDispatchDAOTest {
     request.Predicate = clusterPredicate;
 
     List<AlertNoticeEntity> notices = m_dao.findAllNotices(request);
-    assertEquals(2, notices.size());
+    assertEquals(3, notices.size());
 
     request.Predicate = hdfsPredicate;
     notices = m_dao.findAllNotices(request);
-    assertEquals(1, notices.size());
+    assertEquals(2, notices.size());
 
     request.Predicate = yarnPredicate;
     notices = m_dao.findAllNotices(request);
@@ -511,11 +517,11 @@ public class AlertDispatchDAOTest {
 
     request.Predicate = adminPredicate;
     notices = m_dao.findAllNotices(request);
-    assertEquals(1, notices.size());
+    assertEquals(2, notices.size());
 
     request.Predicate = adminOrOperatorPredicate;
     notices = m_dao.findAllNotices(request);
-    assertEquals(2, notices.size());
+    assertEquals(3, notices.size());
 
     request.Predicate = pendingPredicate;
     notices = m_dao.findAllNotices(request);
@@ -530,6 +536,108 @@ public class AlertDispatchDAOTest {
     assertEquals(1, notices.size());
   }
 
+  /**
+   * Tests that JPA does the pagination work for us.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testAlertNoticePagination() throws Exception {
+    Cluster cluster = initializeNewCluster();
+    m_alertHelper.populateData(cluster);
+
+    AlertNoticeRequest request = new AlertNoticeRequest();
+    request.Pagination = null;
+
+    // get back all 3
+    List<AlertNoticeEntity> notices = m_dao.findAllNotices(request);
+    assertEquals(3, notices.size());
+
+    // only the first 2
+    request.Pagination = new PageRequestImpl(StartingPoint.Beginning, 2, 0,
+        null, null);
+
+    notices = m_dao.findAllNotices(request);
+    assertEquals(2, notices.size());
+
+    // the 2nd and 3rd
+    request.Pagination = new PageRequestImpl(StartingPoint.Beginning, 1, 2,
+        null, null);
+
+    notices = m_dao.findAllNotices(request);
+    assertEquals(1, notices.size());
+
+    // none b/c we're out of index
+    request.Pagination = new PageRequestImpl(StartingPoint.Beginning, 1, 3,
+        null, null);
+
+    notices = m_dao.findAllNotices(request);
+    assertEquals(0, notices.size());
+  }
+
+  /**
+   * Tests that JPA does the sorting work for us.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testAlertNoticeSorting() throws Exception {
+    Cluster cluster = initializeNewCluster();
+    m_alertHelper.populateData(cluster);
+
+    List<SortRequestProperty> sortProperties = new ArrayList<SortRequestProperty>();
+    SortRequest sortRequest = new SortRequestImpl(sortProperties);
+
+    AlertNoticeRequest request = new AlertNoticeRequest();
+    request.Sort = sortRequest;
+
+    Predicate clusterPredicate = new PredicateBuilder().property(
+        AlertNoticeResourceProvider.ALERT_NOTICE_CLUSTER_NAME).equals("c1").toPredicate();
+
+    request.Predicate = clusterPredicate;
+
+    sortProperties.add(new SortRequestProperty(
+        AlertNoticeResourceProvider.ALERT_NOTICE_ID, Order.ASC));
+
+    // get back all 3
+    List<AlertNoticeEntity> notices = m_dao.findAllNotices(request);
+    assertEquals(3, notices.size());
+
+    // assert sorting ASC
+    long lastId = 0L;
+    for (AlertNoticeEntity notice : notices) {
+      if (lastId == 0L) {
+        lastId = notice.getNotificationId();
+        continue;
+      }
+
+      long currentId = notice.getNotificationId();
+      assertTrue(lastId < currentId);
+      lastId = currentId;
+    }
+
+    // clear and do DESC
+    sortProperties.clear();
+    sortProperties.add(new SortRequestProperty(
+        AlertNoticeResourceProvider.ALERT_NOTICE_ID, Order.DESC));
+
+    notices = m_dao.findAllNotices(request);
+    assertEquals(3, notices.size());
+
+    // assert sorting DESC
+    lastId = 0L;
+    for (AlertNoticeEntity notice : notices) {
+      if (lastId == 0L) {
+        lastId = notice.getNotificationId();
+        continue;
+      }
+
+      long currentId = notice.getNotificationId();
+      assertTrue(lastId > currentId);
+      lastId = currentId;
+    }
+  }
+
   /**
    * @return
    */

+ 108 - 0
ambari-server/src/test/java/org/apache/ambari/server/orm/dao/AlertsDAOTest.java

@@ -39,7 +39,13 @@ import junit.framework.Assert;
 
 import org.apache.ambari.server.controller.AlertHistoryRequest;
 import org.apache.ambari.server.controller.internal.AlertHistoryResourceProvider;
+import org.apache.ambari.server.controller.internal.PageRequestImpl;
+import org.apache.ambari.server.controller.internal.SortRequestImpl;
+import org.apache.ambari.server.controller.spi.PageRequest.StartingPoint;
 import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.SortRequest;
+import org.apache.ambari.server.controller.spi.SortRequest.Order;
+import org.apache.ambari.server.controller.spi.SortRequestProperty;
 import org.apache.ambari.server.controller.utilities.PredicateBuilder;
 import org.apache.ambari.server.events.listeners.AlertMaintenanceModeListener;
 import org.apache.ambari.server.events.publishers.AmbariEventPublisher;
@@ -866,6 +872,108 @@ public class AlertsDAOTest {
     assertEquals(1, histories.size());
   }
 
+  /**
+   * Tests that JPA does the pagination work for us.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testAlertHistoryPagination() throws Exception {
+    Cluster cluster = initializeNewCluster();
+    m_alertHelper.populateData(cluster);
+
+    AlertHistoryRequest request = new AlertHistoryRequest();
+    request.Pagination = null;
+
+    // get back all 3
+    List<AlertHistoryEntity> histories = m_dao.findAll(request);
+    assertEquals(3, histories.size());
+
+    // only the first 2
+    request.Pagination = new PageRequestImpl(StartingPoint.Beginning, 2, 0,
+        null, null);
+
+    histories = m_dao.findAll(request);
+    assertEquals(2, histories.size());
+
+    // the 2nd and 3rd
+    request.Pagination = new PageRequestImpl(StartingPoint.Beginning, 1, 2,
+        null, null);
+
+    histories = m_dao.findAll(request);
+    assertEquals(1, histories.size());
+
+    // none b/c we're out of index
+    request.Pagination = new PageRequestImpl(StartingPoint.Beginning, 1, 3,
+        null, null);
+
+    histories = m_dao.findAll(request);
+    assertEquals(0, histories.size());
+  }
+
+  /**
+   * Tests that JPA does the sorting work for us.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testAlertHistorySorting() throws Exception {
+    Cluster cluster = initializeNewCluster();
+    m_alertHelper.populateData(cluster);
+
+    List<SortRequestProperty> sortProperties = new ArrayList<SortRequestProperty>();
+    SortRequest sortRequest = new SortRequestImpl(sortProperties);
+    AlertHistoryRequest request = new AlertHistoryRequest();
+    request.Sort = sortRequest;
+
+    Predicate clusterPredicate = new PredicateBuilder().property(
+        AlertHistoryResourceProvider.ALERT_HISTORY_CLUSTER_NAME).equals("c1").toPredicate();
+
+    request.Predicate = clusterPredicate;
+
+    sortProperties.add(new SortRequestProperty(
+        AlertHistoryResourceProvider.ALERT_HISTORY_SERVICE_NAME, Order.ASC));
+
+    // get back all 3
+    List<AlertHistoryEntity> histories = m_dao.findAll(request);
+    assertEquals(3, histories.size());
+
+    // assert sorting ASC
+    String lastServiceName = null;
+    for (AlertHistoryEntity history : histories) {
+      if (null == lastServiceName) {
+        lastServiceName = history.getServiceName();
+        continue;
+      }
+
+      String currentServiceName = history.getServiceName();
+      assertTrue(lastServiceName.compareTo(currentServiceName) <= 0);
+      lastServiceName = currentServiceName;
+    }
+
+    // clear and do DESC
+    sortProperties.clear();
+    sortProperties.add(new SortRequestProperty(
+        AlertHistoryResourceProvider.ALERT_HISTORY_SERVICE_NAME, Order.DESC));
+
+    // get back all 3
+    histories = m_dao.findAll(request);
+    assertEquals(3, histories.size());
+
+    // assert sorting DESC
+    lastServiceName = null;
+    for (AlertHistoryEntity history : histories) {
+      if (null == lastServiceName) {
+        lastServiceName = history.getServiceName();
+        continue;
+      }
+
+      String currentServiceName = history.getServiceName();
+      assertTrue(lastServiceName.compareTo(currentServiceName) >= 0);
+      lastServiceName = currentServiceName;
+    }
+  }
+
   private Cluster initializeNewCluster() throws Exception {
     String clusterName = "cluster-" + System.currentTimeMillis();
     m_clusters.addCluster(clusterName);