You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by zh...@apache.org on 2016/07/29 02:16:36 UTC

incubator-geode git commit: This closes #219

Repository: incubator-geode
Updated Branches:
  refs/heads/develop a4f93cdd6 -> 547fc4886


This closes #219

GEODE-11: Added Limit to lucene search gfsh commands

Added an option to specify limit in gfsh search. Added a dunit test to verify.

GEODE-11 : Pagination for gfsh lucene search command

Added pagination for gfsh lucene search results. Added junit test to verify.

GEODE-11 : Fixing display messages in pagination


Project: http://git-wip-us.apache.org/repos/asf/incubator-geode/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-geode/commit/547fc488
Tree: http://git-wip-us.apache.org/repos/asf/incubator-geode/tree/547fc488
Diff: http://git-wip-us.apache.org/repos/asf/incubator-geode/diff/547fc488

Branch: refs/heads/develop
Commit: 547fc48860c178c1e024379d58128264275fbc98
Parents: a4f93cd
Author: Aparna Dharmakkan <ad...@pivotal.io>
Authored: Wed Jul 20 13:50:59 2016 -0700
Committer: zhouxh <gz...@pivotal.io>
Committed: Thu Jul 28 19:09:44 2016 -0700

----------------------------------------------------------------------
 .../lucene/internal/cli/LuceneCliStrings.java   |   4 +
 .../internal/cli/LuceneIndexCommands.java       | 143 ++++++++++++++++---
 .../lucene/internal/cli/LuceneQueryInfo.java    |  11 +-
 .../functions/LuceneSearchIndexFunction.java    |   5 +-
 .../cli/LuceneIndexCommandsDUnitTest.java       |  32 +++++
 .../cli/LuceneIndexCommandsJUnitTest.java       |  98 ++++++++++++-
 .../LuceneSearchIndexFunctionJUnitTest.java     |   9 +-
 7 files changed, 271 insertions(+), 31 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/547fc488/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneCliStrings.java
----------------------------------------------------------------------
diff --git a/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneCliStrings.java b/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneCliStrings.java
index 2f61cf9..15d2ca7 100644
--- a/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneCliStrings.java
+++ b/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneCliStrings.java
@@ -65,8 +65,12 @@ public class LuceneCliStrings {
   public static final String LUCENE_SEARCH_INDEX__NAME__HELP = "Name of the lucene index to search.";
   public static final String LUCENE_SEARCH_INDEX__REGION_HELP = "Name/Path of the region where the lucene index exists.";
   public static final String LUCENE_SEARCH_INDEX__QUERY_STRING="queryStrings";
+  public static final String LUCENE_SEARCH_INDEX__LIMIT="limit";
+  public static final String LUCENE_SEARCH_INDEX__LIMIT__HELP="Number of search results needed";
   public static final String LUCENE_SEARCH_INDEX__QUERY_STRING__HELP="Query string to search the lucene index";
   public static final String LUCENE_SEARCH_INDEX__DEFAULT_FIELD="defaultField";
   public static final String LUCENE_SEARCH_INDEX__DEFAULT_FIELD__HELP="Default field to search in";
   public static final String LUCENE_SEARCH_INDEX__NO_RESULTS_MESSAGE="No results";
+  public static final String LUCENE_SEARCH_INDEX__PAGE_SIZE="pageSize";
+  public static final String LUCENE_SEARCH_INDEX__PAGE_SIZE__HELP="Number of results to be returned in a page";
 }

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/547fc488/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommands.java
----------------------------------------------------------------------
diff --git a/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommands.java b/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommands.java
index 7f681da..0edd2ff 100755
--- a/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommands.java
+++ b/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommands.java
@@ -16,6 +16,7 @@
  */
 package com.gemstone.gemfire.cache.lucene.internal.cli;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -31,6 +32,7 @@ import com.gemstone.gemfire.cache.execute.Execution;
 import com.gemstone.gemfire.cache.execute.FunctionAdapter;
 import com.gemstone.gemfire.cache.execute.FunctionInvocationTargetException;
 import com.gemstone.gemfire.cache.execute.ResultCollector;
+
 import com.gemstone.gemfire.cache.lucene.internal.cli.functions.LuceneCreateIndexFunction;
 import com.gemstone.gemfire.cache.lucene.internal.cli.functions.LuceneDescribeIndexFunction;
 import com.gemstone.gemfire.cache.lucene.internal.cli.functions.LuceneListIndexFunction;
@@ -45,9 +47,11 @@ import com.gemstone.gemfire.management.internal.cli.CliUtil;
 import com.gemstone.gemfire.management.internal.cli.commands.AbstractCommandsSupport;
 import com.gemstone.gemfire.management.internal.cli.functions.CliFunctionResult;
 import com.gemstone.gemfire.management.internal.cli.i18n.CliStrings;
+import com.gemstone.gemfire.management.internal.cli.result.CommandResult;
 import com.gemstone.gemfire.management.internal.cli.result.CommandResultException;
 import com.gemstone.gemfire.management.internal.cli.result.ResultBuilder;
 import com.gemstone.gemfire.management.internal.cli.result.TabularResultData;
+import com.gemstone.gemfire.management.internal.cli.shell.Gfsh;
 import com.gemstone.gemfire.management.internal.configuration.domain.XmlEntity;
 import com.gemstone.gemfire.management.internal.security.ResourceOperation;
 
@@ -63,6 +67,7 @@ public class LuceneIndexCommands extends AbstractCommandsSupport {
   private static final LuceneCreateIndexFunction createIndexFunction = new LuceneCreateIndexFunction();
   private static final LuceneDescribeIndexFunction describeIndexFunction = new LuceneDescribeIndexFunction();
   private static final LuceneSearchIndexFunction searchIndexFunction = new LuceneSearchIndexFunction();
+  private List<LuceneSearchResults> searchResults=null;
 
   @CliCommand(value = LuceneCliStrings.LUCENE_LIST_INDEX, help = LuceneCliStrings.LUCENE_LIST_INDEX__HELP)
   @CliMetaData(shellOnly = false, relatedTopic={CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA })
@@ -254,30 +259,43 @@ public class LuceneIndexCommands extends AbstractCommandsSupport {
   }
 
   @CliCommand(value = LuceneCliStrings.LUCENE_SEARCH_INDEX, help = LuceneCliStrings.LUCENE_SEARCH_INDEX__HELP)
-  @CliMetaData(shellOnly = false, relatedTopic={CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA })
+  @CliMetaData(shellOnly = false, relatedTopic = { CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA })
   @ResourceOperation(resource = Resource.CLUSTER, operation = Operation.READ)
   public Result searchIndex(
     @CliOption(key = LuceneCliStrings.LUCENE__INDEX_NAME,
-      mandatory=true,
+      mandatory = true,
       help = LuceneCliStrings.LUCENE_SEARCH_INDEX__NAME__HELP) final String indexName,
 
-    @CliOption (key = LuceneCliStrings.LUCENE__REGION_PATH,
+    @CliOption(key = LuceneCliStrings.LUCENE__REGION_PATH,
       mandatory = true,
       optionContext = ConverterHint.REGIONPATH,
       help = LuceneCliStrings.LUCENE_SEARCH_INDEX__REGION_HELP) final String regionPath,
 
-    @CliOption (key = LuceneCliStrings.LUCENE_SEARCH_INDEX__QUERY_STRING,
+    @CliOption(key = LuceneCliStrings.LUCENE_SEARCH_INDEX__QUERY_STRING,
       mandatory = true,
       help = LuceneCliStrings.LUCENE_SEARCH_INDEX__QUERY_STRING__HELP) final String queryString,
 
-    @CliOption (key = LuceneCliStrings.LUCENE_SEARCH_INDEX__DEFAULT_FIELD,
+    @CliOption(key = LuceneCliStrings.LUCENE_SEARCH_INDEX__DEFAULT_FIELD,
       mandatory = true,
-      help = LuceneCliStrings.LUCENE_SEARCH_INDEX__DEFAULT_FIELD__HELP) final String defaultField) {
-    try {
+      help = LuceneCliStrings.LUCENE_SEARCH_INDEX__DEFAULT_FIELD__HELP) final String defaultField,
 
-      LuceneQueryInfo queryInfo=new LuceneQueryInfo(indexName,regionPath,queryString, defaultField);
-      return getSearchResults(queryInfo);
+    @CliOption(key = LuceneCliStrings.LUCENE_SEARCH_INDEX__LIMIT,
+      mandatory = false,
+      unspecifiedDefaultValue = "-1",
+      help = LuceneCliStrings.LUCENE_SEARCH_INDEX__LIMIT__HELP) final int limit,
 
+    @CliOption(key = LuceneCliStrings.LUCENE_SEARCH_INDEX__PAGE_SIZE,
+      mandatory = false,
+      unspecifiedDefaultValue = "-1",
+      help = LuceneCliStrings.LUCENE_SEARCH_INDEX__PAGE_SIZE__HELP) int pageSize)
+  {
+    try {
+      LuceneQueryInfo queryInfo = new LuceneQueryInfo(indexName, regionPath, queryString, defaultField, limit);
+      if (pageSize == -1) {
+        pageSize = Integer.MAX_VALUE;
+      }
+      searchResults = getSearchResults(queryInfo);
+      return displayResults(pageSize);
     }
     catch (FunctionInvocationTargetException ignore) {
       return ResultBuilder.createGemFireErrorResult(CliStrings.format(CliStrings.COULD_NOT_EXECUTE_COMMAND_TRY_AGAIN,
@@ -295,29 +313,108 @@ public class LuceneIndexCommands extends AbstractCommandsSupport {
     }
   }
 
-  private Result getSearchResults(final LuceneQueryInfo queryInfo) throws Exception {
+  private Result displayResults(int pageSize) throws IOException {
+    if (searchResults.size() == 0) {
+      return ResultBuilder.createInfoResult(LuceneCliStrings.LUCENE_SEARCH_INDEX__NO_RESULTS_MESSAGE);
+    }
+
+    Gfsh gfsh = initGfsh();
+    boolean pagination = searchResults.size() > pageSize;
+    int fromIndex = 0;
+    int toIndex = pageSize < searchResults.size() ? pageSize : searchResults.size();
+    int currentPage = 1;
+    int totalPages = (int) Math.ceil((float) searchResults.size() / pageSize);
+    boolean skipDisplay = false;
+    String step = null;
+    do {
+
+      if (!skipDisplay) {
+        CommandResult commandResult = (CommandResult) getResults(fromIndex, toIndex);
+        if (!pagination) {
+          return commandResult;
+        }
+        Gfsh.println();
+        while (commandResult.hasNextLine()) {
+          gfsh.printAsInfo(commandResult.nextLine());
+        }
+        gfsh.printAsInfo("\t\tPage " + currentPage + " of " + totalPages);
+        String message = ("Press n to move to next page, q to quit and p to previous page : ");
+        step = gfsh.interact(message);
+      }
+
+      switch (step) {
+        case "n":
+        {
+          if (currentPage == totalPages) {
+            gfsh.printAsInfo("No more results to display.");
+            step = gfsh.interact("Press p to move to last page and q to quit.");
+            skipDisplay = true;
+            continue;
+          }
+
+          if(skipDisplay) {
+            skipDisplay=false;
+          }
+          else {
+            currentPage++;
+            int current = fromIndex;
+            fromIndex = toIndex;
+            toIndex = (pageSize + fromIndex >= searchResults.size()) ? searchResults.size() : pageSize + fromIndex;
+          }
+          break;
+        }
+        case "p": {
+          if (currentPage == 1) {
+            gfsh.printAsInfo("At the top of the search results.");
+            step = gfsh.interact("Press n to move to the first page and q to quit.");
+            skipDisplay=true;
+            continue;
+          }
+
+          if (skipDisplay) {
+            skipDisplay = false;
+          }
+          else {
+            currentPage--;
+            int current = fromIndex;
+            toIndex = fromIndex;
+            fromIndex = current - pageSize <= 0 ? 0 : current - pageSize;
+          }
+          break;
+        }
+        case "q":
+          return ResultBuilder.createInfoResult("Search complete.");
+        default:
+          Gfsh.println("Invalid option");
+          break;
+      }
+    } while(true);
+  }
+
+  protected Gfsh initGfsh() {
+    return Gfsh.getCurrentInstance();
+  }
+
+  private List<LuceneSearchResults> getSearchResults(final LuceneQueryInfo queryInfo) throws CommandResultException {
     GeodeSecurityUtil.authorizeRegionManage(queryInfo.getRegionPath());
     final String[] groups = {};
     final ResultCollector<?, ?> rc = this.executeFunctionOnGroups(searchIndexFunction, groups, queryInfo);
     final List<Set<LuceneSearchResults>> functionResults = (List<Set<LuceneSearchResults>>) rc.getResult();
 
-    List<LuceneSearchResults> results = functionResults.stream()
+    return functionResults.stream()
       .flatMap(set -> set.stream())
       .sorted()
       .collect(Collectors.toList());
-    if (results.size() != 0) {
-      final TabularResultData data = ResultBuilder.createTabularResultData();
-      for (LuceneSearchResults struct : results) {
-        data.accumulate("key", struct.getKey());
-        data.accumulate("value", struct.getValue());
-        data.accumulate("score", struct.getScore());
-      }
-      return ResultBuilder.buildResult(data);
-    }
-    else {
-      return ResultBuilder.createInfoResult(LuceneCliStrings.LUCENE_SEARCH_INDEX__NO_RESULTS_MESSAGE);
+  }
+
+  private Result getResults(int fromIndex, int toIndex){
+    final TabularResultData data = ResultBuilder.createTabularResultData();
+    for (int i = fromIndex; i < toIndex; i++) {
+      data.accumulate("key", searchResults.get(i).getKey());
+      data.accumulate("value", searchResults.get(i).getValue());
+      data.accumulate("score", searchResults.get(i).getScore());
     }
-    //@TODO : Pagination
+    return ResultBuilder.buildResult(data);
   }
 
   protected ResultCollector<?, ?> executeFunctionOnGroups(FunctionAdapter function, String[]groups, final LuceneIndexInfo indexInfo) throws CommandResultException {

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/547fc488/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneQueryInfo.java
----------------------------------------------------------------------
diff --git a/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneQueryInfo.java b/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneQueryInfo.java
index 2f86064..0e5012e 100644
--- a/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneQueryInfo.java
+++ b/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneQueryInfo.java
@@ -19,22 +19,27 @@ package com.gemstone.gemfire.cache.lucene.internal.cli;
 
 import java.io.Serializable;
 
+import com.gemstone.gemfire.cache.lucene.LuceneQueryFactory;
+
 public class LuceneQueryInfo implements Serializable {
   private static final long serialVersionUID = 1L;
   private String indexName;
   private String regionPath;
   private String queryString;
   private String defaultField;
+  private int limit;
 
   public LuceneQueryInfo(final String indexName,
                          final String regionPath,
                          final String queryString,
-                         final String defaultField)
+                         final String defaultField,
+                         final int limit)
   {
     this.indexName = indexName;
     this.regionPath = regionPath;
     this.queryString = queryString;
     this.defaultField = defaultField;
+    this.limit = limit;
   }
 
   public String getIndexName() {
@@ -52,4 +57,8 @@ public class LuceneQueryInfo implements Serializable {
   public String getDefaultField() {
     return defaultField;
   }
+
+  public int getLimit() {
+    if (limit == -1) return LuceneQueryFactory.DEFAULT_LIMIT;
+    else return limit; }
 }

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/547fc488/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/functions/LuceneSearchIndexFunction.java
----------------------------------------------------------------------
diff --git a/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/functions/LuceneSearchIndexFunction.java b/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/functions/LuceneSearchIndexFunction.java
index a76a741..8d73790 100755
--- a/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/functions/LuceneSearchIndexFunction.java
+++ b/geode-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/cli/functions/LuceneSearchIndexFunction.java
@@ -71,8 +71,9 @@ public class LuceneSearchIndexFunction<K,V> extends FunctionAdapter implements I
     LuceneService luceneService = LuceneServiceProvider.get(getCache());
     try {
       if (cache.getRegion(queryInfo.getRegionPath())!=null) {
-        final LuceneQuery<K, V> query = luceneService.createLuceneQueryFactory().create(
-          queryInfo.getIndexName(), queryInfo.getRegionPath(), queryInfo.getQueryString(), queryInfo.getDefaultField());
+        final LuceneQuery<K, V> query = luceneService.createLuceneQueryFactory()
+          .setResultLimit(queryInfo.getLimit())
+          .create(queryInfo.getIndexName(), queryInfo.getRegionPath(), queryInfo.getQueryString(), queryInfo.getDefaultField());
         PageableLuceneQueryResults pageableLuceneQueryResults = query.findPages();
         while (pageableLuceneQueryResults.hasNext()) {
           List<LuceneResultStruct> page = pageableLuceneQueryResults.next();

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/547fc488/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommandsDUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommandsDUnitTest.java b/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommandsDUnitTest.java
index 32de8e4..141063f 100755
--- a/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommandsDUnitTest.java
+++ b/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommandsDUnitTest.java
@@ -318,6 +318,8 @@ public class LuceneIndexCommandsDUnitTest extends CliCommandTestBase {
     csb.addOption(LuceneCliStrings.LUCENE__REGION_PATH,REGION_NAME);
     csb.addOption(LuceneCliStrings.LUCENE_SEARCH_INDEX__QUERY_STRING,"field1:value1");
     csb.addOption(LuceneCliStrings.LUCENE_SEARCH_INDEX__DEFAULT_FIELD,"field1");
+    executeCommandAndLogResult(csb);
+
     TabularResultData data = (TabularResultData) executeCommandAndGetResult(csb).getResultData();
     assertEquals(4,data.retrieveAllValues("key").size());
   }
@@ -343,9 +345,39 @@ public class LuceneIndexCommandsDUnitTest extends CliCommandTestBase {
     csb.addOption(LuceneCliStrings.LUCENE__REGION_PATH,REGION_NAME);
     csb.addOption(LuceneCliStrings.LUCENE_SEARCH_INDEX__QUERY_STRING,"NotAnExistingValue");
     csb.addOption(LuceneCliStrings.LUCENE_SEARCH_INDEX__DEFAULT_FIELD,"field1");
+    executeCommandAndLogResult(csb);
+
     String resultAsString = executeCommandAndLogResult(csb);
     assertTrue(resultAsString.contains(LuceneCliStrings.LUCENE_SEARCH_INDEX__NO_RESULTS_MESSAGE));
   }
+
+  @Test
+  public void searchWithLimitShouldReturnCorrectResults() throws Exception {
+    final VM vm1 = Host.getHost(0).getVM(1);
+
+    createIndex(vm1);
+    Map<String,TestObject> entries=new HashMap<>();
+    entries.put("A",new TestObject("field1:value1 ","field2:value2","field3:value3"));
+    entries.put("B",new TestObject("ABC","EFG","HIJ"));
+    entries.put("C",new TestObject("field1:value1","QWE","RTY"));
+    entries.put("D",new TestObject("ABC","EFG","HIJ"));
+    entries.put("E",new TestObject("field1 :value1","ABC","EFG"));
+    entries.put("F",new TestObject("ABC","EFG","HIJ"));
+    entries.put("G",new TestObject("field1: value1","JKR","POW"));
+    entries.put("H",new TestObject("ABC","EFG","H2J"));
+    putEntries(vm1,entries,8);
+
+    CommandStringBuilder csb = new CommandStringBuilder(LuceneCliStrings.LUCENE_SEARCH_INDEX);
+    csb.addOption(LuceneCliStrings.LUCENE__INDEX_NAME,INDEX_NAME);
+    csb.addOption(LuceneCliStrings.LUCENE__REGION_PATH,REGION_NAME);
+    csb.addOption(LuceneCliStrings.LUCENE_SEARCH_INDEX__QUERY_STRING,"field1:value1");
+    csb.addOption(LuceneCliStrings.LUCENE_SEARCH_INDEX__DEFAULT_FIELD,"field1");
+    csb.addOption(LuceneCliStrings.LUCENE_SEARCH_INDEX__LIMIT,"2");
+    executeCommandAndLogResult(csb);
+    TabularResultData data = (TabularResultData) executeCommandAndGetResult(csb).getResultData();
+    assertEquals(2,data.retrieveAllValues("key").size());
+  }
+
   private void createRegion() {
     getCache().createRegionFactory(RegionShortcut.PARTITION).create(REGION_NAME);
   }

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/547fc488/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommandsJUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommandsJUnitTest.java b/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommandsJUnitTest.java
index b9e4e86..09cd737 100644
--- a/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommandsJUnitTest.java
+++ b/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/LuceneIndexCommandsJUnitTest.java
@@ -19,6 +19,8 @@ import static org.junit.Assert.*;
 import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.*;
 
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -34,6 +36,7 @@ import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
+import org.mockito.ArgumentCaptor;
 
 import com.gemstone.gemfire.cache.Cache;
 import com.gemstone.gemfire.cache.execute.Execution;
@@ -50,7 +53,9 @@ import com.gemstone.gemfire.management.cli.Result.Status;
 import com.gemstone.gemfire.management.internal.cli.functions.CliFunctionResult;
 import com.gemstone.gemfire.management.internal.cli.result.CommandResult;
 import com.gemstone.gemfire.management.internal.cli.result.CommandResultException;
+import com.gemstone.gemfire.management.internal.cli.result.ResultBuilder;
 import com.gemstone.gemfire.management.internal.cli.result.TabularResultData;
+import com.gemstone.gemfire.management.internal.cli.shell.Gfsh;
 import com.gemstone.gemfire.test.junit.categories.UnitTest;
 
 /**
@@ -125,7 +130,6 @@ public class LuceneIndexCommandsJUnitTest {
     final LuceneIndexDetails indexDetails2 = createIndexDetails("memberSix", "/Employees", searchableFields, fieldAnalyzers,mockIndexStats2, true);
     final LuceneIndexDetails indexDetails3 = createIndexDetails("memberTen", "/Employees", searchableFields, fieldAnalyzers,mockIndexStats3, true);
 
-
     final List<Set<LuceneIndexDetails>> results = new ArrayList<>();
 
     results.add(CollectionUtils.asSet(indexDetails2, indexDetails1,indexDetails3));
@@ -225,7 +229,7 @@ public class LuceneIndexCommandsJUnitTest {
     doReturn(mockResultCollector).when(commands).executeFunctionOnGroups(isA(LuceneSearchIndexFunction.class),any(),any(LuceneQueryInfo.class));
     doReturn(queryResultsList).when(mockResultCollector).getResult();
 
-    CommandResult result = (CommandResult) commands.searchIndex("index","region","Result1","field1");
+    CommandResult result = (CommandResult) commands.searchIndex("index","region","Result1","field1",-1,-1);
 
     TabularResultData data = (TabularResultData) result.getResultData();
 
@@ -234,6 +238,96 @@ public class LuceneIndexCommandsJUnitTest {
     assertEquals(Arrays.asList("1.1","1.2","1.3"), data.retrieveAllValues("score"));
   }
 
+  @Test
+  public void testSearchIndexWithPaging() throws Exception {
+    final Cache mockCache = mock(Cache.class, "Cache");
+    final Gfsh mockGfsh = mock(Gfsh.class);
+    final ResultCollector mockResultCollector = mock(ResultCollector.class, "ResultCollector");
+    final LuceneIndexCommands commands=spy(createIndexCommands(mockCache,null));
+    ArgumentCaptor<String> resultCaptor  = ArgumentCaptor.forClass(String.class);
+
+    LuceneSearchResults result1=createQueryResults("A","Result1",Float.valueOf("1.7"));
+    LuceneSearchResults result2=createQueryResults("B","Result1",Float.valueOf("1.6"));
+    LuceneSearchResults result3=createQueryResults("C","Result1",Float.valueOf("1.5"));
+    LuceneSearchResults result4=createQueryResults("D","Result1",Float.valueOf("1.4"));
+    LuceneSearchResults result5=createQueryResults("E","Result1",Float.valueOf("1.3"));
+    LuceneSearchResults result6=createQueryResults("F","Result1",Float.valueOf("1.2"));
+    LuceneSearchResults result7=createQueryResults("G","Result1",Float.valueOf("1.1"));
+    final List<Set<LuceneSearchResults>> queryResultsList = getSearchResults(result1, result2, result3, result4, result5, result6, result7);
+
+    doReturn(mockResultCollector).when(commands).executeFunctionOnGroups(isA(LuceneSearchIndexFunction.class),any(),any(LuceneQueryInfo.class));
+    doReturn(queryResultsList).when(mockResultCollector).getResult();
+    doReturn(mockGfsh).when(commands).initGfsh();
+    when(mockGfsh.interact(anyString())).thenReturn("n").thenReturn("n").thenReturn("n").thenReturn("n")
+      .thenReturn("p").thenReturn("p").thenReturn("p").thenReturn("p").thenReturn("p").thenReturn("n").thenReturn("q");
+
+    LuceneSearchResults[] expectedResults = new LuceneSearchResults[] {result7,result6,result5,result4,result3,result2,result1};
+    String expectedPage1 = getPage(expectedResults, new int[] {0,1});
+    String expectedPage2 = getPage(expectedResults, new int[] {2,3});
+    String expectedPage3 = getPage(expectedResults, new int[] {4,5});
+    String expectedPage4 = getPage(expectedResults, new int[] {6});
+
+    commands.searchIndex("index","region","Result1","field1",-1,2);
+    verify(mockGfsh, times(20)).printAsInfo(resultCaptor.capture());
+    List<String> actualPageResults=resultCaptor.getAllValues();
+
+    assertEquals(expectedPage1,actualPageResults.get(0));
+    assertEquals("\t\tPage 1 of 4",actualPageResults.get(1));
+
+    assertEquals(expectedPage2,actualPageResults.get(2));
+    assertEquals("\t\tPage 2 of 4",actualPageResults.get(3));
+
+    assertEquals(expectedPage3,actualPageResults.get(4));
+    assertEquals("\t\tPage 3 of 4",actualPageResults.get(5));
+
+    assertEquals(expectedPage4,actualPageResults.get(6));
+    assertEquals("\t\tPage 4 of 4",actualPageResults.get(7));
+
+    assertEquals("No more results to display.", actualPageResults.get(8));
+
+    assertEquals(expectedPage4,actualPageResults.get(9));
+    assertEquals("\t\tPage 4 of 4",actualPageResults.get(10));
+
+    assertEquals(expectedPage3,actualPageResults.get(11));
+    assertEquals("\t\tPage 3 of 4",actualPageResults.get(12));
+
+    assertEquals(expectedPage2,actualPageResults.get(13));
+    assertEquals("\t\tPage 2 of 4",actualPageResults.get(14));
+
+    assertEquals(expectedPage1,actualPageResults.get(15));
+    assertEquals("\t\tPage 1 of 4",actualPageResults.get(16));
+
+    assertEquals("At the top of the search results.", actualPageResults.get(17));
+
+    assertEquals(expectedPage1,actualPageResults.get(18));
+    assertEquals("\t\tPage 1 of 4",actualPageResults.get(19));
+
+  }
+
+  private String getPage(final LuceneSearchResults[] expectedResults, int[] indexList) {
+    final TabularResultData data = ResultBuilder.createTabularResultData();
+    for (int i:indexList) {
+      data.accumulate("key", expectedResults[i].getKey());
+      data.accumulate("value", expectedResults[i].getValue());
+      data.accumulate("score", expectedResults[i].getScore());
+    }
+    CommandResult commandResult = (CommandResult) ResultBuilder.buildResult(data);
+    StringBuffer buffer = new StringBuffer();
+    while (commandResult.hasNextLine())
+      buffer.append(commandResult.nextLine());
+    return buffer.toString();
+  }
+
+  private List<Set<LuceneSearchResults>> getSearchResults(LuceneSearchResults ... results)
+  {
+    final List<Set<LuceneSearchResults>> queryResultsList = new ArrayList<>();
+    HashSet<LuceneSearchResults> queryResults = new HashSet<>();
+    for(LuceneSearchResults result : results)
+      queryResults.add(result);
+    queryResultsList.add(queryResults);
+    return queryResultsList;
+  }
+
 
   private LuceneIndexStats getMockIndexStats(int queries, int commits, int updates, int docs) {
     LuceneIndexStats mockIndexStats=mock(LuceneIndexStats.class);

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/547fc488/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/functions/LuceneSearchIndexFunctionJUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/functions/LuceneSearchIndexFunctionJUnitTest.java b/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/functions/LuceneSearchIndexFunctionJUnitTest.java
index 88f608c..f3e970f 100644
--- a/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/functions/LuceneSearchIndexFunctionJUnitTest.java
+++ b/geode-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/cli/functions/LuceneSearchIndexFunctionJUnitTest.java
@@ -54,7 +54,7 @@ public class LuceneSearchIndexFunctionJUnitTest {
     ResultSender resultSender = mock(ResultSender.class);
     GemFireCacheImpl cache = Fakes.cache();
 
-    LuceneQueryInfo queryInfo = createMockQueryInfo("index","region","field1:region1","field1");
+    LuceneQueryInfo queryInfo = createMockQueryInfo("index","region","field1:region1","field1",1);
     InternalLuceneService service = getMockLuceneService("A","Value","1.2");
     Region mockRegion=mock(Region.class);
 
@@ -81,7 +81,8 @@ public class LuceneSearchIndexFunctionJUnitTest {
   }
   private InternalLuceneService getMockLuceneService(String resultKey, String resultValue, String resultScore) throws LuceneQueryException{
     InternalLuceneService service=mock(InternalLuceneService.class);
-    LuceneQueryFactory mockQueryFactory = mock(LuceneQueryFactory.class);
+    LuceneQueryFactory mockQueryFactory = spy(LuceneQueryFactory.class);
+    LuceneQueryFactory mockQueryFactory2 = mock(LuceneQueryFactory.class);
     LuceneQuery mockQuery=mock(LuceneQuery.class);
     PageableLuceneQueryResults pageableLuceneQueryResults = mock(PageableLuceneQueryResults.class);
     LuceneResultStruct<String,String> resultStruct = new LuceneResultStructImpl(resultKey,resultValue,Float.valueOf(resultScore));
@@ -89,6 +90,7 @@ public class LuceneSearchIndexFunctionJUnitTest {
     queryResults.add(resultStruct);
 
     doReturn(mockQueryFactory).when(service).createLuceneQueryFactory();
+    doReturn(mockQueryFactory).when(mockQueryFactory).setResultLimit(anyInt());
     doReturn(mockQuery).when(mockQueryFactory).create(any(),any(),any(),any());
     when(mockQuery.findPages()).thenReturn(pageableLuceneQueryResults);
     when(pageableLuceneQueryResults.hasNext()).thenReturn(true).thenReturn(false);
@@ -97,12 +99,13 @@ public class LuceneSearchIndexFunctionJUnitTest {
     return service;
   }
 
-  private LuceneQueryInfo createMockQueryInfo(final String index, final String region, final String query, final String field) {
+  private LuceneQueryInfo createMockQueryInfo(final String index, final String region, final String query, final String field, final int limit) {
     LuceneQueryInfo queryInfo = mock(LuceneQueryInfo.class);
     when(queryInfo.getIndexName()).thenReturn(index);
     when(queryInfo.getRegionPath()).thenReturn(region);
     when(queryInfo.getQueryString()).thenReturn(query);
     when(queryInfo.getDefaultField()).thenReturn(field);
+    when(queryInfo.getLimit()).thenReturn(limit);
     return queryInfo;
   }