Merge branch 'openjdk-java-security'
diff --git a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java
index a5e1894..e93d97e 100644
--- a/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java
+++ b/airavata-api/airavata-api-server/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java
@@ -1160,7 +1160,7 @@
                 searchCriteria.setValue(gatewayId + ":PROJECT");
                 sharingFilters.add(searchCriteria);
                 sharingClient.searchEntities(authzToken.getClaimsMap().get(Constants.GATEWAY_ID),
-                        userName + "@" + gatewayId, sharingFilters, 0, -1).stream().forEach(e -> accessibleProjIds.add(e.getEntityId()));
+                        userName + "@" + gatewayId, sharingFilters, 0, Integer.MAX_VALUE).stream().forEach(e -> accessibleProjIds.add(e.getEntityId()));
                 if (accessibleProjIds.isEmpty()) {
                     result = Collections.emptyList();
                 } else {
@@ -1208,17 +1208,81 @@
         SharingRegistryService.Client sharingClient = sharingClientPool.getResource();
         try {
             List<String> accessibleExpIds = new ArrayList<>();
-            if (ServerSettings.isEnableSharing()) {
-                List<SearchCriteria> sharingFilters = new ArrayList<>();
-                SearchCriteria searchCriteria = new SearchCriteria();
-                searchCriteria.setSearchField(EntitySearchField.ENTITY_TYPE_ID);
-                searchCriteria.setSearchCondition(SearchCondition.EQUAL);
-                searchCriteria.setValue(gatewayId + ":EXPERIMENT");
-                sharingFilters.add(searchCriteria);
-                sharingClient.searchEntities(authzToken.getClaimsMap().get(Constants.GATEWAY_ID),
-                        userName + "@" + gatewayId, sharingFilters, 0, -1).forEach(e -> accessibleExpIds.add(e.getEntityId()));
+            Map<ExperimentSearchFields, String> filtersCopy = new HashMap<>(filters);
+            List<SearchCriteria> sharingFilters = new ArrayList<>();
+            SearchCriteria searchCriteria = new SearchCriteria();
+            searchCriteria.setSearchField(EntitySearchField.ENTITY_TYPE_ID);
+            searchCriteria.setSearchCondition(SearchCondition.EQUAL);
+            searchCriteria.setValue(gatewayId + ":EXPERIMENT");
+            sharingFilters.add(searchCriteria);
+
+            // Apply as much of the filters in the sharing API as possible,
+            // removing each filter that can be filtered via the sharing API
+            if (filtersCopy.containsKey(ExperimentSearchFields.FROM_DATE)) {
+                String fromTime = filtersCopy.remove(ExperimentSearchFields.FROM_DATE);
+                SearchCriteria fromCreatedTimeCriteria = new SearchCriteria();
+                fromCreatedTimeCriteria.setSearchField(EntitySearchField.CREATED_TIME);
+                fromCreatedTimeCriteria.setSearchCondition(SearchCondition.GTE);
+                fromCreatedTimeCriteria.setValue(fromTime);
+                sharingFilters.add(fromCreatedTimeCriteria);
             }
-            List<ExperimentSummaryModel> result = regClient.searchExperiments(gatewayId, userName, accessibleExpIds, filters, limit, offset);
+            if (filtersCopy.containsKey(ExperimentSearchFields.TO_DATE)) {
+                String toTime = filtersCopy.remove(ExperimentSearchFields.TO_DATE);
+                SearchCriteria toCreatedTimeCriteria = new SearchCriteria();
+                toCreatedTimeCriteria.setSearchField(EntitySearchField.CREATED_TIME);
+                toCreatedTimeCriteria.setSearchCondition(SearchCondition.LTE);
+                toCreatedTimeCriteria.setValue(toTime);
+                sharingFilters.add(toCreatedTimeCriteria);
+            }
+            if (filtersCopy.containsKey(ExperimentSearchFields.PROJECT_ID)) {
+                String projectId = filtersCopy.remove(ExperimentSearchFields.PROJECT_ID);
+                SearchCriteria projectParentEntityCriteria = new SearchCriteria();
+                projectParentEntityCriteria.setSearchField(EntitySearchField.PARRENT_ENTITY_ID);
+                projectParentEntityCriteria.setSearchCondition(SearchCondition.EQUAL);
+                projectParentEntityCriteria.setValue(projectId);
+                sharingFilters.add(projectParentEntityCriteria);
+            }
+            if (filtersCopy.containsKey(ExperimentSearchFields.USER_NAME)) {
+                String username = filtersCopy.remove(ExperimentSearchFields.USER_NAME);
+                SearchCriteria usernameOwnerCriteria = new SearchCriteria();
+                usernameOwnerCriteria.setSearchField(EntitySearchField.OWNER_ID);
+                usernameOwnerCriteria.setSearchCondition(SearchCondition.EQUAL);
+                usernameOwnerCriteria.setValue(username + "@" + gatewayId);
+                sharingFilters.add(usernameOwnerCriteria);
+            }
+            if (filtersCopy.containsKey(ExperimentSearchFields.EXPERIMENT_NAME)) {
+                String experimentName = filtersCopy.remove(ExperimentSearchFields.EXPERIMENT_NAME);
+                SearchCriteria experimentNameCriteria = new SearchCriteria();
+                experimentNameCriteria.setSearchField(EntitySearchField.NAME);
+                experimentNameCriteria.setSearchCondition(SearchCondition.LIKE);
+                experimentNameCriteria.setValue(experimentName);
+                sharingFilters.add(experimentNameCriteria);
+            }
+            if (filtersCopy.containsKey(ExperimentSearchFields.EXPERIMENT_DESC)) {
+                String experimentDescription = filtersCopy.remove(ExperimentSearchFields.EXPERIMENT_DESC);
+                SearchCriteria experimentDescriptionCriteria = new SearchCriteria();
+                experimentDescriptionCriteria.setSearchField(EntitySearchField.DESCRIPTION);
+                experimentDescriptionCriteria.setSearchCondition(SearchCondition.LIKE);
+                experimentDescriptionCriteria.setValue(experimentDescription);
+                sharingFilters.add(experimentDescriptionCriteria);
+            }
+            // Grab all of the matching experiments in the sharing registry
+            // unless all of the filtering can be done through the sharing API
+            int searchOffset = 0;
+            int searchLimit = Integer.MAX_VALUE;
+            boolean filteredInSharing = filtersCopy.isEmpty();
+            if (filteredInSharing) {
+                searchOffset = offset;
+                searchLimit = limit;
+            }
+            sharingClient.searchEntities(authzToken.getClaimsMap().get(Constants.GATEWAY_ID),
+                    userName + "@" + gatewayId, sharingFilters, searchOffset, searchLimit).forEach(e -> accessibleExpIds.add(e.getEntityId()));
+            int finalOffset = offset;
+            // If no more filtering to be done (either empty or all done through sharing API), set the offset to 0
+            if (filteredInSharing) {
+                finalOffset = 0;
+            }
+            List<ExperimentSummaryModel> result = regClient.searchExperiments(gatewayId, userName, accessibleExpIds, filtersCopy, limit, finalOffset);
             registryClientPool.returnResource(regClient);
             sharingClientPool.returnResource(sharingClient);
             return result;
@@ -1249,7 +1313,7 @@
     @Override
     @SecurityCheck
     public ExperimentStatistics getExperimentStatistics(AuthzToken authzToken, String gatewayId, long fromTime, long toTime,
-                                                        String userName, String applicationName, String resourceHostName)
+                                                        String userName, String applicationName, String resourceHostName, int limit, int offset)
             throws InvalidRequestException, AiravataClientException, AiravataSystemException, AuthorizationException, TException {
         RegistryService.Client regClient = registryClientPool.getResource();
         // SharingRegistryService.Client sharingClient = sharingClientPool.getResource();
@@ -1278,7 +1342,7 @@
             //         userId + "@" + gatewayId, sharingFilters, 0, Integer.MAX_VALUE).forEach(e -> accessibleExpIds.add(e.getEntityId()));
             List<String> accessibleExpIds = null;
 
-            ExperimentStatistics result = regClient.getExperimentStatistics(gatewayId, fromTime, toTime, userName, applicationName, resourceHostName, accessibleExpIds);
+            ExperimentStatistics result = regClient.getExperimentStatistics(gatewayId, fromTime, toTime, userName, applicationName, resourceHostName, accessibleExpIds, limit, offset);
             registryClientPool.returnResource(regClient);
             // sharingClientPool.returnResource(sharingClient);
             return result;
@@ -2160,7 +2224,8 @@
         existingExperiment.unsetProcesses();
         existingExperiment.unsetExperimentStatus();
         if(existingExperiment.getUserConfigurationData() != null && existingExperiment.getUserConfigurationData()
-                .getComputationalResourceScheduling() != null){
+                .getComputationalResourceScheduling() != null 
+                && existingExperiment.getUserConfigurationData().getComputationalResourceScheduling().getResourceHostId() != null){
             String compResourceId = existingExperiment.getUserConfigurationData()
                     .getComputationalResourceScheduling().getResourceHostId();
 
diff --git a/airavata-api/airavata-api-stubs/src/main/java/org/apache/airavata/api/Airavata.java b/airavata-api/airavata-api-stubs/src/main/java/org/apache/airavata/api/Airavata.java
index bd46d89..19daa18 100644
--- a/airavata-api/airavata-api-stubs/src/main/java/org/apache/airavata/api/Airavata.java
+++ b/airavata-api/airavata-api-stubs/src/main/java/org/apache/airavata/api/Airavata.java
@@ -408,6 +408,12 @@
      * @param resourceHostName
      *       Hostname id substring with which to further filter statistics.
      * 
+     * @param limit
+     *       Amount of results to be fetched.
+     * 
+     * @param offset
+     *       The starting point of the results to be fetched.
+     * 
      * 
      * 
      * @param authzToken
@@ -417,8 +423,10 @@
      * @param userName
      * @param applicationName
      * @param resourceHostName
+     * @param limit
+     * @param offset
      */
-    public org.apache.airavata.model.experiment.ExperimentStatistics getExperimentStatistics(org.apache.airavata.model.security.AuthzToken authzToken, java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName) throws org.apache.airavata.model.error.InvalidRequestException, org.apache.airavata.model.error.AiravataClientException, org.apache.airavata.model.error.AiravataSystemException, org.apache.airavata.model.error.AuthorizationException, org.apache.thrift.TException;
+    public org.apache.airavata.model.experiment.ExperimentStatistics getExperimentStatistics(org.apache.airavata.model.security.AuthzToken authzToken, java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, int limit, int offset) throws org.apache.airavata.model.error.InvalidRequestException, org.apache.airavata.model.error.AiravataClientException, org.apache.airavata.model.error.AiravataSystemException, org.apache.airavata.model.error.AuthorizationException, org.apache.thrift.TException;
 
     /**
      * 
@@ -3056,7 +3064,7 @@
 
     public void searchExperiments(org.apache.airavata.model.security.AuthzToken authzToken, java.lang.String gatewayId, java.lang.String userName, java.util.Map<org.apache.airavata.model.experiment.ExperimentSearchFields,java.lang.String> filters, int limit, int offset, org.apache.thrift.async.AsyncMethodCallback<java.util.List<org.apache.airavata.model.experiment.ExperimentSummaryModel>> resultHandler) throws org.apache.thrift.TException;
 
-    public void getExperimentStatistics(org.apache.airavata.model.security.AuthzToken authzToken, java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, org.apache.thrift.async.AsyncMethodCallback<org.apache.airavata.model.experiment.ExperimentStatistics> resultHandler) throws org.apache.thrift.TException;
+    public void getExperimentStatistics(org.apache.airavata.model.security.AuthzToken authzToken, java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, int limit, int offset, org.apache.thrift.async.AsyncMethodCallback<org.apache.airavata.model.experiment.ExperimentStatistics> resultHandler) throws org.apache.thrift.TException;
 
     public void getExperimentsInProject(org.apache.airavata.model.security.AuthzToken authzToken, java.lang.String projectId, int limit, int offset, org.apache.thrift.async.AsyncMethodCallback<java.util.List<org.apache.airavata.model.experiment.ExperimentModel>> resultHandler) throws org.apache.thrift.TException;
 
@@ -4359,13 +4367,13 @@
       throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT, "searchExperiments failed: unknown result");
     }
 
-    public org.apache.airavata.model.experiment.ExperimentStatistics getExperimentStatistics(org.apache.airavata.model.security.AuthzToken authzToken, java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName) throws org.apache.airavata.model.error.InvalidRequestException, org.apache.airavata.model.error.AiravataClientException, org.apache.airavata.model.error.AiravataSystemException, org.apache.airavata.model.error.AuthorizationException, org.apache.thrift.TException
+    public org.apache.airavata.model.experiment.ExperimentStatistics getExperimentStatistics(org.apache.airavata.model.security.AuthzToken authzToken, java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, int limit, int offset) throws org.apache.airavata.model.error.InvalidRequestException, org.apache.airavata.model.error.AiravataClientException, org.apache.airavata.model.error.AiravataSystemException, org.apache.airavata.model.error.AuthorizationException, org.apache.thrift.TException
     {
-      send_getExperimentStatistics(authzToken, gatewayId, fromTime, toTime, userName, applicationName, resourceHostName);
+      send_getExperimentStatistics(authzToken, gatewayId, fromTime, toTime, userName, applicationName, resourceHostName, limit, offset);
       return recv_getExperimentStatistics();
     }
 
-    public void send_getExperimentStatistics(org.apache.airavata.model.security.AuthzToken authzToken, java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName) throws org.apache.thrift.TException
+    public void send_getExperimentStatistics(org.apache.airavata.model.security.AuthzToken authzToken, java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, int limit, int offset) throws org.apache.thrift.TException
     {
       getExperimentStatistics_args args = new getExperimentStatistics_args();
       args.setAuthzToken(authzToken);
@@ -4375,6 +4383,8 @@
       args.setUserName(userName);
       args.setApplicationName(applicationName);
       args.setResourceHostName(resourceHostName);
+      args.setLimit(limit);
+      args.setOffset(offset);
       sendBase("getExperimentStatistics", args);
     }
 
@@ -11418,9 +11428,9 @@
       }
     }
 
-    public void getExperimentStatistics(org.apache.airavata.model.security.AuthzToken authzToken, java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, org.apache.thrift.async.AsyncMethodCallback<org.apache.airavata.model.experiment.ExperimentStatistics> resultHandler) throws org.apache.thrift.TException {
+    public void getExperimentStatistics(org.apache.airavata.model.security.AuthzToken authzToken, java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, int limit, int offset, org.apache.thrift.async.AsyncMethodCallback<org.apache.airavata.model.experiment.ExperimentStatistics> resultHandler) throws org.apache.thrift.TException {
       checkReady();
-      getExperimentStatistics_call method_call = new getExperimentStatistics_call(authzToken, gatewayId, fromTime, toTime, userName, applicationName, resourceHostName, resultHandler, this, ___protocolFactory, ___transport);
+      getExperimentStatistics_call method_call = new getExperimentStatistics_call(authzToken, gatewayId, fromTime, toTime, userName, applicationName, resourceHostName, limit, offset, resultHandler, this, ___protocolFactory, ___transport);
       this.___currentMethod = method_call;
       ___manager.call(method_call);
     }
@@ -11433,7 +11443,9 @@
       private java.lang.String userName;
       private java.lang.String applicationName;
       private java.lang.String resourceHostName;
-      public getExperimentStatistics_call(org.apache.airavata.model.security.AuthzToken authzToken, java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, org.apache.thrift.async.AsyncMethodCallback<org.apache.airavata.model.experiment.ExperimentStatistics> resultHandler, org.apache.thrift.async.TAsyncClient client, org.apache.thrift.protocol.TProtocolFactory protocolFactory, org.apache.thrift.transport.TNonblockingTransport transport) throws org.apache.thrift.TException {
+      private int limit;
+      private int offset;
+      public getExperimentStatistics_call(org.apache.airavata.model.security.AuthzToken authzToken, java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, int limit, int offset, org.apache.thrift.async.AsyncMethodCallback<org.apache.airavata.model.experiment.ExperimentStatistics> resultHandler, org.apache.thrift.async.TAsyncClient client, org.apache.thrift.protocol.TProtocolFactory protocolFactory, org.apache.thrift.transport.TNonblockingTransport transport) throws org.apache.thrift.TException {
         super(client, protocolFactory, transport, resultHandler, false);
         this.authzToken = authzToken;
         this.gatewayId = gatewayId;
@@ -11442,6 +11454,8 @@
         this.userName = userName;
         this.applicationName = applicationName;
         this.resourceHostName = resourceHostName;
+        this.limit = limit;
+        this.offset = offset;
       }
 
       public void write_args(org.apache.thrift.protocol.TProtocol prot) throws org.apache.thrift.TException {
@@ -11454,6 +11468,8 @@
         args.setUserName(userName);
         args.setApplicationName(applicationName);
         args.setResourceHostName(resourceHostName);
+        args.setLimit(limit);
+        args.setOffset(offset);
         args.write(prot);
         prot.writeMessageEnd();
       }
@@ -18571,7 +18587,7 @@
       public getExperimentStatistics_result getResult(I iface, getExperimentStatistics_args args) throws org.apache.thrift.TException {
         getExperimentStatistics_result result = new getExperimentStatistics_result();
         try {
-          result.success = iface.getExperimentStatistics(args.authzToken, args.gatewayId, args.fromTime, args.toTime, args.userName, args.applicationName, args.resourceHostName);
+          result.success = iface.getExperimentStatistics(args.authzToken, args.gatewayId, args.fromTime, args.toTime, args.userName, args.applicationName, args.resourceHostName, args.limit, args.offset);
         } catch (org.apache.airavata.model.error.InvalidRequestException ire) {
           result.ire = ire;
         } catch (org.apache.airavata.model.error.AiravataClientException ace) {
@@ -25876,7 +25892,7 @@
       }
 
       public void start(I iface, getExperimentStatistics_args args, org.apache.thrift.async.AsyncMethodCallback<org.apache.airavata.model.experiment.ExperimentStatistics> resultHandler) throws org.apache.thrift.TException {
-        iface.getExperimentStatistics(args.authzToken, args.gatewayId, args.fromTime, args.toTime, args.userName, args.applicationName, args.resourceHostName,resultHandler);
+        iface.getExperimentStatistics(args.authzToken, args.gatewayId, args.fromTime, args.toTime, args.userName, args.applicationName, args.resourceHostName, args.limit, args.offset,resultHandler);
       }
     }
 
@@ -73247,6 +73263,8 @@
     private static final org.apache.thrift.protocol.TField USER_NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("userName", org.apache.thrift.protocol.TType.STRING, (short)5);
     private static final org.apache.thrift.protocol.TField APPLICATION_NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("applicationName", org.apache.thrift.protocol.TType.STRING, (short)6);
     private static final org.apache.thrift.protocol.TField RESOURCE_HOST_NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("resourceHostName", org.apache.thrift.protocol.TType.STRING, (short)7);
+    private static final org.apache.thrift.protocol.TField LIMIT_FIELD_DESC = new org.apache.thrift.protocol.TField("limit", org.apache.thrift.protocol.TType.I32, (short)8);
+    private static final org.apache.thrift.protocol.TField OFFSET_FIELD_DESC = new org.apache.thrift.protocol.TField("offset", org.apache.thrift.protocol.TType.I32, (short)9);
 
     private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new getExperimentStatistics_argsStandardSchemeFactory();
     private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new getExperimentStatistics_argsTupleSchemeFactory();
@@ -73258,6 +73276,8 @@
     public java.lang.String userName; // required
     public java.lang.String applicationName; // required
     public java.lang.String resourceHostName; // required
+    public int limit; // required
+    public int offset; // required
 
     /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
     public enum _Fields implements org.apache.thrift.TFieldIdEnum {
@@ -73267,7 +73287,9 @@
       TO_TIME((short)4, "toTime"),
       USER_NAME((short)5, "userName"),
       APPLICATION_NAME((short)6, "applicationName"),
-      RESOURCE_HOST_NAME((short)7, "resourceHostName");
+      RESOURCE_HOST_NAME((short)7, "resourceHostName"),
+      LIMIT((short)8, "limit"),
+      OFFSET((short)9, "offset");
 
       private static final java.util.Map<java.lang.String, _Fields> byName = new java.util.HashMap<java.lang.String, _Fields>();
 
@@ -73296,6 +73318,10 @@
             return APPLICATION_NAME;
           case 7: // RESOURCE_HOST_NAME
             return RESOURCE_HOST_NAME;
+          case 8: // LIMIT
+            return LIMIT;
+          case 9: // OFFSET
+            return OFFSET;
           default:
             return null;
         }
@@ -73338,6 +73364,8 @@
     // isset id assignments
     private static final int __FROMTIME_ISSET_ID = 0;
     private static final int __TOTIME_ISSET_ID = 1;
+    private static final int __LIMIT_ISSET_ID = 2;
+    private static final int __OFFSET_ISSET_ID = 3;
     private byte __isset_bitfield = 0;
     public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
     static {
@@ -73356,11 +73384,19 @@
           new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
       tmpMap.put(_Fields.RESOURCE_HOST_NAME, new org.apache.thrift.meta_data.FieldMetaData("resourceHostName", org.apache.thrift.TFieldRequirementType.DEFAULT, 
           new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+      tmpMap.put(_Fields.LIMIT, new org.apache.thrift.meta_data.FieldMetaData("limit", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+          new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32)));
+      tmpMap.put(_Fields.OFFSET, new org.apache.thrift.meta_data.FieldMetaData("offset", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+          new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32)));
       metaDataMap = java.util.Collections.unmodifiableMap(tmpMap);
       org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(getExperimentStatistics_args.class, metaDataMap);
     }
 
     public getExperimentStatistics_args() {
+      this.limit = 50;
+
+      this.offset = 0;
+
     }
 
     public getExperimentStatistics_args(
@@ -73370,7 +73406,9 @@
       long toTime,
       java.lang.String userName,
       java.lang.String applicationName,
-      java.lang.String resourceHostName)
+      java.lang.String resourceHostName,
+      int limit,
+      int offset)
     {
       this();
       this.authzToken = authzToken;
@@ -73382,6 +73420,10 @@
       this.userName = userName;
       this.applicationName = applicationName;
       this.resourceHostName = resourceHostName;
+      this.limit = limit;
+      setLimitIsSet(true);
+      this.offset = offset;
+      setOffsetIsSet(true);
     }
 
     /**
@@ -73406,6 +73448,8 @@
       if (other.isSetResourceHostName()) {
         this.resourceHostName = other.resourceHostName;
       }
+      this.limit = other.limit;
+      this.offset = other.offset;
     }
 
     public getExperimentStatistics_args deepCopy() {
@@ -73423,6 +73467,10 @@
       this.userName = null;
       this.applicationName = null;
       this.resourceHostName = null;
+      this.limit = 50;
+
+      this.offset = 0;
+
     }
 
     public org.apache.airavata.model.security.AuthzToken getAuthzToken() {
@@ -73591,6 +73639,52 @@
       }
     }
 
+    public int getLimit() {
+      return this.limit;
+    }
+
+    public getExperimentStatistics_args setLimit(int limit) {
+      this.limit = limit;
+      setLimitIsSet(true);
+      return this;
+    }
+
+    public void unsetLimit() {
+      __isset_bitfield = org.apache.thrift.EncodingUtils.clearBit(__isset_bitfield, __LIMIT_ISSET_ID);
+    }
+
+    /** Returns true if field limit is set (has been assigned a value) and false otherwise */
+    public boolean isSetLimit() {
+      return org.apache.thrift.EncodingUtils.testBit(__isset_bitfield, __LIMIT_ISSET_ID);
+    }
+
+    public void setLimitIsSet(boolean value) {
+      __isset_bitfield = org.apache.thrift.EncodingUtils.setBit(__isset_bitfield, __LIMIT_ISSET_ID, value);
+    }
+
+    public int getOffset() {
+      return this.offset;
+    }
+
+    public getExperimentStatistics_args setOffset(int offset) {
+      this.offset = offset;
+      setOffsetIsSet(true);
+      return this;
+    }
+
+    public void unsetOffset() {
+      __isset_bitfield = org.apache.thrift.EncodingUtils.clearBit(__isset_bitfield, __OFFSET_ISSET_ID);
+    }
+
+    /** Returns true if field offset is set (has been assigned a value) and false otherwise */
+    public boolean isSetOffset() {
+      return org.apache.thrift.EncodingUtils.testBit(__isset_bitfield, __OFFSET_ISSET_ID);
+    }
+
+    public void setOffsetIsSet(boolean value) {
+      __isset_bitfield = org.apache.thrift.EncodingUtils.setBit(__isset_bitfield, __OFFSET_ISSET_ID, value);
+    }
+
     public void setFieldValue(_Fields field, java.lang.Object value) {
       switch (field) {
       case AUTHZ_TOKEN:
@@ -73649,6 +73743,22 @@
         }
         break;
 
+      case LIMIT:
+        if (value == null) {
+          unsetLimit();
+        } else {
+          setLimit((java.lang.Integer)value);
+        }
+        break;
+
+      case OFFSET:
+        if (value == null) {
+          unsetOffset();
+        } else {
+          setOffset((java.lang.Integer)value);
+        }
+        break;
+
       }
     }
 
@@ -73675,6 +73785,12 @@
       case RESOURCE_HOST_NAME:
         return getResourceHostName();
 
+      case LIMIT:
+        return getLimit();
+
+      case OFFSET:
+        return getOffset();
+
       }
       throw new java.lang.IllegalStateException();
     }
@@ -73700,6 +73816,10 @@
         return isSetApplicationName();
       case RESOURCE_HOST_NAME:
         return isSetResourceHostName();
+      case LIMIT:
+        return isSetLimit();
+      case OFFSET:
+        return isSetOffset();
       }
       throw new java.lang.IllegalStateException();
     }
@@ -73782,6 +73902,24 @@
           return false;
       }
 
+      boolean this_present_limit = true;
+      boolean that_present_limit = true;
+      if (this_present_limit || that_present_limit) {
+        if (!(this_present_limit && that_present_limit))
+          return false;
+        if (this.limit != that.limit)
+          return false;
+      }
+
+      boolean this_present_offset = true;
+      boolean that_present_offset = true;
+      if (this_present_offset || that_present_offset) {
+        if (!(this_present_offset && that_present_offset))
+          return false;
+        if (this.offset != that.offset)
+          return false;
+      }
+
       return true;
     }
 
@@ -73813,6 +73951,10 @@
       if (isSetResourceHostName())
         hashCode = hashCode * 8191 + resourceHostName.hashCode();
 
+      hashCode = hashCode * 8191 + limit;
+
+      hashCode = hashCode * 8191 + offset;
+
       return hashCode;
     }
 
@@ -73894,6 +74036,26 @@
           return lastComparison;
         }
       }
+      lastComparison = java.lang.Boolean.valueOf(isSetLimit()).compareTo(other.isSetLimit());
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+      if (isSetLimit()) {
+        lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.limit, other.limit);
+        if (lastComparison != 0) {
+          return lastComparison;
+        }
+      }
+      lastComparison = java.lang.Boolean.valueOf(isSetOffset()).compareTo(other.isSetOffset());
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+      if (isSetOffset()) {
+        lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.offset, other.offset);
+        if (lastComparison != 0) {
+          return lastComparison;
+        }
+      }
       return 0;
     }
 
@@ -73961,6 +74123,14 @@
         sb.append(this.resourceHostName);
       }
       first = false;
+      if (!first) sb.append(", ");
+      sb.append("limit:");
+      sb.append(this.limit);
+      first = false;
+      if (!first) sb.append(", ");
+      sb.append("offset:");
+      sb.append(this.offset);
+      first = false;
       sb.append(")");
       return sb.toString();
     }
@@ -74074,6 +74244,22 @@
                 org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
               }
               break;
+            case 8: // LIMIT
+              if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
+                struct.limit = iprot.readI32();
+                struct.setLimitIsSet(true);
+              } else { 
+                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+              }
+              break;
+            case 9: // OFFSET
+              if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
+                struct.offset = iprot.readI32();
+                struct.setOffsetIsSet(true);
+              } else { 
+                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+              }
+              break;
             default:
               org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
           }
@@ -74126,6 +74312,12 @@
           oprot.writeString(struct.resourceHostName);
           oprot.writeFieldEnd();
         }
+        oprot.writeFieldBegin(LIMIT_FIELD_DESC);
+        oprot.writeI32(struct.limit);
+        oprot.writeFieldEnd();
+        oprot.writeFieldBegin(OFFSET_FIELD_DESC);
+        oprot.writeI32(struct.offset);
+        oprot.writeFieldEnd();
         oprot.writeFieldStop();
         oprot.writeStructEnd();
       }
@@ -74157,7 +74349,13 @@
         if (struct.isSetResourceHostName()) {
           optionals.set(2);
         }
-        oprot.writeBitSet(optionals, 3);
+        if (struct.isSetLimit()) {
+          optionals.set(3);
+        }
+        if (struct.isSetOffset()) {
+          optionals.set(4);
+        }
+        oprot.writeBitSet(optionals, 5);
         if (struct.isSetUserName()) {
           oprot.writeString(struct.userName);
         }
@@ -74167,6 +74365,12 @@
         if (struct.isSetResourceHostName()) {
           oprot.writeString(struct.resourceHostName);
         }
+        if (struct.isSetLimit()) {
+          oprot.writeI32(struct.limit);
+        }
+        if (struct.isSetOffset()) {
+          oprot.writeI32(struct.offset);
+        }
       }
 
       @Override
@@ -74181,7 +74385,7 @@
         struct.setFromTimeIsSet(true);
         struct.toTime = iprot.readI64();
         struct.setToTimeIsSet(true);
-        java.util.BitSet incoming = iprot.readBitSet(3);
+        java.util.BitSet incoming = iprot.readBitSet(5);
         if (incoming.get(0)) {
           struct.userName = iprot.readString();
           struct.setUserNameIsSet(true);
@@ -74194,6 +74398,14 @@
           struct.resourceHostName = iprot.readString();
           struct.setResourceHostNameIsSet(true);
         }
+        if (incoming.get(3)) {
+          struct.limit = iprot.readI32();
+          struct.setLimitIsSet(true);
+        }
+        if (incoming.get(4)) {
+          struct.offset = iprot.readI32();
+          struct.setOffsetIsSet(true);
+        }
       }
     }
 
diff --git a/airavata-api/airavata-client-sdks/airavata-python-sdk/airavata/api/Airavata-remote b/airavata-api/airavata-client-sdks/airavata-python-sdk/airavata/api/Airavata-remote
index 2531223..a90b1ba 100755
--- a/airavata-api/airavata-client-sdks/airavata-python-sdk/airavata/api/Airavata-remote
+++ b/airavata-api/airavata-client-sdks/airavata-python-sdk/airavata/api/Airavata-remote
@@ -50,7 +50,7 @@
     print('   getUserProjects(AuthzToken authzToken, string gatewayId, string userName, i32 limit, i32 offset)')
     print('   searchProjects(AuthzToken authzToken, string gatewayId, string userName,  filters, i32 limit, i32 offset)')
     print('   searchExperiments(AuthzToken authzToken, string gatewayId, string userName,  filters, i32 limit, i32 offset)')
-    print('  ExperimentStatistics getExperimentStatistics(AuthzToken authzToken, string gatewayId, i64 fromTime, i64 toTime, string userName, string applicationName, string resourceHostName)')
+    print('  ExperimentStatistics getExperimentStatistics(AuthzToken authzToken, string gatewayId, i64 fromTime, i64 toTime, string userName, string applicationName, string resourceHostName, i32 limit, i32 offset)')
     print('   getExperimentsInProject(AuthzToken authzToken, string projectId, i32 limit, i32 offset)')
     print('   getUserExperiments(AuthzToken authzToken, string gatewayId, string userName, i32 limit, i32 offset)')
     print('  string createExperiment(AuthzToken authzToken, string gatewayId, ExperimentModel experiment)')
@@ -452,10 +452,10 @@
     pp.pprint(client.searchExperiments(eval(args[0]), args[1], args[2], eval(args[3]), eval(args[4]), eval(args[5]),))
 
 elif cmd == 'getExperimentStatistics':
-    if len(args) != 7:
-        print('getExperimentStatistics requires 7 args')
+    if len(args) != 9:
+        print('getExperimentStatistics requires 9 args')
         sys.exit(1)
-    pp.pprint(client.getExperimentStatistics(eval(args[0]), args[1], eval(args[2]), eval(args[3]), args[4], args[5], args[6],))
+    pp.pprint(client.getExperimentStatistics(eval(args[0]), args[1], eval(args[2]), eval(args[3]), args[4], args[5], args[6], eval(args[7]), eval(args[8]),))
 
 elif cmd == 'getExperimentsInProject':
     if len(args) != 4:
diff --git a/airavata-api/airavata-client-sdks/airavata-python-sdk/airavata/api/Airavata.py b/airavata-api/airavata-client-sdks/airavata-python-sdk/airavata/api/Airavata.py
index 94b5d17..de0b91e 100644
--- a/airavata-api/airavata-client-sdks/airavata-python-sdk/airavata/api/Airavata.py
+++ b/airavata-api/airavata-client-sdks/airavata-python-sdk/airavata/api/Airavata.py
@@ -459,7 +459,7 @@
         """
         pass
 
-    def getExperimentStatistics(self, authzToken, gatewayId, fromTime, toTime, userName, applicationName, resourceHostName):
+    def getExperimentStatistics(self, authzToken, gatewayId, fromTime, toTime, userName, applicationName, resourceHostName, limit, offset):
         """
 
         Get Experiment Statistics
@@ -483,6 +483,12 @@
         @param resourceHostName
               Hostname id substring with which to further filter statistics.
 
+        @param limit
+              Amount of results to be fetched.
+
+        @param offset
+              The starting point of the results to be fetched.
+
 
 
         Parameters:
@@ -493,6 +499,8 @@
          - userName
          - applicationName
          - resourceHostName
+         - limit
+         - offset
         """
         pass
 
@@ -4913,7 +4921,7 @@
             raise result.ae
         raise TApplicationException(TApplicationException.MISSING_RESULT, "searchExperiments failed: unknown result")
 
-    def getExperimentStatistics(self, authzToken, gatewayId, fromTime, toTime, userName, applicationName, resourceHostName):
+    def getExperimentStatistics(self, authzToken, gatewayId, fromTime, toTime, userName, applicationName, resourceHostName, limit, offset):
         """
 
         Get Experiment Statistics
@@ -4937,6 +4945,12 @@
         @param resourceHostName
               Hostname id substring with which to further filter statistics.
 
+        @param limit
+              Amount of results to be fetched.
+
+        @param offset
+              The starting point of the results to be fetched.
+
 
 
         Parameters:
@@ -4947,11 +4961,13 @@
          - userName
          - applicationName
          - resourceHostName
+         - limit
+         - offset
         """
-        self.send_getExperimentStatistics(authzToken, gatewayId, fromTime, toTime, userName, applicationName, resourceHostName)
+        self.send_getExperimentStatistics(authzToken, gatewayId, fromTime, toTime, userName, applicationName, resourceHostName, limit, offset)
         return self.recv_getExperimentStatistics()
 
-    def send_getExperimentStatistics(self, authzToken, gatewayId, fromTime, toTime, userName, applicationName, resourceHostName):
+    def send_getExperimentStatistics(self, authzToken, gatewayId, fromTime, toTime, userName, applicationName, resourceHostName, limit, offset):
         self._oprot.writeMessageBegin('getExperimentStatistics', TMessageType.CALL, self._seqid)
         args = getExperimentStatistics_args()
         args.authzToken = authzToken
@@ -4961,6 +4977,8 @@
         args.userName = userName
         args.applicationName = applicationName
         args.resourceHostName = resourceHostName
+        args.limit = limit
+        args.offset = offset
         args.write(self._oprot)
         self._oprot.writeMessageEnd()
         self._oprot.trans.flush()
@@ -14646,7 +14664,7 @@
         iprot.readMessageEnd()
         result = getExperimentStatistics_result()
         try:
-            result.success = self._handler.getExperimentStatistics(args.authzToken, args.gatewayId, args.fromTime, args.toTime, args.userName, args.applicationName, args.resourceHostName)
+            result.success = self._handler.getExperimentStatistics(args.authzToken, args.gatewayId, args.fromTime, args.toTime, args.userName, args.applicationName, args.resourceHostName, args.limit, args.offset)
             msg_type = TMessageType.REPLY
         except (TTransport.TTransportException, KeyboardInterrupt, SystemExit):
             raise
@@ -25020,6 +25038,8 @@
      - userName
      - applicationName
      - resourceHostName
+     - limit
+     - offset
     """
 
     thrift_spec = (
@@ -25031,9 +25051,11 @@
         (5, TType.STRING, 'userName', 'UTF8', None, ),  # 5
         (6, TType.STRING, 'applicationName', 'UTF8', None, ),  # 6
         (7, TType.STRING, 'resourceHostName', 'UTF8', None, ),  # 7
+        (8, TType.I32, 'limit', None, 50, ),  # 8
+        (9, TType.I32, 'offset', None, 0, ),  # 9
     )
 
-    def __init__(self, authzToken=None, gatewayId=None, fromTime=None, toTime=None, userName=None, applicationName=None, resourceHostName=None,):
+    def __init__(self, authzToken=None, gatewayId=None, fromTime=None, toTime=None, userName=None, applicationName=None, resourceHostName=None, limit=thrift_spec[8][4], offset=thrift_spec[9][4],):
         self.authzToken = authzToken
         self.gatewayId = gatewayId
         self.fromTime = fromTime
@@ -25041,6 +25063,8 @@
         self.userName = userName
         self.applicationName = applicationName
         self.resourceHostName = resourceHostName
+        self.limit = limit
+        self.offset = offset
 
     def read(self, iprot):
         if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
@@ -25087,6 +25111,16 @@
                     self.resourceHostName = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
                 else:
                     iprot.skip(ftype)
+            elif fid == 8:
+                if ftype == TType.I32:
+                    self.limit = iprot.readI32()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 9:
+                if ftype == TType.I32:
+                    self.offset = iprot.readI32()
+                else:
+                    iprot.skip(ftype)
             else:
                 iprot.skip(ftype)
             iprot.readFieldEnd()
@@ -25125,6 +25159,14 @@
             oprot.writeFieldBegin('resourceHostName', TType.STRING, 7)
             oprot.writeString(self.resourceHostName.encode('utf-8') if sys.version_info[0] == 2 else self.resourceHostName)
             oprot.writeFieldEnd()
+        if self.limit is not None:
+            oprot.writeFieldBegin('limit', TType.I32, 8)
+            oprot.writeI32(self.limit)
+            oprot.writeFieldEnd()
+        if self.offset is not None:
+            oprot.writeFieldBegin('offset', TType.I32, 9)
+            oprot.writeI32(self.offset)
+            oprot.writeFieldEnd()
         oprot.writeFieldStop()
         oprot.writeStructEnd()
 
diff --git a/airavata-api/airavata-client-sdks/airavata-python-sdk/airavata/model/workspace/ttypes.py b/airavata-api/airavata-client-sdks/airavata-python-sdk/airavata/model/workspace/ttypes.py
index b25e05e..2dd3484 100644
--- a/airavata-api/airavata-client-sdks/airavata-python-sdk/airavata/model/workspace/ttypes.py
+++ b/airavata-api/airavata-client-sdks/airavata-python-sdk/airavata/model/workspace/ttypes.py
@@ -723,6 +723,96 @@
         return not (self == other)
 
 
+class GatewayUsageReportingCommand(object):
+    """
+    Attributes:
+     - gatewayId
+     - computeResourceId
+     - command
+    """
+
+    thrift_spec = (
+        None,  # 0
+        (1, TType.STRING, 'gatewayId', 'UTF8', None, ),  # 1
+        (2, TType.STRING, 'computeResourceId', 'UTF8', None, ),  # 2
+        (3, TType.STRING, 'command', 'UTF8', None, ),  # 3
+    )
+
+    def __init__(self, gatewayId=None, computeResourceId=None, command=None,):
+        self.gatewayId = gatewayId
+        self.computeResourceId = computeResourceId
+        self.command = command
+
+    def read(self, iprot):
+        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
+            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
+            return
+        iprot.readStructBegin()
+        while True:
+            (fname, ftype, fid) = iprot.readFieldBegin()
+            if ftype == TType.STOP:
+                break
+            if fid == 1:
+                if ftype == TType.STRING:
+                    self.gatewayId = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 2:
+                if ftype == TType.STRING:
+                    self.computeResourceId = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 3:
+                if ftype == TType.STRING:
+                    self.command = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            else:
+                iprot.skip(ftype)
+            iprot.readFieldEnd()
+        iprot.readStructEnd()
+
+    def write(self, oprot):
+        if oprot._fast_encode is not None and self.thrift_spec is not None:
+            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
+            return
+        oprot.writeStructBegin('GatewayUsageReportingCommand')
+        if self.gatewayId is not None:
+            oprot.writeFieldBegin('gatewayId', TType.STRING, 1)
+            oprot.writeString(self.gatewayId.encode('utf-8') if sys.version_info[0] == 2 else self.gatewayId)
+            oprot.writeFieldEnd()
+        if self.computeResourceId is not None:
+            oprot.writeFieldBegin('computeResourceId', TType.STRING, 2)
+            oprot.writeString(self.computeResourceId.encode('utf-8') if sys.version_info[0] == 2 else self.computeResourceId)
+            oprot.writeFieldEnd()
+        if self.command is not None:
+            oprot.writeFieldBegin('command', TType.STRING, 3)
+            oprot.writeString(self.command.encode('utf-8') if sys.version_info[0] == 2 else self.command)
+            oprot.writeFieldEnd()
+        oprot.writeFieldStop()
+        oprot.writeStructEnd()
+
+    def validate(self):
+        if self.gatewayId is None:
+            raise TProtocolException(message='Required field gatewayId is unset!')
+        if self.computeResourceId is None:
+            raise TProtocolException(message='Required field computeResourceId is unset!')
+        if self.command is None:
+            raise TProtocolException(message='Required field command is unset!')
+        return
+
+    def __repr__(self):
+        L = ['%s=%r' % (key, value)
+             for key, value in self.__dict__.items()]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
+
+
 class Notification(object):
     """
     Attributes:
diff --git a/airavata-api/airavata-client-sdks/airavata-python-sdk/setup.py b/airavata-api/airavata-client-sdks/airavata-python-sdk/setup.py
index d190881..e5af942 100644
--- a/airavata-api/airavata-client-sdks/airavata-python-sdk/setup.py
+++ b/airavata-api/airavata-client-sdks/airavata-python-sdk/setup.py
@@ -10,7 +10,7 @@
 
 setup(
     name='airavata-python-sdk',
-    version='1.0.0',
+    version='1.0.1',
     packages=find_packages(),
     package_data={'transport': ['*.ini'], 'sample': ['*.pem']},
     url='http://airavata.com',
diff --git a/airavata-services/profile-service/iam-admin-services-core/src/main/java/org/apache/airavata/service/profile/iam/admin/services/core/impl/TenantManagementKeycloakImpl.java b/airavata-services/profile-service/iam-admin-services-core/src/main/java/org/apache/airavata/service/profile/iam/admin/services/core/impl/TenantManagementKeycloakImpl.java
index 7baa89d..14dc387 100644
--- a/airavata-services/profile-service/iam-admin-services-core/src/main/java/org/apache/airavata/service/profile/iam/admin/services/core/impl/TenantManagementKeycloakImpl.java
+++ b/airavata-services/profile-service/iam-admin-services-core/src/main/java/org/apache/airavata/service/profile/iam/admin/services/core/impl/TenantManagementKeycloakImpl.java
@@ -303,15 +303,15 @@
             Response httpResponse = client.realms().realm(gatewayDetails.getGatewayId()).clients().create(pgaClient);
             logger.info("Tenant Client configuration exited with code : " + httpResponse.getStatus()+" : " +httpResponse.getStatusInfo());
 
-            // Add the manage-users role to the web client
+            // Add the manage-users and manage-clients roles to the web client
             UserRepresentation serviceAccountUserRepresentation = getUserByUsername(client, gatewayDetails.getGatewayId(), "service-account-" + pgaClient.getClientId());
             UserResource serviceAccountUser = client.realms().realm(gatewayDetails.getGatewayId()).users().get(serviceAccountUserRepresentation.getId());
             String realmManagementClientId = getRealmManagementClientId(client, gatewayDetails.getGatewayId());
-            List<RoleRepresentation> manageUsersRole = serviceAccountUser.roles().clientLevel(realmManagementClientId).listAvailable()
+            List<RoleRepresentation> manageUsersAndManageClientsRoles = serviceAccountUser.roles().clientLevel(realmManagementClientId).listAvailable()
                     .stream()
-                    .filter(r -> r.getName().equals("manage-users"))
+                    .filter(r -> r.getName().equals("manage-users") || r.getName().equals("manage-clients"))
                     .collect(Collectors.toList());
-            serviceAccountUser.roles().clientLevel(realmManagementClientId).add(manageUsersRole);
+            serviceAccountUser.roles().clientLevel(realmManagementClientId).add(manageUsersAndManageClientsRoles);
 
             if(httpResponse.getStatus() == 201){
                 String ClientUUID = client.realms().realm(gatewayDetails.getGatewayId()).clients().findByClientId(pgaClient.getClientId()).get(0).getId();
diff --git a/dev-tools/ansible/inventories/scigap/production/host_vars/global-flood/vars.yml b/dev-tools/ansible/inventories/scigap/production/host_vars/global-flood/vars.yml
index 16df3d6..8b5b657 100644
--- a/dev-tools/ansible/inventories/scigap/production/host_vars/global-flood/vars.yml
+++ b/dev-tools/ansible/inventories/scigap/production/host_vars/global-flood/vars.yml
@@ -22,11 +22,11 @@
 #gateway_data_store_resource_id: "js-168-166.jetstream-cloud.org_e86fd426-201a-461a-a0b4-4368af59ca28"
 #gateway_data_store_hostname: "js-168-166.jetstream-cloud.org"
 
-vhost_servername: "globalflood.scigap.org"
+vhost_servername: "global-floods-monitoring.scigap.org"
 vhost_ssl: True
-ssl_certificate_file: "/etc/letsencrypt/live/globalflood.scigap.org/cert.pem"
-ssl_certificate_chain_file: "/etc/letsencrypt/live/globalflood.scigap.org/fullchain.pem"
-ssl_certificate_key_file: "/etc/letsencrypt/live/globalflood.scigap.org/privkey.pem"
+ssl_certificate_file: "/etc/letsencrypt/live/global-floods-monitoring.scigap.org/cert.pem"
+ssl_certificate_chain_file: "/etc/letsencrypt/live/global-floods-monitoring.scigap.org/fullchain.pem"
+ssl_certificate_key_file: "/etc/letsencrypt/live/global-floods-monitoring.scigap.org/privkey.pem"
 
 
 ## Keycloak related variables
@@ -36,7 +36,7 @@
 
 auth_options:
   password:
-    name: "Global Flood Assessment Gateway"
+    name: "Global Floods Assessment Gateway"
   external:
     - name: "Existing Institution Credentials"
       idp_alias: "cilogon"
@@ -47,7 +47,7 @@
 gateway_data_store_ssh_public_key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDPuGmLPwwXu15TCdLwsE9dFoWJu1kzQzdLukslMKjVCf05Fe+MyCP7EG5n1psGigSORGm0bo/9t9fPQGyPRUR2jIBI5lR2bE5JJ8XNotf+Gdv45FXhXHTIPSeb1bAymEQVghALxqeYOWnnP+6aL61Zy6nAokMJdkdRXFRxr0paYLbH3oaCAVdp2ggMXP+KMvkKHjMSa9aZFK/V/YrIRONKDrdK+DV7D0xk4XGplRAsw8UpS8GJipfq8uTM23Tr/UMCYUCbW8qqf0HYFohY+51lT/1JjYb+cBWjy1iuNGVQVnQsuNSOoDnW1C07V6VFzwKZJOv+rYhw6iL5rcq3fzDD"
 
 admin_emails: "[('CIRC', 'circ-iu-group@iu.edu'),('Wang Jun','wang208@iu.edu')]"
-portal_title: "Global Flood Assessment Gateway"
+portal_title: "Global Floods Assessment Gateway"
 
 #django_google_analytics_tracking_id: "UA-66348921-1"
 ...
diff --git a/dev-tools/ansible/inventories/scigap/production/host_vars/immune/vars.yml b/dev-tools/ansible/inventories/scigap/production/host_vars/immune/vars.yml
index 7054a41..d892f8a 100644
--- a/dev-tools/ansible/inventories/scigap/production/host_vars/immune/vars.yml
+++ b/dev-tools/ansible/inventories/scigap/production/host_vars/immune/vars.yml
@@ -27,6 +27,12 @@
 
 vhost_servername: "immuneportal.ccbb.iupui.edu"
 vhost_ssl: True
+vhost_aliases:
+  - url: /data/
+    path: "{{ user_data_dir }}/regsnps/regsnps-data/"
+    headers:
+      - name: "Access-Control-Allow-Origin"
+        value: '"*"'
 ssl_certificate_file: "/etc/letsencrypt/live/immuneportal.ccbb.iupui.edu/cert.pem"
 ssl_certificate_chain_file: "/etc/letsencrypt/live/immuneportal.ccbb.iupui.edu/fullchain.pem"
 ssl_certificate_key_file: "/etc/letsencrypt/live/immuneportal.ccbb.iupui.edu/privkey.pem"
diff --git a/dev-tools/ansible/inventories/scigap/production/host_vars/regsnps/vars.yml b/dev-tools/ansible/inventories/scigap/production/host_vars/regsnps/vars.yml
index d3ff111..5ce8bfc 100644
--- a/dev-tools/ansible/inventories/scigap/production/host_vars/regsnps/vars.yml
+++ b/dev-tools/ansible/inventories/scigap/production/host_vars/regsnps/vars.yml
@@ -30,6 +30,9 @@
 vhost_aliases:
   - url: /data/
     path: "{{ user_data_dir }}/regsnps/regsnps-data/"
+    headers:
+      - name: "Access-Control-Allow-Origin"
+        value: '"*"'
 ssl_certificate_file: "/etc/letsencrypt/live/regsnps.ccbb.iupui.edu/cert.pem"
 ssl_certificate_chain_file: "/etc/letsencrypt/live/regsnps.ccbb.iupui.edu/fullchain.pem"
 ssl_certificate_key_file: "/etc/letsencrypt/live/regsnps.ccbb.iupui.edu/privkey.pem"
diff --git a/dev-tools/ansible/inventories/scigap/production/host_vars/simccs/vars.yml b/dev-tools/ansible/inventories/scigap/production/host_vars/simccs/vars.yml
index 74bdd63..158f993 100644
--- a/dev-tools/ansible/inventories/scigap/production/host_vars/simccs/vars.yml
+++ b/dev-tools/ansible/inventories/scigap/production/host_vars/simccs/vars.yml
@@ -25,8 +25,7 @@
 gateway_data_store_resource_id: "scigap11.sciencegateways.iu.edu_96b8dcec-ac84-438a-9927-91baaf87758b"
 
 django_tus_endpoint: "https://tus.simccs.scigap.org/files/"
-# simccs_maptool_branch: "master"
-simccs_maptool_branch: "dev"
+simccs_maptool_branch: "master"
 airavata_django_extra_dependencies:
   # Need to separately install cython in the VM or do two deploys, one with
   # just cython, then a second with the other dependencies. Reason: pyjnius
@@ -41,7 +40,7 @@
   MAPTOOL_SETTINGS:
     CPLEX_APPLICATION_ID: "cplex-solver_99721933-c9e4-4285-9ef1-d035ca82b541"
     DATASETS_DIR: "/data/gateway-user-data/simccs-datasets"
-    JAVA_OPTIONS: "-Xmx8g"
+    JAVA_OPTIONS: "-Xmx16g"
   LOGIN_REDIRECT_URL: "simccs_maptool:home"
 
 vhost_servername: "simccs.org"
diff --git a/dev-tools/ansible/inventories/scigap/production/host_vars/testdrive/vars.yml b/dev-tools/ansible/inventories/scigap/production/host_vars/testdrive/vars.yml
index f3e6565..3125b0c 100644
--- a/dev-tools/ansible/inventories/scigap/production/host_vars/testdrive/vars.yml
+++ b/dev-tools/ansible/inventories/scigap/production/host_vars/testdrive/vars.yml
@@ -20,7 +20,8 @@
 
 ---
 airavata_django_extra_dependencies:
-  - git+https://github.com/machristie/gateways19-tutorial.git@solution#egg=gateways19-tutorial
+  - git+https://github.com/machristie/custom_ui_tutorial_app.git@main#egg=custom_ui_tutorial_app
+airavata_django_git_branch: "staging"
 vhost_servername: "testdrive.airavata.org"
 vhost_ssl: True
 ssl_certificate_file: "/etc/letsencrypt/live/testdrive.airavata.org/cert.pem"
diff --git a/dev-tools/ansible/inventories/scigap/production/host_vars/unggateway/vars.yml b/dev-tools/ansible/inventories/scigap/production/host_vars/unggateway/vars.yml
new file mode 100644
index 0000000..06df75e
--- /dev/null
+++ b/dev-tools/ansible/inventories/scigap/production/host_vars/unggateway/vars.yml
@@ -0,0 +1,53 @@
+#
+#
+# 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.
+#
+
+---
+#gateway_data_store_resource_id: "js-168-166.jetstream-cloud.org_e86fd426-201a-461a-a0b4-4368af59ca28"
+#gateway_data_store_hostname: "js-168-166.jetstream-cloud.org"
+
+vhost_servername: "gateway.ung.scigap.org"
+vhost_ssl: True
+ssl_certificate_file: "/etc/letsencrypt/live/gateway.ung.scigap.org/cert.pem"
+ssl_certificate_chain_file: "/etc/letsencrypt/live/gateway.ung.scigap.org/fullchain.pem"
+ssl_certificate_key_file: "/etc/letsencrypt/live/gateway.ung.scigap.org/privkey.pem"
+
+
+## Keycloak related variables
+tenant_domain: "gateway-ung"
+oauth_client_key: "{{ vault_oauth_client_key }}"
+oauth_client_secret: "{{ vault_oauth_client_secret }}"
+
+auth_options:
+  password:
+    name: "UNG Gateway"
+  external:
+    - name: "Existing Institution Credentials"
+      idp_alias: "cilogon"
+      logo: "images/cilogon-logo-24x24-b.png"
+
+gateway_id: "gateway-ung"
+experiment_data_dir: "{{ user_data_dir }}/gateway-ung"
+gateway_data_store_ssh_public_key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCbGE2Zb7zHfOx1IwaUQkAsRNr3Y6ieqQ2jnvbvs4XBM0ARw3ME0feMIcJKB3NFjIEQqS67w1mV0tpoCmRVtSK7IVujbCCvyRFUOLh+BOgqF7Xm887FPidOm0qpSh3s46Wbd758uXurqojedC2xJp62e4aSgzdijyX2gBI1giQHaKSE2WW6L5wGkhIQoxSvaL1hSnL7adH6e2RqB1+ePbO6OGr6pRbNIQqCgYZGvNAJBoiP9K1CwQTcGkj0b2Ptr2kD2JWQqivdPM5YVR4PxH2vtovL0HLjHvXmljWdns/b4PQGDyfpUMgKAthlAlq5zGOJZqfIGdBxLnC8AaoZNGiF"
+
+admin_emails: "[('CIRC', 'circ-iu-group@iu.edu'),('Luis Parra','Luis.CuevaParra@ung.edu'),('Smit Patel','snpate8729@ung.edu')]"
+portal_title: "UNG Gateway"
+
+#django_google_analytics_tracking_id: "UA-66348921-1"
+...
diff --git a/dev-tools/ansible/inventories/scigap/production/host_vars/unggateway/vault.yml b/dev-tools/ansible/inventories/scigap/production/host_vars/unggateway/vault.yml
new file mode 100644
index 0000000..8e64f9d
--- /dev/null
+++ b/dev-tools/ansible/inventories/scigap/production/host_vars/unggateway/vault.yml
@@ -0,0 +1,15 @@
+$ANSIBLE_VAULT;1.1;AES256
+39313531363439616433393962323631383038396434376330616536643761373134663637663031
+3735306132666365303764303132386333323731626432340a646339313164316235613261373935
+32643436666665393133353432343238306530653936643162306262613364326438313935623363
+6437656661353963630a386137393633383832346635363839393361343364616531633034383039
+31323236306535333665383733313665323335326566393234373836383265316563333765656437
+30616164353736323964373138643136616433613630303530343065343936613030313565336539
+33396331616138653430333165326638393237363164336566663565636164623265316363306532
+64363566363934366161643062306436313531636661336439316531333335363764356437363433
+33303538376336396666646564396433633233663637363062626537656634643464646165623731
+39363038366130613833636238303066313737313232386131383064306666393465616436343362
+39626164396133393130616434373363383266353266333333313364393562313334366161343166
+62626666376133333232393535373364623731333265343638373663373463633336386365363666
+33373337313434663431336338303563356131343038353963333561306465656465623763613739
+6133306532333664383262336135623865303939663563623166
diff --git a/dev-tools/ansible/inventories/scigap/production/hosts b/dev-tools/ansible/inventories/scigap/production/hosts
index 1ea4323..a2ab1fd 100644
--- a/dev-tools/ansible/inventories/scigap/production/hosts
+++ b/dev-tools/ansible/inventories/scigap/production/hosts
@@ -27,7 +27,8 @@
 [django]
 ampgateway ansible_host=156.56.104.84
 csbglsu ansible_host=156.56.104.84
-distantreader ansible_host=156.56.104.84
+# disable distantreader deploy
+# distantreader ansible_host=156.56.104.84
 epwgateway ansible_host=156.56.104.84
 georgiastate ansible_host=156.56.104.84
 hubzero ansible_host=156.56.104.84
@@ -70,6 +71,7 @@
 louisiana-state ansible_host=156.56.104.84
 hicops-deepsnap ansible_host=156.56.104.84
 ultrascan ansible_host=156.56.104.84
+unggateway ansible_host=156.56.104.84
 
 # dreg jetstream server
 dreg ansible_host=149.165.156.72 ansible_user=centos
diff --git a/dev-tools/ansible/roles/django/defaults/main.yml b/dev-tools/ansible/roles/django/defaults/main.yml
index df6b9bb..abe65d6 100644
--- a/dev-tools/ansible/roles/django/defaults/main.yml
+++ b/dev-tools/ansible/roles/django/defaults/main.yml
@@ -73,6 +73,7 @@
   - "django_airavata/static/common/dist"
   - "django_airavata/apps/admin/static/django_airavata_admin/dist"
   - "django_airavata/apps/groups/static/django_airavata_groups/dist"
+  - "django_airavata/apps/auth/static/django_airavata_auth/dist"
   - "django_airavata/apps/workspace/static/django_airavata_workspace/dist"
   - "django_airavata/apps/dataparsers/static/django_airavata_dataparsers/dist"
 
diff --git a/dev-tools/ansible/roles/django/tasks/main.yml b/dev-tools/ansible/roles/django/tasks/main.yml
index 935b973..021f4b5 100644
--- a/dev-tools/ansible/roles/django/tasks/main.yml
+++ b/dev-tools/ansible/roles/django/tasks/main.yml
@@ -148,7 +148,21 @@
     - delete older files
   with_items: "{{ django_portal_js_build_dirs }}"
 
-- name: Create virtual environment for Django portal and install dependencies
+- name: Create virtual environment for Django portal and update pip, setuptools and wheel
+  pip:
+    name: "{{ item }}"
+    virtualenv: "{{ django_venv_dir }}"
+    virtualenv_command: "{{ python_virtualenv_command }}"
+    chdir: "{{ airavata_django_checkout }}"
+    state: latest
+  become: yes
+  become_user: "{{user}}"
+  with_list:
+    - pip
+    - setuptools
+    - wheel
+
+- name: Install dependencies in virtual environment for Django portal
   pip:
     requirements: "{{ item }}"
     virtualenv: "{{ django_venv_dir }}"
diff --git a/dev-tools/ansible/roles/django/templates/django-ssl-vhost.conf.j2 b/dev-tools/ansible/roles/django/templates/django-ssl-vhost.conf.j2
index 3e6e4b7..16e5fab 100644
--- a/dev-tools/ansible/roles/django/templates/django-ssl-vhost.conf.j2
+++ b/dev-tools/ansible/roles/django/templates/django-ssl-vhost.conf.j2
@@ -61,6 +61,9 @@
     Alias "{{ alias.url }}" "{{ alias.path }}"
     <Directory "{{ alias.path }}">
         Require all granted
+        {% for header in alias.headers|default([]) %}
+        Header set {{ header.name }} {{ header.value }}
+        {% endfor %}
     </Directory>
     {% endfor %}
 
diff --git a/dev-tools/ansible/roles/django/templates/django-vhost.conf.j2 b/dev-tools/ansible/roles/django/templates/django-vhost.conf.j2
index dd9b271..e5c4398 100644
--- a/dev-tools/ansible/roles/django/templates/django-vhost.conf.j2
+++ b/dev-tools/ansible/roles/django/templates/django-vhost.conf.j2
@@ -52,6 +52,9 @@
     Alias "{{ alias.url }}" "{{ alias.path }}"
     <Directory "{{ alias.path }}">
         Require all granted
+        {% for header in alias.headers|default([]) %}
+        Header set {{ header.name }} {{ header.value }}
+        {% endfor %}
     </Directory>
     {% endfor %}
 
diff --git a/dev-tools/ansible/roles/django/templates/settings_local.py.j2 b/dev-tools/ansible/roles/django/templates/settings_local.py.j2
index 90e5bda..5fc1d1f 100644
--- a/dev-tools/ansible/roles/django/templates/settings_local.py.j2
+++ b/dev-tools/ansible/roles/django/templates/settings_local.py.j2
@@ -120,9 +120,17 @@
 AIRAVATA_API_PORT = {{ api_server_port }}
 AIRAVATA_API_SECURE = False
 {% endif %}
-GATEWAY_DATA_STORE_RESOURCE_ID = '{{ gateway_data_store_resource_id }}'
-GATEWAY_DATA_STORE_DIR = '{{ experiment_data_dir }}'
-GATEWAY_DATA_STORE_HOSTNAME = '{{ gateway_data_store_hostname }}'
+
+USER_STORAGES = {
+    'default': {
+        'BACKEND': 'airavata_django_portal_sdk.user_storage.backends.DjangoFileSystemProvider',
+        'STORAGE_RESOURCE_ID': '{{ gateway_data_store_resource_id }}',
+        'OPTIONS': {
+            'directory': '{{ experiment_data_dir }}',
+        }
+    }
+}
+
 FILE_UPLOAD_TEMP_DIR = "{{ file_upload_tmp_dir }}"
 
 # Profile Service Configuration
diff --git a/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/repositories/AbstractRepository.java b/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/repositories/AbstractRepository.java
index acbd3fb..95062c3 100644
--- a/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/repositories/AbstractRepository.java
+++ b/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/repositories/AbstractRepository.java
@@ -113,6 +113,21 @@
         return get(id) != null;
     }
 
+    public int scalarInt(String query, Map<String, Object> queryParams) {
+
+        int scalarInt = execute(entityManager -> {
+            Query jpaQuery = entityManager.createQuery(query);
+
+            for (Map.Entry<String, Object> entry : queryParams.entrySet()) {
+
+                jpaQuery.setParameter(entry.getKey(), entry.getValue());
+            }
+
+            return ((Number)jpaQuery.getSingleResult()).intValue();
+        });
+        return scalarInt;
+    }
+
     public <R> R execute(Committer<EntityManager, R> committer){
         EntityManager entityManager = null;
         try {
diff --git a/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/repositories/expcatalog/ExperimentSummaryRepository.java b/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/repositories/expcatalog/ExperimentSummaryRepository.java
index 5db3792..bcbc09a 100644
--- a/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/repositories/expcatalog/ExperimentSummaryRepository.java
+++ b/modules/registry/registry-core/src/main/java/org/apache/airavata/registry/core/repositories/expcatalog/ExperimentSummaryRepository.java
@@ -25,30 +25,32 @@
 import org.apache.airavata.model.status.ExperimentState;
 import org.apache.airavata.registry.core.entities.expcatalog.ExperimentSummaryEntity;
 import org.apache.airavata.registry.core.entities.expcatalog.JobEntity;
-import org.apache.airavata.registry.core.entities.expcatalog.ProcessEntity;
-import org.apache.airavata.registry.core.entities.expcatalog.TaskEntity;
 import org.apache.airavata.registry.core.utils.DBConstants;
 import org.apache.airavata.registry.cpi.RegistryException;
 import org.apache.airavata.registry.cpi.ResultOrderType;
-import org.apache.airavata.registry.cpi.utils.Constants;
-import org.apache.derby.vti.Restriction;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.sql.Timestamp;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.persistence.Query;
 
 public class ExperimentSummaryRepository extends ExpCatAbstractRepository<ExperimentSummaryModel, ExperimentSummaryEntity, String> {
     private final static Logger logger = LoggerFactory.getLogger(ExperimentSummaryRepository.class);
+    private final static int ACCESSIBLE_EXPERIMENT_IDS_BATCH_SIZE = 10000;
 
     public ExperimentSummaryRepository() { super(ExperimentSummaryModel.class, ExperimentSummaryEntity.class); }
 
     public List<ExperimentSummaryModel> searchAllAccessibleExperiments(List<String> accessibleExperimentIds, Map<String, String> filters, int limit,
                                                                        int offset, Object orderByIdentifier, ResultOrderType resultOrderType) throws RegistryException, IllegalArgumentException {
         String query = "SELECT ES FROM " + ExperimentSummaryEntity.class.getSimpleName() + " ES WHERE ";
+        String whereClause = "";
         Map<String, Object> queryParameters = new HashMap<>();
 
         if (filters == null || !filters.containsKey(DBConstants.Experiment.GATEWAY_ID)) {
@@ -64,50 +66,50 @@
                     + " JOIN J.task T"
                     + " JOIN T.process P"
                     + " WHERE J.jobId = : " + DBConstants.Job.JOB_ID;
-            query += "ES.experimentId IN  ( "+ query_jobId + " ) AND ";
+            whereClause += "ES.experimentId IN  ( " + query_jobId + " ) AND ";
         }
 
         if (filters.get(DBConstants.Experiment.USER_NAME) != null) {
             logger.debug("Filter Experiments by User");
             queryParameters.put(DBConstants.Experiment.USER_NAME, filters.get(DBConstants.Experiment.USER_NAME));
-            query += "ES.userName LIKE :" + DBConstants.Experiment.USER_NAME + " AND ";
+            whereClause += "ES.userName LIKE :" + DBConstants.Experiment.USER_NAME + " AND ";
         }
 
         if (filters.get(DBConstants.Experiment.GATEWAY_ID) != null) {
             logger.debug("Filter Experiments by Gateway ID");
             queryParameters.put(DBConstants.Experiment.GATEWAY_ID, filters.get(DBConstants.Experiment.GATEWAY_ID));
-            query += "ES.gatewayId LIKE :" + DBConstants.Experiment.GATEWAY_ID + " AND ";
+            whereClause += "ES.gatewayId LIKE :" + DBConstants.Experiment.GATEWAY_ID + " AND ";
         }
 
         if (filters.get(DBConstants.Experiment.PROJECT_ID) != null) {
             logger.debug("Filter Experiments by Project ID");
             queryParameters.put(DBConstants.Experiment.PROJECT_ID, filters.get(DBConstants.Experiment.PROJECT_ID));
-            query += "ES.projectId LIKE :" + DBConstants.Experiment.PROJECT_ID + " AND ";
+            whereClause += "ES.projectId LIKE :" + DBConstants.Experiment.PROJECT_ID + " AND ";
         }
 
         if (filters.get(DBConstants.Experiment.EXPERIMENT_NAME) != null) {
             logger.debug("Filter Experiments by Name");
             queryParameters.put(DBConstants.Experiment.EXPERIMENT_NAME, filters.get(DBConstants.Experiment.EXPERIMENT_NAME));
-            query += "ES.name LIKE :" + DBConstants.Experiment.EXPERIMENT_NAME + " AND ";
+            whereClause += "ES.name LIKE :" + DBConstants.Experiment.EXPERIMENT_NAME + " AND ";
         }
 
         if (filters.get(DBConstants.Experiment.DESCRIPTION) != null) {
             logger.debug("Filter Experiments by Description");
             queryParameters.put(DBConstants.Experiment.DESCRIPTION, filters.get(DBConstants.Experiment.DESCRIPTION));
-            query += "ES.description LIKE :" + DBConstants.Experiment.DESCRIPTION + " AND ";
+            whereClause += "ES.description LIKE :" + DBConstants.Experiment.DESCRIPTION + " AND ";
         }
 
         if (filters.get(DBConstants.Experiment.EXECUTION_ID) != null) {
             logger.debug("Filter Experiments by Execution ID");
             queryParameters.put(DBConstants.Experiment.EXECUTION_ID, filters.get(DBConstants.Experiment.EXECUTION_ID));
-            query += "ES.executionId LIKE :" + DBConstants.Experiment.EXECUTION_ID + " AND ";
+            whereClause += "ES.executionId LIKE :" + DBConstants.Experiment.EXECUTION_ID + " AND ";
         }
 
         if (filters.get(DBConstants.ExperimentSummary.EXPERIMENT_STATUS) != null) {
             logger.debug("Filter Experiments by State");
             String state = ExperimentState.valueOf(filters.get(DBConstants.ExperimentSummary.EXPERIMENT_STATUS)).toString();
             queryParameters.put(DBConstants.ExperimentSummary.EXPERIMENT_STATUS, state);
-            query += "ES.experimentStatus LIKE :" + DBConstants.ExperimentSummary.EXPERIMENT_STATUS + " AND ";
+            whereClause += "ES.experimentStatus LIKE :" + DBConstants.ExperimentSummary.EXPERIMENT_STATUS + " AND ";
         }
 
         if (filters.get(DBConstants.ExperimentSummary.FROM_DATE) != null
@@ -120,7 +122,8 @@
                 logger.debug("Filter Experiments by CreationTime");
                 queryParameters.put(DBConstants.ExperimentSummary.FROM_DATE, fromDate);
                 queryParameters.put(DBConstants.ExperimentSummary.TO_DATE, toDate);
-                query += "ES.creationTime BETWEEN :" + DBConstants.ExperimentSummary.FROM_DATE + " AND :" + DBConstants.ExperimentSummary.TO_DATE + " AND ";
+                whereClause += "ES.creationTime BETWEEN :" + DBConstants.ExperimentSummary.FROM_DATE + " AND :"
+                        + DBConstants.ExperimentSummary.TO_DATE + " AND ";
             }
 
         }
@@ -128,13 +131,13 @@
         if (filters.get(DBConstants.Experiment.USER_NAME) != null) {
             logger.debug("Filter Experiments by Username");
             queryParameters.put(DBConstants.Experiment.USER_NAME, filters.get(DBConstants.Experiment.USER_NAME));
-            query += "ES.userName = :" + DBConstants.Experiment.USER_NAME + " AND ";
+            whereClause += "ES.userName = :" + DBConstants.Experiment.USER_NAME + " AND ";
         }
 
         if (!accessibleExperimentIds.isEmpty()) {
             logger.debug("Filter Experiments by Accessible Experiment IDs");
             queryParameters.put(DBConstants.Experiment.ACCESSIBLE_EXPERIMENT_IDS, accessibleExperimentIds);
-            query += " ES.experimentId IN :" + DBConstants.Experiment.ACCESSIBLE_EXPERIMENT_IDS;
+            whereClause += " ES.experimentId IN :" + DBConstants.Experiment.ACCESSIBLE_EXPERIMENT_IDS;
         }
 
         else {
@@ -142,16 +145,88 @@
             return new ArrayList<ExperimentSummaryModel>();
         }
 
+        int queryLimit = limit;
+        int queryOffset = offset;
+        int accessibleExperimentIdsBatchNum = 0;
+
+        // Figure out the initial batch of accessible experiment ids and the
+        // offset into it by counting the matching experiments in each batch
+        if (queryOffset > 0) {
+            String countQuery = "SELECT COUNT(ES) FROM " + ExperimentSummaryEntity.class.getSimpleName() + " ES WHERE ";
+            countQuery += whereClause;
+            BatchOffset batchOffset = findInitialAccessibleExperimentsBatchOffset(countQuery, queryOffset, queryParameters, accessibleExperimentIds);
+            queryOffset = batchOffset.offset;
+            accessibleExperimentIdsBatchNum = batchOffset.batchNum;
+        }
+
+        query += whereClause;
         if (orderByIdentifier != null && resultOrderType != null && orderByIdentifier.equals(DBConstants.Experiment.CREATION_TIME)) {
             String order = (resultOrderType == ResultOrderType.ASC) ? "ASC" : "DESC";
             query += " ORDER BY ES." + DBConstants.Experiment.CREATION_TIME + " " + order;
         }
 
-        List<ExperimentSummaryModel> experimentSummaryModelList = select(query, limit, offset, queryParameters);
-        return experimentSummaryModelList;
+        List<ExperimentSummaryModel> allExperimentSummaryModels = new ArrayList<>();
+
+        // Break up the query in batches over accessibleExperimentIds
+        // NOTE: this assumes that the accessibleExperimentIds are sorted in the
+        // same order as the expected experiment summary results
+        double totalBatches = Math.ceil(
+                Integer.valueOf(accessibleExperimentIds.size()).floatValue() / ACCESSIBLE_EXPERIMENT_IDS_BATCH_SIZE);
+        for (int batchNum = accessibleExperimentIdsBatchNum; batchNum < totalBatches; batchNum++) {
+            List<String> accessibleExperimentIdsBatch = accessibleExperimentIds.subList(
+                    batchNum * ACCESSIBLE_EXPERIMENT_IDS_BATCH_SIZE,
+                    Math.min(accessibleExperimentIds.size(), (batchNum + 1) * ACCESSIBLE_EXPERIMENT_IDS_BATCH_SIZE));
+            queryParameters.put(DBConstants.Experiment.ACCESSIBLE_EXPERIMENT_IDS, accessibleExperimentIdsBatch);
+            List<ExperimentSummaryModel> experimentSummaryModelList = select(query, queryLimit, queryOffset, queryParameters);
+            allExperimentSummaryModels.addAll(experimentSummaryModelList);
+            if (allExperimentSummaryModels.size() == limit) {
+                return allExperimentSummaryModels;
+            } else if (limit > 0 && allExperimentSummaryModels.size() < limit) {
+                queryLimit -= experimentSummaryModelList.size();
+                // In the next and subsequent batches, start from offset 0
+                queryOffset = 0;
+            }
+        }
+        return allExperimentSummaryModels;
     }
 
-    public ExperimentStatistics getAccessibleExperimentStatistics(List<String> accessibleExperimentIds, Map<String,String> filters) throws RegistryException {
+    class BatchOffset {
+        final int batchNum;
+        final int offset;
+
+        BatchOffset(int batchNum, int offset) {
+            this.batchNum = batchNum;
+            this.offset = offset;
+        }
+    }
+
+    private BatchOffset findInitialAccessibleExperimentsBatchOffset(String query, int queryOffset,
+            Map<String, Object> queryParameters, List<String> accessibleExperimentIds) {
+        
+        int accumulator = 0;
+
+        double totalBatches = Math.ceil(
+                Integer.valueOf(accessibleExperimentIds.size()).floatValue() / ACCESSIBLE_EXPERIMENT_IDS_BATCH_SIZE);
+        for (int batchNum = 0; batchNum < totalBatches; batchNum++) {
+            List<String> accessibleExperimentIdsBatch = accessibleExperimentIds.subList(
+                    batchNum * ACCESSIBLE_EXPERIMENT_IDS_BATCH_SIZE,
+                    Math.min(accessibleExperimentIds.size(), (batchNum + 1) * ACCESSIBLE_EXPERIMENT_IDS_BATCH_SIZE));
+            queryParameters.put(DBConstants.Experiment.ACCESSIBLE_EXPERIMENT_IDS, accessibleExperimentIdsBatch);
+            int count = scalarInt(query, queryParameters);
+            if (accumulator + count > queryOffset ) {
+                return new BatchOffset(batchNum, queryOffset - accumulator);
+            } else if (accumulator + count == queryOffset) {
+                // The initial batch is the next batch since this batch ends at the queryOffset
+                return new BatchOffset(batchNum + 1, 0);
+            }
+            accumulator += count;
+        }
+        // We didn't find a batch with the offset in it, so just return a batch
+        // num past the last one
+        return new BatchOffset(Double.valueOf(totalBatches).intValue(), 0);
+    }
+
+    public ExperimentStatistics getAccessibleExperimentStatistics(List<String> accessibleExperimentIds, Map<String,String> filters, int limit, int offset) throws RegistryException {
 
         try {
 
@@ -202,44 +277,58 @@
 
             }
 
+            int allExperimentsCount = getExperimentStatisticsCountForState(null, gatewayId, fromDate, toDate,
+                    userName, applicationName, resourceHostName, accessibleExperimentIds);
             List<ExperimentSummaryModel> allExperiments = getExperimentStatisticsForState(null, gatewayId,
-                    fromDate, toDate, userName, applicationName, resourceHostName, accessibleExperimentIds);
-            experimentStatistics.setAllExperimentCount(allExperiments.size());
+                    fromDate, toDate, userName, applicationName, resourceHostName, accessibleExperimentIds, limit, offset);
+            experimentStatistics.setAllExperimentCount(allExperimentsCount);
             experimentStatistics.setAllExperiments(allExperiments);
 
-            List<ExperimentSummaryModel> createdExperiments = getExperimentStatisticsForState(ExperimentState.CREATED, gatewayId,
-                    fromDate, toDate, userName, applicationName, resourceHostName, accessibleExperimentIds);
-            createdExperiments.addAll(getExperimentStatisticsForState(ExperimentState.VALIDATED, gatewayId,
-                    fromDate, toDate, userName, applicationName, resourceHostName, accessibleExperimentIds));
-            experimentStatistics.setCreatedExperimentCount(createdExperiments.size());
+            List<ExperimentState> createdStates = Arrays.asList(ExperimentState.CREATED, ExperimentState.VALIDATED);
+            int createdExperimentsCount = getExperimentStatisticsCountForState(
+                    createdStates, gatewayId, fromDate, toDate,
+                    userName, applicationName, resourceHostName, accessibleExperimentIds);
+            List<ExperimentSummaryModel> createdExperiments = getExperimentStatisticsForState(createdStates, gatewayId,
+                    fromDate, toDate, userName, applicationName, resourceHostName, accessibleExperimentIds, limit, offset);
+            experimentStatistics.setCreatedExperimentCount(createdExperimentsCount);
             experimentStatistics.setCreatedExperiments(createdExperiments);
 
-            List<ExperimentSummaryModel> runningExperiments = getExperimentStatisticsForState(ExperimentState.EXECUTING, gatewayId,
+            List<ExperimentState> runningStates = Arrays.asList(ExperimentState.EXECUTING, ExperimentState.SCHEDULED, ExperimentState.LAUNCHED);
+            int runningExperimentsCount = getExperimentStatisticsCountForState(
+                    runningStates, gatewayId,
                     fromDate, toDate, userName, applicationName, resourceHostName, accessibleExperimentIds);
-            runningExperiments.addAll(getExperimentStatisticsForState(ExperimentState.SCHEDULED, gatewayId, fromDate,
-                    toDate, userName, applicationName, resourceHostName, accessibleExperimentIds));
-            runningExperiments.addAll(getExperimentStatisticsForState(ExperimentState.LAUNCHED, gatewayId, fromDate,
-                    toDate, userName, applicationName, resourceHostName, accessibleExperimentIds));
-            experimentStatistics.setRunningExperimentCount(runningExperiments.size());
+            List<ExperimentSummaryModel> runningExperiments = getExperimentStatisticsForState(runningStates, gatewayId,
+                    fromDate, toDate, userName, applicationName, resourceHostName, accessibleExperimentIds, limit, offset);
+            experimentStatistics.setRunningExperimentCount(runningExperimentsCount);
             experimentStatistics.setRunningExperiments(runningExperiments);
 
+            List<ExperimentState> completedStates = Arrays.asList(ExperimentState.COMPLETED);
+            int completedExperimentsCount = getExperimentStatisticsCountForState(
+                    completedStates, gatewayId,
+                    fromDate, toDate, userName, applicationName, resourceHostName, accessibleExperimentIds);
             List<ExperimentSummaryModel> completedExperiments = getExperimentStatisticsForState(
-                    ExperimentState.COMPLETED, gatewayId, fromDate, toDate, userName, applicationName, resourceHostName,
-                    accessibleExperimentIds);
-            experimentStatistics.setCompletedExperimentCount(completedExperiments.size());
+                    completedStates, gatewayId, fromDate, toDate, userName, applicationName, resourceHostName,
+                    accessibleExperimentIds, limit, offset);
+            experimentStatistics.setCompletedExperimentCount(completedExperimentsCount);
             experimentStatistics.setCompletedExperiments(completedExperiments);
 
-            List<ExperimentSummaryModel> failedExperiments = getExperimentStatisticsForState(ExperimentState.FAILED,
-                    gatewayId, fromDate, toDate, userName, applicationName, resourceHostName, accessibleExperimentIds);
-            experimentStatistics.setFailedExperimentCount(failedExperiments.size());
+            List<ExperimentState> failedStates = Arrays.asList(ExperimentState.FAILED);
+            int failedExperimentsCount = getExperimentStatisticsCountForState(
+                    failedStates, gatewayId,
+                    fromDate, toDate, userName, applicationName, resourceHostName, accessibleExperimentIds);
+            List<ExperimentSummaryModel> failedExperiments = getExperimentStatisticsForState(failedStates,
+                    gatewayId, fromDate, toDate, userName, applicationName, resourceHostName, accessibleExperimentIds, limit, offset);
+            experimentStatistics.setFailedExperimentCount(failedExperimentsCount);
             experimentStatistics.setFailedExperiments(failedExperiments);
 
+            List<ExperimentState> cancelledStates = Arrays.asList(ExperimentState.CANCELED, ExperimentState.CANCELING);
+            int cancelledExperimentsCount = getExperimentStatisticsCountForState(
+                    cancelledStates, gatewayId,
+                    fromDate, toDate, userName, applicationName, resourceHostName, accessibleExperimentIds);
             List<ExperimentSummaryModel> cancelledExperiments = getExperimentStatisticsForState(
-                    ExperimentState.CANCELED, gatewayId, fromDate, toDate, userName, applicationName, resourceHostName,
-                    accessibleExperimentIds);
-            cancelledExperiments.addAll(getExperimentStatisticsForState(ExperimentState.CANCELING, gatewayId, fromDate,
-                    toDate, userName, applicationName, resourceHostName, accessibleExperimentIds));
-            experimentStatistics.setCancelledExperimentCount(cancelledExperiments.size());
+                    cancelledStates, gatewayId, fromDate, toDate, userName, applicationName, resourceHostName,
+                    accessibleExperimentIds, limit, offset);
+            experimentStatistics.setCancelledExperimentCount(cancelledExperimentsCount);
             experimentStatistics.setCancelledExperiments(cancelledExperiments);
 
             return experimentStatistics;
@@ -252,22 +341,62 @@
 
     }
 
-    protected List<ExperimentSummaryModel> getExperimentStatisticsForState(ExperimentState experimentState, String gatewayId, Timestamp fromDate, Timestamp toDate,
+    protected int getExperimentStatisticsCountForState(List<ExperimentState> experimentStates, String gatewayId, Timestamp fromDate, Timestamp toDate,
                                                                            String userName, String applicationName, String resourceHostName, List<String> experimentIds) throws RegistryException, IllegalArgumentException {
+        String query = "SELECT count(ES.experimentId) FROM " + ExperimentSummaryEntity.class.getSimpleName() + " ES WHERE ";
+        Map<String, Object> queryParameters = new HashMap<>();
+
+        String finalQuery = filterExperimentStatisticsQuery(query, queryParameters, experimentStates, gatewayId, 
+            fromDate, toDate, userName, applicationName, resourceHostName, experimentIds);
+        
+        if (finalQuery == null) {
+            return 0;
+        }
+
+        long count = (long) execute(entityManager -> {
+            Query jpaQuery = entityManager.createQuery(finalQuery);
+            for (Map.Entry<String, Object> entry : queryParameters.entrySet()) {
+
+                jpaQuery.setParameter(entry.getKey(), entry.getValue());
+            }
+            return jpaQuery.getSingleResult();
+        });
+        return Long.valueOf(count).intValue();
+    }
+
+    protected List<ExperimentSummaryModel> getExperimentStatisticsForState(List<ExperimentState> experimentStates, String gatewayId, Timestamp fromDate, Timestamp toDate,
+                                                                           String userName, String applicationName, String resourceHostName, List<String> experimentIds, int limit, int offset) throws RegistryException, IllegalArgumentException {
 
         String query = "SELECT ES FROM " + ExperimentSummaryEntity.class.getSimpleName() + " ES WHERE ";
         Map<String, Object> queryParameters = new HashMap<>();
 
-        if (experimentState != null) {
-            logger.debug("Filter Experiments by Experiment State");
-            queryParameters.put(DBConstants.ExperimentSummary.EXPERIMENT_STATUS, experimentState);
-            query += "ES.experimentStatus LIKE :" + DBConstants.ExperimentSummary.EXPERIMENT_STATUS + " AND ";
+        query = filterExperimentStatisticsQuery(query, queryParameters, experimentStates, gatewayId, 
+            fromDate, toDate, userName, applicationName, resourceHostName, experimentIds);
+
+        if (query == null) {
+            return new ArrayList<ExperimentSummaryModel>();
+        }
+
+        query += "ORDER BY ES.creationTime DESC, ES.experimentId"; // experimentId is the ordering tiebreaker
+        List<ExperimentSummaryModel> experimentSummaryModelList = select(query, limit, offset, queryParameters);
+        return experimentSummaryModelList;
+    }
+
+    protected String filterExperimentStatisticsQuery(String query, Map<String, Object> queryParameters, 
+            List<ExperimentState> experimentStates, String gatewayId, Timestamp fromDate, 
+            Timestamp toDate, String userName, String applicationName, String resourceHostName, List<String> experimentIds) {
+
+        if (experimentStates != null) {
+            logger.debug("Filter Experiments by Experiment States");
+            List<String> statesAsStrings = experimentStates.stream().map(s -> s.toString()).collect(Collectors.toList());
+            queryParameters.put(DBConstants.ExperimentSummary.EXPERIMENT_STATUS, statesAsStrings);
+            query += "ES.experimentStatus IN :" + DBConstants.ExperimentSummary.EXPERIMENT_STATUS + " AND ";
         }
 
         if (gatewayId != null) {
             logger.debug("Filter Experiments by GatewayId");
             queryParameters.put(DBConstants.Experiment.GATEWAY_ID, gatewayId);
-            query += "ES.gatewayId LIKE :" + DBConstants.Experiment.GATEWAY_ID + " AND ";
+            query += "ES.gatewayId = :" + DBConstants.Experiment.GATEWAY_ID + " AND ";
         }
 
         if (fromDate != null && toDate != null) {
@@ -283,13 +412,13 @@
         if (userName != null) {
             logger.debug("Filter Experiments by UserName");
             queryParameters.put(DBConstants.Experiment.USER_NAME, userName);
-            query += "ES.userName LIKE :" + DBConstants.Experiment.USER_NAME + " AND ";
+            query += "ES.userName = :" + DBConstants.Experiment.USER_NAME + " AND ";
         }
 
         if (applicationName != null) {
             logger.debug("Filter Experiments by ApplicationName");
             queryParameters.put(DBConstants.Experiment.EXECUTION_ID, applicationName);
-            query += "ES.executionId LIKE :" + DBConstants.Experiment.EXECUTION_ID + " AND ";
+            query += "ES.executionId = :" + DBConstants.Experiment.EXECUTION_ID + " AND ";
         }
 
         if (experimentIds != null) {
@@ -298,14 +427,14 @@
                 queryParameters.put(DBConstants.Experiment.EXPERIMENT_ID, experimentIds);
                 query += "ES.experimentId IN :" + DBConstants.Experiment.EXPERIMENT_ID + " AND ";
             } else {
-                return new ArrayList<ExperimentSummaryModel>();
+                return null;
             }
         }
 
         if (resourceHostName != null) {
             logger.debug("Filter Experiments by ResourceHostName");
             queryParameters.put(DBConstants.Experiment.RESOURCE_HOST_ID, resourceHostName);
-            query += "ES.resourceHostId LIKE :" + DBConstants.Experiment.RESOURCE_HOST_ID + " ";
+            query += "ES.resourceHostId = :" + DBConstants.Experiment.RESOURCE_HOST_ID + " ";
         }
 
         else {
@@ -313,9 +442,7 @@
             query = query.substring(0, query.length() - 4);
         }
 
-        query += "ORDER BY ES.creationTime DESC";
-        List<ExperimentSummaryModel> experimentSummaryModelList = select(query, Integer.MAX_VALUE, 0, queryParameters);
-        return experimentSummaryModelList;
+        return query;
     }
 
 }
diff --git a/modules/registry/registry-core/src/test/java/org/apache/airavata/registry/core/repositories/expcatalog/ExperimentSummaryRepositoryTest.java b/modules/registry/registry-core/src/test/java/org/apache/airavata/registry/core/repositories/expcatalog/ExperimentSummaryRepositoryTest.java
index 1b0c415..ea068dd 100644
--- a/modules/registry/registry-core/src/test/java/org/apache/airavata/registry/core/repositories/expcatalog/ExperimentSummaryRepositoryTest.java
+++ b/modules/registry/registry-core/src/test/java/org/apache/airavata/registry/core/repositories/expcatalog/ExperimentSummaryRepositoryTest.java
@@ -93,6 +93,15 @@
         experimentModelTwo.setDescription("descriptionTwo");
         experimentModelTwo.setExecutionId("executionIdTwo");
 
+        ExperimentModel experimentModelThree = new ExperimentModel();
+        experimentModelThree.setProjectId(projectId);
+        experimentModelThree.setGatewayId(gatewayId);
+        experimentModelThree.setExperimentType(ExperimentType.SINGLE_APPLICATION);
+        experimentModelThree.setUserName("userThree");
+        experimentModelThree.setExperimentName("nameThree");
+        experimentModelThree.setDescription("descriptionThree");
+        experimentModelThree.setExecutionId("executionIdThree");
+
         String experimentIdOne = experimentRepository.addExperiment(experimentModelOne);
         assertTrue(experimentIdOne != null);
         // Reload experiment to get its status' identifier
@@ -103,6 +112,11 @@
         // Reload experiment to get its status' identifier
         experimentModelTwo = experimentRepository.getExperiment(experimentIdTwo);
 
+        String experimentIdThree = experimentRepository.addExperiment(experimentModelThree);
+        assertTrue(experimentIdThree != null);
+        // Reload experiment to get its status' identifier
+        experimentModelThree = experimentRepository.getExperiment(experimentIdThree);
+
         Timestamp timeOne = Timestamp.valueOf("2010-01-01 09:00:00");
         experimentModelOne.setCreationTime(timeOne.getTime());
         experimentRepository.updateExperiment(experimentModelOne, experimentIdOne);
@@ -111,14 +125,18 @@
         experimentModelTwo.setCreationTime(timeTwo.getTime());
         experimentRepository.updateExperiment(experimentModelTwo, experimentIdTwo);
 
+        Timestamp timeThree = Timestamp.valueOf("2020-01-01 09:00:00");
+        experimentModelThree.setCreationTime(timeThree.getTime());
+        experimentRepository.updateExperiment(experimentModelThree, experimentIdThree);
+
         Map<String, String> filters = new HashMap<>();
         filters.put(DBConstants.Experiment.GATEWAY_ID, gatewayId);
         filters.put(DBConstants.Experiment.PROJECT_ID, projectId);
 
-        List<String> allExperimentIds = Arrays.asList( experimentIdOne, experimentIdTwo);
+        List<String> allExperimentIds = Arrays.asList( experimentIdOne, experimentIdTwo, experimentIdThree);
         List<ExperimentSummaryModel> experimentSummaryModelList = experimentSummaryRepository.
                 searchAllAccessibleExperiments(allExperimentIds, filters, -1, 0, null, null);
-        assertEquals(2, experimentSummaryModelList.size());
+        assertEquals(3, experimentSummaryModelList.size());
 
         filters.put(DBConstants.Experiment.EXECUTION_ID, "executionIdTwo");
 
@@ -166,6 +184,32 @@
         assertEquals("should return only userOne's exp", 1, experimentSummaryModelList.size());
         assertEquals("userOne", experimentSummaryModelList.get(0).getUserName());
 
+        // Test with pagination
+        filters.clear();
+        filters.put(DBConstants.Experiment.GATEWAY_ID, gatewayId);
+        experimentSummaryModelList = experimentSummaryRepository.searchAllAccessibleExperiments(
+                                allExperimentIds, filters, 2, 0,
+                                DBConstants.Experiment.CREATION_TIME, ResultOrderType.ASC);
+        assertEquals("should only return 2 experiments since limit=2", 2, experimentSummaryModelList.size());
+        assertEquals(experimentIdOne, experimentSummaryModelList.get(0).getExperimentId());
+        assertEquals(experimentIdTwo, experimentSummaryModelList.get(1).getExperimentId());
+        // page 2
+        experimentSummaryModelList = experimentSummaryRepository.searchAllAccessibleExperiments(
+                                allExperimentIds, filters, 2, 2,
+                                DBConstants.Experiment.CREATION_TIME, ResultOrderType.ASC);
+        assertEquals("should only return 1 experiment since limit=2 but partial last page", 1, experimentSummaryModelList.size());
+        assertEquals(experimentIdThree, experimentSummaryModelList.get(0).getExperimentId());
+        // Test with offset at the end (should return empty list)
+        experimentSummaryModelList = experimentSummaryRepository.searchAllAccessibleExperiments(
+                                allExperimentIds, filters, 3, 3,
+                                DBConstants.Experiment.CREATION_TIME, ResultOrderType.ASC);
+        assertEquals("should return 0 since we're just past the last page (page size of 3)", 0, experimentSummaryModelList.size());
+        // Test with offset past the end (should return empty list)
+        experimentSummaryModelList = experimentSummaryRepository.searchAllAccessibleExperiments(
+                                allExperimentIds, filters, 3, 10,
+                                DBConstants.Experiment.CREATION_TIME, ResultOrderType.ASC);
+        assertEquals("should return 0 since we're well past the last page (page size of 3)", 0, experimentSummaryModelList.size());
+
         filters = new HashMap<>();
         filters.put(DBConstants.Experiment.GATEWAY_ID, gatewayId);
         filters.put(DBConstants.Experiment.USER_NAME, "userTwo");
@@ -174,12 +218,12 @@
         filters.put(DBConstants.ExperimentSummary.FROM_DATE, fromDate);
         filters.put(DBConstants.ExperimentSummary.TO_DATE, toDate);
 
-        ExperimentStatistics experimentStatistics = experimentSummaryRepository.getAccessibleExperimentStatistics(allExperimentIds, filters);
+        ExperimentStatistics experimentStatistics = experimentSummaryRepository.getAccessibleExperimentStatistics(allExperimentIds, filters, 10, 0);
         assertTrue(experimentStatistics.getAllExperimentCount() == 0);
 
         filters.remove(DBConstants.Experiment.RESOURCE_HOST_ID);
 
-        experimentStatistics = experimentSummaryRepository.getAccessibleExperimentStatistics(allExperimentIds, filters);
+        experimentStatistics = experimentSummaryRepository.getAccessibleExperimentStatistics(allExperimentIds, filters, 10, 0);
         assertTrue(experimentStatistics.getAllExperimentCount() == 1);
         assertEquals(experimentStatistics.getAllExperiments().get(0).getExperimentId(), experimentIdTwo);
 
@@ -194,16 +238,21 @@
         String statusIdTwo = experimentStatusRepository.addExperimentStatus(experimentStatusTwo, experimentIdTwo);
         assertTrue(statusIdTwo != null);
 
-        experimentStatistics = experimentSummaryRepository.getAccessibleExperimentStatistics(allExperimentIds, filters);
-        assertTrue(experimentStatistics.getAllExperimentCount() == 1);
+        ExperimentStatus experimentStatusThree = new ExperimentStatus(ExperimentState.CANCELED);
+        String statusIdThree = experimentStatusRepository.addExperimentStatus(experimentStatusThree, experimentIdThree);
+        assertTrue(statusIdThree != null);
+
+        experimentStatistics = experimentSummaryRepository.getAccessibleExperimentStatistics(allExperimentIds, filters, 10, 0);
+        assertEquals(2, experimentStatistics.getAllExperimentCount());
         assertTrue(experimentStatistics.getRunningExperimentCount() == 1);
-        assertEquals(experimentIdTwo, experimentStatistics.getAllExperiments().get(0).getExperimentId());
+        // Experiment 3 is most recent
+        assertEquals(experimentIdThree, experimentStatistics.getAllExperiments().get(0).getExperimentId());
 
         filters.remove(DBConstants.ExperimentSummary.FROM_DATE);
         filters.remove(DBConstants.ExperimentSummary.TO_DATE);
 
-        experimentStatistics = experimentSummaryRepository.getAccessibleExperimentStatistics(allExperimentIds, filters);
-        assertTrue(experimentStatistics.getAllExperimentCount() == 2);
+        experimentStatistics = experimentSummaryRepository.getAccessibleExperimentStatistics(allExperimentIds, filters, 10, 0);
+        assertTrue(experimentStatistics.getAllExperimentCount() == 3);
         assertTrue(experimentStatistics.getCreatedExperimentCount() == 1);
         assertTrue(experimentStatistics.getRunningExperimentCount() == 1);
 
@@ -226,13 +275,32 @@
         assertEquals(experimentIdTwo, experimentSummaryModelList.get(0).getExperimentId());
 
         // Experiment 2 is EXECUTING and should be the only one returned
-        experimentStatistics = experimentSummaryRepository.getAccessibleExperimentStatistics(Collections.singletonList(experimentIdTwo), filters);
+        experimentStatistics = experimentSummaryRepository.getAccessibleExperimentStatistics(Collections.singletonList(experimentIdTwo), filters, 10, 0);
         assertTrue(experimentStatistics.getAllExperimentCount() == 1);
         assertTrue(experimentStatistics.getCreatedExperimentCount() == 0);
         assertTrue(experimentStatistics.getRunningExperimentCount() == 1);
 
+        // Check pagination
+        filters = new HashMap<>();
+        filters.put(DBConstants.Experiment.GATEWAY_ID, gatewayId);
+        // First page
+        experimentStatistics = experimentSummaryRepository.getAccessibleExperimentStatistics(allExperimentIds, filters, 1, 0);
+        // Should still return total count even when only returning the first page of experiment summaries
+        assertEquals(3, experimentStatistics.getAllExperimentCount());
+        // experiment 3 is most recent
+        assertEquals(1, experimentStatistics.getAllExperimentsSize());
+        assertEquals(experimentIdThree, experimentStatistics.getAllExperiments().get(0).getExperimentId());
+        // Second page
+        experimentStatistics = experimentSummaryRepository.getAccessibleExperimentStatistics(allExperimentIds, filters, 1, 1);
+        // Should still return total count even when only returning the first page of experiment summaries
+        assertEquals(3, experimentStatistics.getAllExperimentCount());
+        // experiment 2 is less recent
+        assertEquals(1, experimentStatistics.getAllExperimentsSize());
+        assertEquals(experimentIdTwo, experimentStatistics.getAllExperiments().get(0).getExperimentId());
+
         experimentRepository.removeExperiment(experimentIdOne);
         experimentRepository.removeExperiment(experimentIdTwo);
+        experimentRepository.removeExperiment(experimentIdThree);
 
         gatewayRepository.removeGateway(gatewayId);
         projectRepository.removeProject(projectId);
diff --git a/modules/registry/registry-server/registry-api-service/src/main/java/org/apache/airavata/registry/api/service/handler/RegistryServerHandler.java b/modules/registry/registry-server/registry-api-service/src/main/java/org/apache/airavata/registry/api/service/handler/RegistryServerHandler.java
index 3ff767d..779b5ed 100644
--- a/modules/registry/registry-server/registry-api-service/src/main/java/org/apache/airavata/registry/api/service/handler/RegistryServerHandler.java
+++ b/modules/registry/registry-server/registry-api-service/src/main/java/org/apache/airavata/registry/api/service/handler/RegistryServerHandler.java
@@ -436,7 +436,7 @@
      * @param toTime    Ending data time.
      */
     @Override
-    public ExperimentStatistics getExperimentStatistics(String gatewayId, long fromTime, long toTime, String userName, String applicationName, String resourceHostName, List<String> accessibleExpIds) throws RegistryServiceException, TException {
+    public ExperimentStatistics getExperimentStatistics(String gatewayId, long fromTime, long toTime, String userName, String applicationName, String resourceHostName, List<String> accessibleExpIds, int limit, int offset) throws RegistryServiceException, TException {
         if (!isGatewayExistInternal(gatewayId)){
             logger.error("Gateway does not exist.Please provide a valid gateway id...");
             throw new AiravataSystemException(AiravataErrorType.INTERNAL_ERROR);
@@ -461,7 +461,10 @@
                 filters.put(Constants.FieldConstants.ExperimentConstants.RESOURCE_HOST_ID, resourceHostName);
             }
 
-            ExperimentStatistics result = experimentSummaryRepository.getAccessibleExperimentStatistics(accessibleExpIds, filters);
+            // Cap the max returned experiment summaries at 1000
+            limit = Math.min(limit, 1000);
+
+            ExperimentStatistics result = experimentSummaryRepository.getAccessibleExperimentStatistics(accessibleExpIds, filters, limit, offset);
             logger.debug("Airavata retrieved experiments for gateway id : " + gatewayId + " between : " + AiravataUtils.getTime(fromTime) + " and " + AiravataUtils.getTime(toTime));
             return result;
         }catch (Exception e) {
@@ -3645,7 +3648,8 @@
                 switch (experimentState){
                     case CREATED: case VALIDATED:
                         if(experiment.getUserConfigurationData() != null && experiment.getUserConfigurationData()
-                                .getComputationalResourceScheduling() != null){
+                                .getComputationalResourceScheduling() != null 
+                                && experiment.getUserConfigurationData().getComputationalResourceScheduling().getResourceHostId() != null){
                             String compResourceId = experiment.getUserConfigurationData()
                                     .getComputationalResourceScheduling().getResourceHostId();
                             ComputeResourceDescription computeResourceDescription = new ComputeResourceRepository()
@@ -3747,7 +3751,8 @@
             }
 
             if(experiment.getUserConfigurationData() != null && experiment.getUserConfigurationData()
-                    .getComputationalResourceScheduling() != null){
+                    .getComputationalResourceScheduling() != null 
+                    && experiment.getUserConfigurationData().getComputationalResourceScheduling().getResourceHostId() != null){
 
                 String compResourceId = experiment.getUserConfigurationData()
                         .getComputationalResourceScheduling().getResourceHostId();
diff --git a/modules/registry/registry-server/registry-api-stubs/src/main/java/org/apache/airavata/registry/api/RegistryService.java b/modules/registry/registry-server/registry-api-stubs/src/main/java/org/apache/airavata/registry/api/RegistryService.java
index 67b0c32..2b89543 100644
--- a/modules/registry/registry-server/registry-api-stubs/src/main/java/org/apache/airavata/registry/api/RegistryService.java
+++ b/modules/registry/registry-server/registry-api-stubs/src/main/java/org/apache/airavata/registry/api/RegistryService.java
@@ -351,6 +351,13 @@
      * @param accessibleExpIds
      *    Experiment IDs which are accessible to the current user.
      * 
+     * @param limit
+     *       Amount of results to be fetched.
+     * 
+     * @param offset
+     *       The starting point of the results to be fetched.
+     * 
+     * 
      * 
      * @param gatewayId
      * @param fromTime
@@ -359,8 +366,10 @@
      * @param applicationName
      * @param resourceHostName
      * @param accessibleExpIds
+     * @param limit
+     * @param offset
      */
-    public org.apache.airavata.model.experiment.ExperimentStatistics getExperimentStatistics(java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, java.util.List<java.lang.String> accessibleExpIds) throws org.apache.airavata.registry.api.exception.RegistryServiceException, org.apache.thrift.TException;
+    public org.apache.airavata.model.experiment.ExperimentStatistics getExperimentStatistics(java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, java.util.List<java.lang.String> accessibleExpIds, int limit, int offset) throws org.apache.airavata.registry.api.exception.RegistryServiceException, org.apache.thrift.TException;
 
     /**
      * 
@@ -2662,7 +2671,7 @@
 
     public void searchExperiments(java.lang.String gatewayId, java.lang.String userName, java.util.List<java.lang.String> accessibleExpIds, java.util.Map<org.apache.airavata.model.experiment.ExperimentSearchFields,java.lang.String> filters, int limit, int offset, org.apache.thrift.async.AsyncMethodCallback<java.util.List<org.apache.airavata.model.experiment.ExperimentSummaryModel>> resultHandler) throws org.apache.thrift.TException;
 
-    public void getExperimentStatistics(java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, java.util.List<java.lang.String> accessibleExpIds, org.apache.thrift.async.AsyncMethodCallback<org.apache.airavata.model.experiment.ExperimentStatistics> resultHandler) throws org.apache.thrift.TException;
+    public void getExperimentStatistics(java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, java.util.List<java.lang.String> accessibleExpIds, int limit, int offset, org.apache.thrift.async.AsyncMethodCallback<org.apache.airavata.model.experiment.ExperimentStatistics> resultHandler) throws org.apache.thrift.TException;
 
     public void getExperimentsInProject(java.lang.String gatewayId, java.lang.String projectId, int limit, int offset, org.apache.thrift.async.AsyncMethodCallback<java.util.List<org.apache.airavata.model.experiment.ExperimentModel>> resultHandler) throws org.apache.thrift.TException;
 
@@ -3591,13 +3600,13 @@
       throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT, "searchExperiments failed: unknown result");
     }
 
-    public org.apache.airavata.model.experiment.ExperimentStatistics getExperimentStatistics(java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, java.util.List<java.lang.String> accessibleExpIds) throws org.apache.airavata.registry.api.exception.RegistryServiceException, org.apache.thrift.TException
+    public org.apache.airavata.model.experiment.ExperimentStatistics getExperimentStatistics(java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, java.util.List<java.lang.String> accessibleExpIds, int limit, int offset) throws org.apache.airavata.registry.api.exception.RegistryServiceException, org.apache.thrift.TException
     {
-      send_getExperimentStatistics(gatewayId, fromTime, toTime, userName, applicationName, resourceHostName, accessibleExpIds);
+      send_getExperimentStatistics(gatewayId, fromTime, toTime, userName, applicationName, resourceHostName, accessibleExpIds, limit, offset);
       return recv_getExperimentStatistics();
     }
 
-    public void send_getExperimentStatistics(java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, java.util.List<java.lang.String> accessibleExpIds) throws org.apache.thrift.TException
+    public void send_getExperimentStatistics(java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, java.util.List<java.lang.String> accessibleExpIds, int limit, int offset) throws org.apache.thrift.TException
     {
       getExperimentStatistics_args args = new getExperimentStatistics_args();
       args.setGatewayId(gatewayId);
@@ -3607,6 +3616,8 @@
       args.setApplicationName(applicationName);
       args.setResourceHostName(resourceHostName);
       args.setAccessibleExpIds(accessibleExpIds);
+      args.setLimit(limit);
+      args.setOffset(offset);
       sendBase("getExperimentStatistics", args);
     }
 
@@ -9090,9 +9101,9 @@
       }
     }
 
-    public void getExperimentStatistics(java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, java.util.List<java.lang.String> accessibleExpIds, org.apache.thrift.async.AsyncMethodCallback<org.apache.airavata.model.experiment.ExperimentStatistics> resultHandler) throws org.apache.thrift.TException {
+    public void getExperimentStatistics(java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, java.util.List<java.lang.String> accessibleExpIds, int limit, int offset, org.apache.thrift.async.AsyncMethodCallback<org.apache.airavata.model.experiment.ExperimentStatistics> resultHandler) throws org.apache.thrift.TException {
       checkReady();
-      getExperimentStatistics_call method_call = new getExperimentStatistics_call(gatewayId, fromTime, toTime, userName, applicationName, resourceHostName, accessibleExpIds, resultHandler, this, ___protocolFactory, ___transport);
+      getExperimentStatistics_call method_call = new getExperimentStatistics_call(gatewayId, fromTime, toTime, userName, applicationName, resourceHostName, accessibleExpIds, limit, offset, resultHandler, this, ___protocolFactory, ___transport);
       this.___currentMethod = method_call;
       ___manager.call(method_call);
     }
@@ -9105,7 +9116,9 @@
       private java.lang.String applicationName;
       private java.lang.String resourceHostName;
       private java.util.List<java.lang.String> accessibleExpIds;
-      public getExperimentStatistics_call(java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, java.util.List<java.lang.String> accessibleExpIds, org.apache.thrift.async.AsyncMethodCallback<org.apache.airavata.model.experiment.ExperimentStatistics> resultHandler, org.apache.thrift.async.TAsyncClient client, org.apache.thrift.protocol.TProtocolFactory protocolFactory, org.apache.thrift.transport.TNonblockingTransport transport) throws org.apache.thrift.TException {
+      private int limit;
+      private int offset;
+      public getExperimentStatistics_call(java.lang.String gatewayId, long fromTime, long toTime, java.lang.String userName, java.lang.String applicationName, java.lang.String resourceHostName, java.util.List<java.lang.String> accessibleExpIds, int limit, int offset, org.apache.thrift.async.AsyncMethodCallback<org.apache.airavata.model.experiment.ExperimentStatistics> resultHandler, org.apache.thrift.async.TAsyncClient client, org.apache.thrift.protocol.TProtocolFactory protocolFactory, org.apache.thrift.transport.TNonblockingTransport transport) throws org.apache.thrift.TException {
         super(client, protocolFactory, transport, resultHandler, false);
         this.gatewayId = gatewayId;
         this.fromTime = fromTime;
@@ -9114,6 +9127,8 @@
         this.applicationName = applicationName;
         this.resourceHostName = resourceHostName;
         this.accessibleExpIds = accessibleExpIds;
+        this.limit = limit;
+        this.offset = offset;
       }
 
       public void write_args(org.apache.thrift.protocol.TProtocol prot) throws org.apache.thrift.TException {
@@ -9126,6 +9141,8 @@
         args.setApplicationName(applicationName);
         args.setResourceHostName(resourceHostName);
         args.setAccessibleExpIds(accessibleExpIds);
+        args.setLimit(limit);
+        args.setOffset(offset);
         args.write(prot);
         prot.writeMessageEnd();
       }
@@ -15988,7 +16005,7 @@
       public getExperimentStatistics_result getResult(I iface, getExperimentStatistics_args args) throws org.apache.thrift.TException {
         getExperimentStatistics_result result = new getExperimentStatistics_result();
         try {
-          result.success = iface.getExperimentStatistics(args.gatewayId, args.fromTime, args.toTime, args.userName, args.applicationName, args.resourceHostName, args.accessibleExpIds);
+          result.success = iface.getExperimentStatistics(args.gatewayId, args.fromTime, args.toTime, args.userName, args.applicationName, args.resourceHostName, args.accessibleExpIds, args.limit, args.offset);
         } catch (org.apache.airavata.registry.api.exception.RegistryServiceException rse) {
           result.rse = rse;
         }
@@ -21965,7 +21982,7 @@
       }
 
       public void start(I iface, getExperimentStatistics_args args, org.apache.thrift.async.AsyncMethodCallback<org.apache.airavata.model.experiment.ExperimentStatistics> resultHandler) throws org.apache.thrift.TException {
-        iface.getExperimentStatistics(args.gatewayId, args.fromTime, args.toTime, args.userName, args.applicationName, args.resourceHostName, args.accessibleExpIds,resultHandler);
+        iface.getExperimentStatistics(args.gatewayId, args.fromTime, args.toTime, args.userName, args.applicationName, args.resourceHostName, args.accessibleExpIds, args.limit, args.offset,resultHandler);
       }
     }
 
@@ -52824,6 +52841,8 @@
     private static final org.apache.thrift.protocol.TField APPLICATION_NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("applicationName", org.apache.thrift.protocol.TType.STRING, (short)5);
     private static final org.apache.thrift.protocol.TField RESOURCE_HOST_NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("resourceHostName", org.apache.thrift.protocol.TType.STRING, (short)6);
     private static final org.apache.thrift.protocol.TField ACCESSIBLE_EXP_IDS_FIELD_DESC = new org.apache.thrift.protocol.TField("accessibleExpIds", org.apache.thrift.protocol.TType.LIST, (short)7);
+    private static final org.apache.thrift.protocol.TField LIMIT_FIELD_DESC = new org.apache.thrift.protocol.TField("limit", org.apache.thrift.protocol.TType.I32, (short)8);
+    private static final org.apache.thrift.protocol.TField OFFSET_FIELD_DESC = new org.apache.thrift.protocol.TField("offset", org.apache.thrift.protocol.TType.I32, (short)9);
 
     private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new getExperimentStatistics_argsStandardSchemeFactory();
     private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new getExperimentStatistics_argsTupleSchemeFactory();
@@ -52835,6 +52854,8 @@
     public java.lang.String applicationName; // required
     public java.lang.String resourceHostName; // required
     public java.util.List<java.lang.String> accessibleExpIds; // required
+    public int limit; // required
+    public int offset; // required
 
     /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
     public enum _Fields implements org.apache.thrift.TFieldIdEnum {
@@ -52844,7 +52865,9 @@
       USER_NAME((short)4, "userName"),
       APPLICATION_NAME((short)5, "applicationName"),
       RESOURCE_HOST_NAME((short)6, "resourceHostName"),
-      ACCESSIBLE_EXP_IDS((short)7, "accessibleExpIds");
+      ACCESSIBLE_EXP_IDS((short)7, "accessibleExpIds"),
+      LIMIT((short)8, "limit"),
+      OFFSET((short)9, "offset");
 
       private static final java.util.Map<java.lang.String, _Fields> byName = new java.util.HashMap<java.lang.String, _Fields>();
 
@@ -52873,6 +52896,10 @@
             return RESOURCE_HOST_NAME;
           case 7: // ACCESSIBLE_EXP_IDS
             return ACCESSIBLE_EXP_IDS;
+          case 8: // LIMIT
+            return LIMIT;
+          case 9: // OFFSET
+            return OFFSET;
           default:
             return null;
         }
@@ -52915,6 +52942,8 @@
     // isset id assignments
     private static final int __FROMTIME_ISSET_ID = 0;
     private static final int __TOTIME_ISSET_ID = 1;
+    private static final int __LIMIT_ISSET_ID = 2;
+    private static final int __OFFSET_ISSET_ID = 3;
     private byte __isset_bitfield = 0;
     public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
     static {
@@ -52934,11 +52963,19 @@
       tmpMap.put(_Fields.ACCESSIBLE_EXP_IDS, new org.apache.thrift.meta_data.FieldMetaData("accessibleExpIds", org.apache.thrift.TFieldRequirementType.DEFAULT, 
           new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, 
               new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING))));
+      tmpMap.put(_Fields.LIMIT, new org.apache.thrift.meta_data.FieldMetaData("limit", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+          new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32)));
+      tmpMap.put(_Fields.OFFSET, new org.apache.thrift.meta_data.FieldMetaData("offset", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+          new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32)));
       metaDataMap = java.util.Collections.unmodifiableMap(tmpMap);
       org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(getExperimentStatistics_args.class, metaDataMap);
     }
 
     public getExperimentStatistics_args() {
+      this.limit = 50;
+
+      this.offset = 0;
+
     }
 
     public getExperimentStatistics_args(
@@ -52948,7 +52985,9 @@
       java.lang.String userName,
       java.lang.String applicationName,
       java.lang.String resourceHostName,
-      java.util.List<java.lang.String> accessibleExpIds)
+      java.util.List<java.lang.String> accessibleExpIds,
+      int limit,
+      int offset)
     {
       this();
       this.gatewayId = gatewayId;
@@ -52960,6 +52999,10 @@
       this.applicationName = applicationName;
       this.resourceHostName = resourceHostName;
       this.accessibleExpIds = accessibleExpIds;
+      this.limit = limit;
+      setLimitIsSet(true);
+      this.offset = offset;
+      setOffsetIsSet(true);
     }
 
     /**
@@ -52985,6 +53028,8 @@
         java.util.List<java.lang.String> __this__accessibleExpIds = new java.util.ArrayList<java.lang.String>(other.accessibleExpIds);
         this.accessibleExpIds = __this__accessibleExpIds;
       }
+      this.limit = other.limit;
+      this.offset = other.offset;
     }
 
     public getExperimentStatistics_args deepCopy() {
@@ -53002,6 +53047,10 @@
       this.applicationName = null;
       this.resourceHostName = null;
       this.accessibleExpIds = null;
+      this.limit = 50;
+
+      this.offset = 0;
+
     }
 
     public java.lang.String getGatewayId() {
@@ -53185,6 +53234,52 @@
       }
     }
 
+    public int getLimit() {
+      return this.limit;
+    }
+
+    public getExperimentStatistics_args setLimit(int limit) {
+      this.limit = limit;
+      setLimitIsSet(true);
+      return this;
+    }
+
+    public void unsetLimit() {
+      __isset_bitfield = org.apache.thrift.EncodingUtils.clearBit(__isset_bitfield, __LIMIT_ISSET_ID);
+    }
+
+    /** Returns true if field limit is set (has been assigned a value) and false otherwise */
+    public boolean isSetLimit() {
+      return org.apache.thrift.EncodingUtils.testBit(__isset_bitfield, __LIMIT_ISSET_ID);
+    }
+
+    public void setLimitIsSet(boolean value) {
+      __isset_bitfield = org.apache.thrift.EncodingUtils.setBit(__isset_bitfield, __LIMIT_ISSET_ID, value);
+    }
+
+    public int getOffset() {
+      return this.offset;
+    }
+
+    public getExperimentStatistics_args setOffset(int offset) {
+      this.offset = offset;
+      setOffsetIsSet(true);
+      return this;
+    }
+
+    public void unsetOffset() {
+      __isset_bitfield = org.apache.thrift.EncodingUtils.clearBit(__isset_bitfield, __OFFSET_ISSET_ID);
+    }
+
+    /** Returns true if field offset is set (has been assigned a value) and false otherwise */
+    public boolean isSetOffset() {
+      return org.apache.thrift.EncodingUtils.testBit(__isset_bitfield, __OFFSET_ISSET_ID);
+    }
+
+    public void setOffsetIsSet(boolean value) {
+      __isset_bitfield = org.apache.thrift.EncodingUtils.setBit(__isset_bitfield, __OFFSET_ISSET_ID, value);
+    }
+
     public void setFieldValue(_Fields field, java.lang.Object value) {
       switch (field) {
       case GATEWAY_ID:
@@ -53243,6 +53338,22 @@
         }
         break;
 
+      case LIMIT:
+        if (value == null) {
+          unsetLimit();
+        } else {
+          setLimit((java.lang.Integer)value);
+        }
+        break;
+
+      case OFFSET:
+        if (value == null) {
+          unsetOffset();
+        } else {
+          setOffset((java.lang.Integer)value);
+        }
+        break;
+
       }
     }
 
@@ -53269,6 +53380,12 @@
       case ACCESSIBLE_EXP_IDS:
         return getAccessibleExpIds();
 
+      case LIMIT:
+        return getLimit();
+
+      case OFFSET:
+        return getOffset();
+
       }
       throw new java.lang.IllegalStateException();
     }
@@ -53294,6 +53411,10 @@
         return isSetResourceHostName();
       case ACCESSIBLE_EXP_IDS:
         return isSetAccessibleExpIds();
+      case LIMIT:
+        return isSetLimit();
+      case OFFSET:
+        return isSetOffset();
       }
       throw new java.lang.IllegalStateException();
     }
@@ -53376,6 +53497,24 @@
           return false;
       }
 
+      boolean this_present_limit = true;
+      boolean that_present_limit = true;
+      if (this_present_limit || that_present_limit) {
+        if (!(this_present_limit && that_present_limit))
+          return false;
+        if (this.limit != that.limit)
+          return false;
+      }
+
+      boolean this_present_offset = true;
+      boolean that_present_offset = true;
+      if (this_present_offset || that_present_offset) {
+        if (!(this_present_offset && that_present_offset))
+          return false;
+        if (this.offset != that.offset)
+          return false;
+      }
+
       return true;
     }
 
@@ -53407,6 +53546,10 @@
       if (isSetAccessibleExpIds())
         hashCode = hashCode * 8191 + accessibleExpIds.hashCode();
 
+      hashCode = hashCode * 8191 + limit;
+
+      hashCode = hashCode * 8191 + offset;
+
       return hashCode;
     }
 
@@ -53488,6 +53631,26 @@
           return lastComparison;
         }
       }
+      lastComparison = java.lang.Boolean.valueOf(isSetLimit()).compareTo(other.isSetLimit());
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+      if (isSetLimit()) {
+        lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.limit, other.limit);
+        if (lastComparison != 0) {
+          return lastComparison;
+        }
+      }
+      lastComparison = java.lang.Boolean.valueOf(isSetOffset()).compareTo(other.isSetOffset());
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+      if (isSetOffset()) {
+        lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.offset, other.offset);
+        if (lastComparison != 0) {
+          return lastComparison;
+        }
+      }
       return 0;
     }
 
@@ -53555,6 +53718,14 @@
         sb.append(this.accessibleExpIds);
       }
       first = false;
+      if (!first) sb.append(", ");
+      sb.append("limit:");
+      sb.append(this.limit);
+      first = false;
+      if (!first) sb.append(", ");
+      sb.append("offset:");
+      sb.append(this.offset);
+      first = false;
       sb.append(")");
       return sb.toString();
     }
@@ -53671,6 +53842,22 @@
                 org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
               }
               break;
+            case 8: // LIMIT
+              if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
+                struct.limit = iprot.readI32();
+                struct.setLimitIsSet(true);
+              } else { 
+                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+              }
+              break;
+            case 9: // OFFSET
+              if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
+                struct.offset = iprot.readI32();
+                struct.setOffsetIsSet(true);
+              } else { 
+                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+              }
+              break;
             default:
               org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
           }
@@ -53730,6 +53917,12 @@
           }
           oprot.writeFieldEnd();
         }
+        oprot.writeFieldBegin(LIMIT_FIELD_DESC);
+        oprot.writeI32(struct.limit);
+        oprot.writeFieldEnd();
+        oprot.writeFieldBegin(OFFSET_FIELD_DESC);
+        oprot.writeI32(struct.offset);
+        oprot.writeFieldEnd();
         oprot.writeFieldStop();
         oprot.writeStructEnd();
       }
@@ -53763,7 +53956,13 @@
         if (struct.isSetAccessibleExpIds()) {
           optionals.set(3);
         }
-        oprot.writeBitSet(optionals, 4);
+        if (struct.isSetLimit()) {
+          optionals.set(4);
+        }
+        if (struct.isSetOffset()) {
+          optionals.set(5);
+        }
+        oprot.writeBitSet(optionals, 6);
         if (struct.isSetUserName()) {
           oprot.writeString(struct.userName);
         }
@@ -53782,6 +53981,12 @@
             }
           }
         }
+        if (struct.isSetLimit()) {
+          oprot.writeI32(struct.limit);
+        }
+        if (struct.isSetOffset()) {
+          oprot.writeI32(struct.offset);
+        }
       }
 
       @Override
@@ -53793,7 +53998,7 @@
         struct.setFromTimeIsSet(true);
         struct.toTime = iprot.readI64();
         struct.setToTimeIsSet(true);
-        java.util.BitSet incoming = iprot.readBitSet(4);
+        java.util.BitSet incoming = iprot.readBitSet(6);
         if (incoming.get(0)) {
           struct.userName = iprot.readString();
           struct.setUserNameIsSet(true);
@@ -53819,6 +54024,14 @@
           }
           struct.setAccessibleExpIdsIsSet(true);
         }
+        if (incoming.get(4)) {
+          struct.limit = iprot.readI32();
+          struct.setLimitIsSet(true);
+        }
+        if (incoming.get(5)) {
+          struct.offset = iprot.readI32();
+          struct.setOffsetIsSet(true);
+        }
       }
     }
 
diff --git a/modules/registry/release-migration-scripts/0.19-0.20/DeltaScripts/experimentCatalog_schema_delta.sql b/modules/registry/release-migration-scripts/0.19-0.20/DeltaScripts/experimentCatalog_schema_delta.sql
index 5338cbe..89e45dd 100644
--- a/modules/registry/release-migration-scripts/0.19-0.20/DeltaScripts/experimentCatalog_schema_delta.sql
+++ b/modules/registry/release-migration-scripts/0.19-0.20/DeltaScripts/experimentCatalog_schema_delta.sql
@@ -30,3 +30,6 @@
 -- AIRAVATA-3369: Convert USER_FRIENDLY_DESCRIPTION from VARCHAR to TEXT (CLOB)
 alter table EXPERIMENT_INPUT modify column USER_FRIENDLY_DESCRIPTION TEXT;
 alter table PROCESS_INPUT modify column USER_FRIENDLY_DESCRIPTION TEXT;
+
+-- AIRAVATA-3322: Index on experiment_status to help statistics queries
+CREATE INDEX IF NOT EXISTS experiment_status_experiment_id_time_of_state_change_state ON EXPERIMENT_STATUS (EXPERIMENT_ID, TIME_OF_STATE_CHANGE, STATE);
diff --git a/modules/sharing-registry/sharing-registry-server/src/main/java/org/apache/airavata/sharing/registry/server/SharingRegistryServerHandler.java b/modules/sharing-registry/sharing-registry-server/src/main/java/org/apache/airavata/sharing/registry/server/SharingRegistryServerHandler.java
index b138272..8ef6530 100644
--- a/modules/sharing-registry/sharing-registry-server/src/main/java/org/apache/airavata/sharing/registry/server/SharingRegistryServerHandler.java
+++ b/modules/sharing-registry/sharing-registry-server/src/main/java/org/apache/airavata/sharing/registry/server/SharingRegistryServerHandler.java
@@ -62,7 +62,6 @@
     @Override
     public String createDomain(Domain domain) throws SharingRegistryException, DuplicateEntryException, TException {
         try{
-            domain.setDomainId(domain.getName());
             if((new DomainRepository()).get(domain.getDomainId()) != null)
                 throw new DuplicateEntryException("There exist domain with given domain id");
 
diff --git a/thrift-interface-descriptions/airavata-apis/airavata_api.thrift b/thrift-interface-descriptions/airavata-apis/airavata_api.thrift
index 57f7919..eead3b8 100644
--- a/thrift-interface-descriptions/airavata-apis/airavata_api.thrift
+++ b/thrift-interface-descriptions/airavata-apis/airavata_api.thrift
@@ -515,6 +515,12 @@
      * @param resourceHostName
      *       Hostname id substring with which to further filter statistics.
      *
+     * @param limit
+     *       Amount of results to be fetched.
+     *
+     * @param offset
+     *       The starting point of the results to be fetched.
+     *
      **/
     experiment_model.ExperimentStatistics getExperimentStatistics(1: required security_model.AuthzToken authzToken,
                             2: required string gatewayId,
@@ -522,7 +528,9 @@
                             4: required i64 toTime,
                             5: string userName,
                             6: string applicationName,
-                            7: string resourceHostName)
+                            7: string resourceHostName,
+                            8: i32 limit = 50,
+                            9: i32 offset = 0)
                 throws (1: airavata_errors.InvalidRequestException ire,
                         2: airavata_errors.AiravataClientException ace,
                         3: airavata_errors.AiravataSystemException ase,
diff --git a/thrift-interface-descriptions/component-cpis/registry-api.thrift b/thrift-interface-descriptions/component-cpis/registry-api.thrift
index 1694a1d..5cbea03 100644
--- a/thrift-interface-descriptions/component-cpis/registry-api.thrift
+++ b/thrift-interface-descriptions/component-cpis/registry-api.thrift
@@ -365,6 +365,13 @@
              *
              * @param accessibleExpIds
              *    Experiment IDs which are accessible to the current user.
+             *
+             * @param limit
+             *       Amount of results to be fetched.
+             *
+             * @param offset
+             *       The starting point of the results to be fetched.
+             *
              **/
             experiment_model.ExperimentStatistics getExperimentStatistics(1: required string gatewayId,
                                     2: required i64 fromTime,
@@ -372,7 +379,9 @@
                                     4: string userName,
                                     5: string applicationName,
                                     6: string resourceHostName,
-                                    7: list<string> accessibleExpIds)
+                                    7: list<string> accessibleExpIds,
+                                    8: i32 limit = 50,
+                                    9: i32 offset = 0)
                         throws (1: registry_api_errors.RegistryServiceException rse)