org.apache.geode.cache.lucene.internal.cli.LuceneIndexCommands.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.geode.cache.lucene.internal.cli.LuceneIndexCommands.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package org.apache.geode.cache.lucene.internal.cli;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.lang.StringUtils;
import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;

import org.apache.geode.SystemFailure;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.execute.Execution;
import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.FunctionInvocationTargetException;
import org.apache.geode.cache.execute.ResultCollector;
import org.apache.geode.cache.lucene.internal.cli.functions.LuceneCreateIndexFunction;
import org.apache.geode.cache.lucene.internal.cli.functions.LuceneDescribeIndexFunction;
import org.apache.geode.cache.lucene.internal.cli.functions.LuceneDestroyIndexFunction;
import org.apache.geode.cache.lucene.internal.cli.functions.LuceneListIndexFunction;
import org.apache.geode.cache.lucene.internal.cli.functions.LuceneSearchIndexFunction;
import org.apache.geode.cache.lucene.internal.security.LucenePermission;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.cache.execute.AbstractExecution;
import org.apache.geode.management.cli.CliMetaData;
import org.apache.geode.management.cli.ConverterHint;
import org.apache.geode.management.cli.Result;
import org.apache.geode.management.internal.cli.CliUtil;
import org.apache.geode.management.internal.cli.LogWrapper;
import org.apache.geode.management.internal.cli.commands.GfshCommand;
import org.apache.geode.management.internal.cli.functions.CliFunctionResult;
import org.apache.geode.management.internal.cli.i18n.CliStrings;
import org.apache.geode.management.internal.cli.result.CommandResult;
import org.apache.geode.management.internal.cli.result.CommandResultException;
import org.apache.geode.management.internal.cli.result.ResultBuilder;
import org.apache.geode.management.internal.cli.result.TabularResultData;
import org.apache.geode.management.internal.cli.shell.Gfsh;
import org.apache.geode.management.internal.configuration.domain.XmlEntity;
import org.apache.geode.security.GemFireSecurityException;
import org.apache.geode.security.ResourcePermission.Operation;
import org.apache.geode.security.ResourcePermission.Resource;

/**
 * The LuceneIndexCommands class encapsulates all Geode shell (Gfsh) commands related to Lucene
 * indexes defined in Geode.
 *
 * @see GfshCommand
 * @see LuceneIndexDetails
 * @see LuceneListIndexFunction
 */
@SuppressWarnings("unused")
public class LuceneIndexCommands implements GfshCommand {
    private static final LuceneCreateIndexFunction createIndexFunction = new LuceneCreateIndexFunction();
    private static final LuceneDescribeIndexFunction describeIndexFunction = new LuceneDescribeIndexFunction();
    private static final LuceneSearchIndexFunction searchIndexFunction = new LuceneSearchIndexFunction();
    private static final LuceneDestroyIndexFunction destroyIndexFunction = new LuceneDestroyIndexFunction();
    private List<LuceneSearchResults> searchResults = null;

    @CliCommand(value = LuceneCliStrings.LUCENE_LIST_INDEX, help = LuceneCliStrings.LUCENE_LIST_INDEX__HELP)
    @CliMetaData(relatedTopic = { CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA })
    public Result listIndex(
            @CliOption(key = LuceneCliStrings.LUCENE_LIST_INDEX__STATS, specifiedDefaultValue = "true", unspecifiedDefaultValue = "false", help = LuceneCliStrings.LUCENE_LIST_INDEX__STATS__HELP) final boolean stats) {

        getSecurityService().authorize(Resource.CLUSTER, Operation.READ, LucenePermission.TARGET);

        try {
            return toTabularResult(getIndexListing(), stats);
        } catch (FunctionInvocationTargetException ignore) {
            return ResultBuilder.createGemFireErrorResult(CliStrings
                    .format(CliStrings.COULD_NOT_EXECUTE_COMMAND_TRY_AGAIN, LuceneCliStrings.LUCENE_LIST_INDEX));
        } catch (VirtualMachineError e) {
            SystemFailure.initiateFailure(e);
            throw e;
        } catch (Throwable t) {
            SystemFailure.checkFailure();
            getCache().getLogger().info(t);
            return ResultBuilder.createGemFireErrorResult(
                    String.format(LuceneCliStrings.LUCENE_LIST_INDEX__ERROR_MESSAGE, toString(t, isDebugging())));
        }
    }

    @SuppressWarnings("unchecked")
    protected List<LuceneIndexDetails> getIndexListing() {
        final Execution functionExecutor = getMembersFunctionExecutor(getMembers(getCache()));

        if (functionExecutor instanceof AbstractExecution) {
            ((AbstractExecution) functionExecutor).setIgnoreDepartedMembers(true);
        }

        final ResultCollector resultsCollector = functionExecutor.execute(new LuceneListIndexFunction());
        final List<Set<LuceneIndexDetails>> results = (List<Set<LuceneIndexDetails>>) resultsCollector.getResult();

        List<LuceneIndexDetails> sortedResults = results.stream().flatMap(Collection::stream).sorted()
                .collect(Collectors.toList());
        LinkedHashSet<LuceneIndexDetails> uniqResults = new LinkedHashSet<>();
        uniqResults.addAll(sortedResults);
        sortedResults.clear();
        sortedResults.addAll(uniqResults);
        return sortedResults;
    }

    protected Result toTabularResult(final List<LuceneIndexDetails> indexDetailsList, boolean stats) {
        if (!indexDetailsList.isEmpty()) {
            final TabularResultData indexData = ResultBuilder.createTabularResultData();

            for (final LuceneIndexDetails indexDetails : indexDetailsList) {
                indexData.accumulate("Index Name", indexDetails.getIndexName());
                indexData.accumulate("Region Path", indexDetails.getRegionPath());
                indexData.accumulate("Server Name", indexDetails.getServerName());
                indexData.accumulate("Indexed Fields", indexDetails.getSearchableFieldNamesString());
                indexData.accumulate("Field Analyzer", indexDetails.getFieldAnalyzersString());
                indexData.accumulate("Status", indexDetails.getInitialized() ? "Initialized" : "Defined");

                if (stats) {
                    if (!indexDetails.getInitialized()) {
                        indexData.accumulate("Query Executions", "NA");
                        indexData.accumulate("Updates", "NA");
                        indexData.accumulate("Commits", "NA");
                        indexData.accumulate("Documents", "NA");
                    } else {
                        indexData.accumulate("Query Executions",
                                indexDetails.getIndexStats().get("queryExecutions"));
                        indexData.accumulate("Updates", indexDetails.getIndexStats().get("updates"));
                        indexData.accumulate("Commits", indexDetails.getIndexStats().get("commits"));
                        indexData.accumulate("Documents", indexDetails.getIndexStats().get("documents"));
                    }
                }
            }
            return ResultBuilder.buildResult(indexData);
        } else {
            return ResultBuilder.createInfoResult(LuceneCliStrings.LUCENE_LIST_INDEX__INDEXES_NOT_FOUND_MESSAGE);
        }
    }

    /**
     * On the server, we also verify the resource operation permissions CLUSTER:WRITE:DISK
     */
    @CliCommand(value = LuceneCliStrings.LUCENE_CREATE_INDEX, help = LuceneCliStrings.LUCENE_CREATE_INDEX__HELP)
    @CliMetaData(relatedTopic = { CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA })
    // TODO : Add optionContext for indexName
    public Result createIndex(
            @CliOption(key = LuceneCliStrings.LUCENE__INDEX_NAME, mandatory = true, help = LuceneCliStrings.LUCENE_CREATE_INDEX__NAME__HELP) final String indexName,

            @CliOption(key = LuceneCliStrings.LUCENE__REGION_PATH, mandatory = true, optionContext = ConverterHint.REGION_PATH, help = LuceneCliStrings.LUCENE_CREATE_INDEX__REGION_HELP) final String regionPath,

            @CliOption(key = LuceneCliStrings.LUCENE_CREATE_INDEX__FIELD, mandatory = true, help = LuceneCliStrings.LUCENE_CREATE_INDEX__FIELD_HELP) final String[] fields,

            @CliOption(key = LuceneCliStrings.LUCENE_CREATE_INDEX__ANALYZER, help = LuceneCliStrings.LUCENE_CREATE_INDEX__ANALYZER_HELP) final String[] analyzers) {

        Result result;
        XmlEntity xmlEntity = null;

        // Every lucene index potentially writes to disk.
        getSecurityService().authorize(Resource.CLUSTER, Operation.MANAGE, LucenePermission.TARGET);

        try {
            final InternalCache cache = getCache();

            // trim fields for any leading trailing spaces.
            String[] trimmedFields = Arrays.stream(fields).map(String::trim).toArray(String[]::new);
            LuceneIndexInfo indexInfo = new LuceneIndexInfo(indexName, regionPath, trimmedFields, analyzers);

            final ResultCollector<?, ?> rc = this.executeFunctionOnAllMembers(createIndexFunction, indexInfo);
            final List<CliFunctionResult> funcResults = (List<CliFunctionResult>) rc.getResult();

            final TabularResultData tabularResult = ResultBuilder.createTabularResultData();
            for (final CliFunctionResult cliFunctionResult : funcResults) {
                tabularResult.accumulate("Member", cliFunctionResult.getMemberIdOrName());

                if (cliFunctionResult.isSuccessful()) {
                    tabularResult.accumulate("Status", "Successfully created lucene index");
                    // if (xmlEntity == null) {
                    // xmlEntity = cliFunctionResult.getXmlEntity();
                    // }
                } else {
                    tabularResult.accumulate("Status", "Failed: " + cliFunctionResult.getMessage());
                }
            }
            result = ResultBuilder.buildResult(tabularResult);
        } catch (IllegalArgumentException iae) {
            LogWrapper.getInstance().info(iae.getMessage());
            result = ResultBuilder.createUserErrorResult(iae.getMessage());
        } catch (CommandResultException crex) {
            result = crex.getResult();
        } catch (Exception e) {
            result = ResultBuilder.createGemFireErrorResult(e.getMessage());
        }
        // TODO - store in cluster config
        // if (xmlEntity != null) {
        // result.setCommandPersisted((new SharedConfigurationWriter()).addXmlEntity(xmlEntity,
        // groups));
        // }

        return result;
    }

    @CliCommand(value = LuceneCliStrings.LUCENE_DESCRIBE_INDEX, help = LuceneCliStrings.LUCENE_DESCRIBE_INDEX__HELP)
    @CliMetaData(relatedTopic = { CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA })
    public Result describeIndex(
            @CliOption(key = LuceneCliStrings.LUCENE__INDEX_NAME, mandatory = true, help = LuceneCliStrings.LUCENE_DESCRIBE_INDEX__NAME__HELP) final String indexName,

            @CliOption(key = LuceneCliStrings.LUCENE__REGION_PATH, mandatory = true, optionContext = ConverterHint.REGION_PATH, help = LuceneCliStrings.LUCENE_DESCRIBE_INDEX__REGION_HELP) final String regionPath) {

        getSecurityService().authorize(Resource.CLUSTER, Operation.READ, LucenePermission.TARGET);

        try {
            LuceneIndexInfo indexInfo = new LuceneIndexInfo(indexName, regionPath);
            return toTabularResult(getIndexDetails(indexInfo), true);
        } catch (FunctionInvocationTargetException ignore) {
            return ResultBuilder.createGemFireErrorResult(CliStrings.format(
                    CliStrings.COULD_NOT_EXECUTE_COMMAND_TRY_AGAIN, LuceneCliStrings.LUCENE_DESCRIBE_INDEX));
        } catch (VirtualMachineError e) {
            SystemFailure.initiateFailure(e);
            throw e;
        } catch (IllegalArgumentException e) {
            return ResultBuilder.createInfoResult(e.getMessage());
        } catch (Throwable t) {
            SystemFailure.checkFailure();
            getCache().getLogger().info(t);
            return ResultBuilder.createGemFireErrorResult(String
                    .format(LuceneCliStrings.LUCENE_DESCRIBE_INDEX__ERROR_MESSAGE, toString(t, isDebugging())));
        }
    }

    @SuppressWarnings("unchecked")
    protected List<LuceneIndexDetails> getIndexDetails(LuceneIndexInfo indexInfo) throws Exception {
        final ResultCollector<?, ?> rc = executeFunctionOnRegion(describeIndexFunction, indexInfo, true);
        final List<LuceneIndexDetails> funcResults = (List<LuceneIndexDetails>) rc.getResult();
        return funcResults.stream().filter(Objects::nonNull).collect(Collectors.toList());
    }

    /**
     * Internally, we verify the resource operation permissions DATA:READ:[RegionName]
     */
    @CliCommand(value = LuceneCliStrings.LUCENE_SEARCH_INDEX, help = LuceneCliStrings.LUCENE_SEARCH_INDEX__HELP)
    @CliMetaData(relatedTopic = { CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA })
    public Result searchIndex(
            @CliOption(key = LuceneCliStrings.LUCENE__INDEX_NAME, mandatory = true, help = LuceneCliStrings.LUCENE_SEARCH_INDEX__NAME__HELP) final String indexName,

            @CliOption(key = LuceneCliStrings.LUCENE__REGION_PATH, mandatory = true, optionContext = ConverterHint.REGION_PATH, help = LuceneCliStrings.LUCENE_SEARCH_INDEX__REGION_HELP) final String regionPath,

            @CliOption(key = { LuceneCliStrings.LUCENE_SEARCH_INDEX__QUERY_STRING,
                    LuceneCliStrings.LUCENE_SEARCH_INDEX__QUERY_STRINGS }, mandatory = true, help = LuceneCliStrings.LUCENE_SEARCH_INDEX__QUERY_STRING__HELP) final String queryString,

            @CliOption(key = LuceneCliStrings.LUCENE_SEARCH_INDEX__DEFAULT_FIELD, mandatory = true, help = LuceneCliStrings.LUCENE_SEARCH_INDEX__DEFAULT_FIELD__HELP) final String defaultField,

            @CliOption(key = LuceneCliStrings.LUCENE_SEARCH_INDEX__LIMIT, unspecifiedDefaultValue = "-1", help = LuceneCliStrings.LUCENE_SEARCH_INDEX__LIMIT__HELP) final int limit,

            @CliOption(key = LuceneCliStrings.LUCENE_SEARCH_INDEX__KEYSONLY, unspecifiedDefaultValue = "false", help = LuceneCliStrings.LUCENE_SEARCH_INDEX__KEYSONLY__HELP) boolean keysOnly) {

        getSecurityService().authorize(Resource.DATA, Operation.READ, regionPath);

        try {
            LuceneQueryInfo queryInfo = new LuceneQueryInfo(indexName, regionPath, queryString, defaultField, limit,
                    keysOnly);
            int pageSize = Integer.MAX_VALUE;
            searchResults = getSearchResults(queryInfo);
            return displayResults(pageSize, keysOnly);
        } catch (FunctionInvocationTargetException ignore) {
            return ResultBuilder.createGemFireErrorResult(CliStrings
                    .format(CliStrings.COULD_NOT_EXECUTE_COMMAND_TRY_AGAIN, LuceneCliStrings.LUCENE_SEARCH_INDEX));
        } catch (VirtualMachineError e) {
            SystemFailure.initiateFailure(e);
            throw e;
        } catch (IllegalArgumentException e) {
            return ResultBuilder.createInfoResult(e.getMessage());
        } catch (GemFireSecurityException e) {
            throw e;
        } catch (Throwable t) {
            SystemFailure.checkFailure();
            getCache().getLogger().info(t);
            return ResultBuilder.createGemFireErrorResult(
                    String.format(LuceneCliStrings.LUCENE_SEARCH_INDEX__ERROR_MESSAGE, toString(t, isDebugging())));
        }
    }

    @CliCommand(value = LuceneCliStrings.LUCENE_DESTROY_INDEX, help = LuceneCliStrings.LUCENE_DESTROY_INDEX__HELP)
    @CliMetaData(relatedTopic = { CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA })
    public Result destroyIndex(
            @CliOption(key = LuceneCliStrings.LUCENE__INDEX_NAME, help = LuceneCliStrings.LUCENE_DESTROY_INDEX__NAME__HELP) final String indexName,

            @CliOption(key = LuceneCliStrings.LUCENE__REGION_PATH, mandatory = true, optionContext = ConverterHint.REGION_PATH, help = LuceneCliStrings.LUCENE_DESTROY_INDEX__REGION_HELP) final String regionPath) {

        if (StringUtils.isBlank(regionPath) || regionPath.equals(Region.SEPARATOR)) {
            return ResultBuilder.createInfoResult(
                    CliStrings.format(LuceneCliStrings.LUCENE_DESTROY_INDEX__MSG__REGION_CANNOT_BE_EMPTY));
        }

        if (indexName != null && StringUtils.isEmpty(indexName)) {
            return ResultBuilder.createInfoResult(
                    CliStrings.format(LuceneCliStrings.LUCENE_DESTROY_INDEX__MSG__INDEX_CANNOT_BE_EMPTY));
        }

        getSecurityService().authorize(Resource.CLUSTER, Operation.MANAGE, LucenePermission.TARGET);

        Result result;
        try {
            List<CliFunctionResult> accumulatedResults = new ArrayList<>();
            final XmlEntity xmlEntity = executeDestroyIndexFunction(accumulatedResults, indexName, regionPath);
            result = getDestroyIndexResult(accumulatedResults, indexName, regionPath);
            if (xmlEntity != null) {
                persistClusterConfiguration(result, () -> {
                    // Delete the xml entity to remove the index(es) in all groups
                    getSharedConfiguration().deleteXmlEntity(xmlEntity, null);
                });
            }
        } catch (FunctionInvocationTargetException ignore) {
            result = ResultBuilder.createGemFireErrorResult(CliStrings
                    .format(CliStrings.COULD_NOT_EXECUTE_COMMAND_TRY_AGAIN, LuceneCliStrings.LUCENE_DESTROY_INDEX));
        } catch (VirtualMachineError e) {
            SystemFailure.initiateFailure(e);
            throw e;
        } catch (IllegalArgumentException e) {
            result = ResultBuilder.createInfoResult(e.getMessage());
        } catch (Throwable t) {
            t.printStackTrace();
            SystemFailure.checkFailure();
            getCache().getLogger().warning(LuceneCliStrings.LUCENE_DESTROY_INDEX__EXCEPTION_MESSAGE, t);
            result = ResultBuilder.createGemFireErrorResult(t.getMessage());
        }
        return result;
    }

    private XmlEntity executeDestroyIndexFunction(List<CliFunctionResult> accumulatedResults, String indexName,
            String regionPath) {
        // Destroy has three cases:
        //
        // - no members define the region
        // In this case, send the request to all members to handle the case where the index has been
        // created, but not the region
        //
        // - all members define the region
        // In this case, send the request to one of the region members to destroy the index on all
        // member
        //
        // - some members define the region; some don't
        // In this case, send the request to one of the region members to destroy the index in all the
        // region members. Then send the function to the remaining members to handle the case where
        // the index has been created, but not the region
        XmlEntity xmlEntity = null;
        InternalCache cache = getCache();
        Set<DistributedMember> regionMembers = getRegionMembers(cache, regionPath);
        Set<DistributedMember> normalMembers = getNormalMembers(cache);
        LuceneDestroyIndexInfo indexInfo = new LuceneDestroyIndexInfo(indexName, regionPath);
        ResultCollector<?, ?> rc;
        if (regionMembers.isEmpty()) {
            // Attempt to destroy the proxy index on all members
            indexInfo.setDefinedDestroyOnly(true);
            rc = executeFunction(destroyIndexFunction, indexInfo, normalMembers);
            accumulatedResults.addAll((List<CliFunctionResult>) rc.getResult());
        } else {
            // Attempt to destroy the index on a region member
            indexInfo.setDefinedDestroyOnly(false);
            Set<DistributedMember> singleMember = new HashSet<>();
            singleMember.add(regionMembers.iterator().next());
            rc = executeFunction(destroyIndexFunction, indexInfo, singleMember);
            List<CliFunctionResult> cliFunctionResults = (List<CliFunctionResult>) rc.getResult();
            CliFunctionResult cliFunctionResult = cliFunctionResults.get(0);
            xmlEntity = cliFunctionResult.getXmlEntity();
            for (DistributedMember regionMember : regionMembers) {
                accumulatedResults.add(new CliFunctionResult(regionMember.getId(), cliFunctionResult.isSuccessful(),
                        cliFunctionResult.getMessage()));
            }
            // If that succeeds, destroy the proxy index(es) on all other members if necessary
            if (cliFunctionResult.isSuccessful()) {
                normalMembers.removeAll(regionMembers);
                if (!normalMembers.isEmpty()) {
                    indexInfo.setDefinedDestroyOnly(true);
                    rc = executeFunction(destroyIndexFunction, indexInfo, normalMembers);
                    accumulatedResults.addAll((List<CliFunctionResult>) rc.getResult());
                }
            } else {
                // @todo Should dummy results be added to the accumulatedResults for the non-region
                // members in the failed case
            }
        }
        return xmlEntity;
    }

    protected Set<DistributedMember> getRegionMembers(InternalCache cache, String regionPath) {
        return CliUtil.getMembersForeRegionViaFunction(cache, regionPath, true);
    }

    protected Set<DistributedMember> getNormalMembers(InternalCache cache) {
        return CliUtil.getAllNormalMembers(cache);
    }

    private Result getDestroyIndexResult(List<CliFunctionResult> cliFunctionResults, String indexName,
            String regionPath) {
        final TabularResultData tabularResult = ResultBuilder.createTabularResultData();
        for (CliFunctionResult cliFunctionResult : cliFunctionResults) {
            tabularResult.accumulate("Member", cliFunctionResult.getMemberIdOrName());
            if (cliFunctionResult.isSuccessful()) {
                tabularResult.accumulate("Status", indexName == null ? CliStrings.format(
                        LuceneCliStrings.LUCENE_DESTROY_INDEX__MSG__SUCCESSFULLY_DESTROYED_INDEXES_FROM_REGION_0,
                        new Object[] { regionPath })
                        : CliStrings.format(
                                LuceneCliStrings.LUCENE_DESTROY_INDEX__MSG__SUCCESSFULLY_DESTROYED_INDEX_0_FROM_REGION_1,
                                new Object[] { indexName, regionPath }));
            } else {
                tabularResult.accumulate("Status", cliFunctionResult.getMessage());
            }
        }
        return ResultBuilder.buildResult(tabularResult);
    }

    private Result displayResults(int pageSize, boolean keysOnly) throws Exception {
        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, keysOnly);
                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 Exception {
        final String[] groups = {};
        final ResultCollector<?, ?> rc = this.executeSearch(queryInfo);
        final List<Set<LuceneSearchResults>> functionResults = (List<Set<LuceneSearchResults>>) rc.getResult();

        return functionResults.stream().flatMap(Collection::stream).sorted().collect(Collectors.toList());
    }

    private Result getResults(int fromIndex, int toIndex, boolean keysonly) throws Exception {
        final TabularResultData data = ResultBuilder.createTabularResultData();
        for (int i = fromIndex; i < toIndex; i++) {
            if (!searchResults.get(i).getExceptionFlag()) {
                data.accumulate("key", searchResults.get(i).getKey());
                if (!keysonly) {
                    data.accumulate("value", searchResults.get(i).getValue());
                    data.accumulate("score", searchResults.get(i).getScore());
                }
            } else {
                throw new Exception(searchResults.get(i).getExceptionMessage());
            }
        }
        return ResultBuilder.buildResult(data);
    }

    protected ResultCollector<?, ?> executeFunctionOnAllMembers(Function function,
            final LuceneFunctionSerializable functionArguments)
            throws IllegalArgumentException, CommandResultException {
        Set<DistributedMember> targetMembers = CliUtil.getAllNormalMembers(getCache());
        return executeFunction(function, functionArguments, targetMembers);
    }

    protected ResultCollector<?, ?> executeSearch(final LuceneQueryInfo queryInfo) throws Exception {
        return executeFunctionOnRegion(searchIndexFunction, queryInfo, false);
    }

    protected ResultCollector<?, ?> executeFunctionOnRegion(Function function,
            LuceneFunctionSerializable functionArguments, boolean returnAllMembers) {
        Set<DistributedMember> targetMembers = CliUtil.getMembersForeRegionViaFunction(getCache(),
                functionArguments.getRegionPath(), returnAllMembers);
        if (targetMembers.isEmpty()) {
            throw new IllegalArgumentException(CliStrings.format(
                    LuceneCliStrings.LUCENE_DESTROY_INDEX__MSG__COULDNOT_FIND_MEMBERS_FOR_REGION_0,
                    new Object[] { functionArguments.getRegionPath() }));
        }
        return executeFunction(function, functionArguments, targetMembers);
    }

    protected ResultCollector<?, ?> executeFunction(Function function, LuceneFunctionSerializable functionArguments,
            Set<DistributedMember> targetMembers) {
        return CliUtil.executeFunction(function, functionArguments, targetMembers);
    }

    @CliAvailabilityIndicator({ LuceneCliStrings.LUCENE_SEARCH_INDEX, LuceneCliStrings.LUCENE_CREATE_INDEX,
            LuceneCliStrings.LUCENE_DESCRIBE_INDEX, LuceneCliStrings.LUCENE_LIST_INDEX,
            LuceneCliStrings.LUCENE_DESTROY_INDEX })
    public boolean indexCommandsAvailable() {
        Gfsh gfsh = Gfsh.getCurrentInstance();

        // command should always be available on the server
        if (gfsh == null) {
            return true;
        }

        // if in gfshVM, only when gfsh is connected and ready
        return gfsh.isConnectedAndReady();
    }
}