/* * 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 org.apache.geode.SystemFailure; import org.apache.geode.cache.Cache; import org.apache.geode.cache.execute.Execution; import org.apache.geode.cache.execute.FunctionAdapter; 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.LuceneListIndexFunction; import org.apache.geode.cache.lucene.internal.cli.functions.LuceneSearchIndexFunction; import org.apache.geode.distributed.DistributedMember; import org.apache.geode.internal.cache.execute.AbstractExecution; import org.apache.geode.internal.security.IntegratedSecurityService; import org.apache.geode.internal.security.SecurityService; 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.commands.AbstractCommandsSupport; 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.management.internal.security.ResourceOperation; import org.apache.geode.security.ResourcePermission.Operation; import org.apache.geode.security.ResourcePermission.Resource; import org.springframework.shell.core.annotation.CliAvailabilityIndicator; import org.springframework.shell.core.annotation.CliCommand; import org.springframework.shell.core.annotation.CliOption; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; /** * The LuceneIndexCommands class encapsulates all Geode shell (Gfsh) commands related to Lucene * indexes defined in Geode. * </p> * * @see AbstractCommandsSupport * @see LuceneIndexDetails * @see LuceneListIndexFunction */ @SuppressWarnings("unused") 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; private SecurityService securityService = IntegratedSecurityService.getSecurityService(); @CliCommand(value = LuceneCliStrings.LUCENE_LIST_INDEX, help = LuceneCliStrings.LUCENE_LIST_INDEX__HELP) @CliMetaData(shellOnly = false, relatedTopic = {CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA}) @ResourceOperation(resource = Resource.CLUSTER, operation = Operation.READ) public Result listIndex(@CliOption(key = LuceneCliStrings.LUCENE_LIST_INDEX__STATS, mandatory = false, specifiedDefaultValue = "true", unspecifiedDefaultValue = "false", help = LuceneCliStrings.LUCENE_LIST_INDEX__STATS__HELP) final boolean stats) { 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(set -> set.stream()).sorted().collect(Collectors.toList()); LinkedHashSet<LuceneIndexDetails> uniqResults = new LinkedHashSet<LuceneIndexDetails>(); 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() == true ? "Initialized" : "Defined"); if (stats == true) { 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); } } @CliCommand(value = LuceneCliStrings.LUCENE_CREATE_INDEX, help = LuceneCliStrings.LUCENE_CREATE_INDEX__HELP) @CliMetaData(shellOnly = false, 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.REGIONPATH, 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) @CliMetaData( valueSeparator = ",") final String[] fields, @CliOption(key = LuceneCliStrings.LUCENE_CREATE_INDEX__ANALYZER, mandatory = false, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = LuceneCliStrings.LUCENE_CREATE_INDEX__ANALYZER_HELP) @CliMetaData( valueSeparator = ",") final String[] analyzers, @CliOption(key = LuceneCliStrings.LUCENE_CREATE_INDEX__GROUP, optionContext = ConverterHint.MEMBERGROUP, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = LuceneCliStrings.LUCENE_CREATE_INDEX__GROUP__HELP) @CliMetaData( valueSeparator = ",") final String[] groups) { Result result = null; XmlEntity xmlEntity = null; this.securityService.authorizeRegionManage(regionPath); try { final Cache cache = getCache(); LuceneIndexInfo indexInfo = new LuceneIndexInfo(indexName, regionPath, fields, analyzers); final ResultCollector<?, ?> rc = this.executeFunctionOnGroups(createIndexFunction, groups, 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 (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(shellOnly = false, relatedTopic = {CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA}) @ResourceOperation(resource = Resource.CLUSTER, operation = Operation.READ) 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.REGIONPATH, help = LuceneCliStrings.LUCENE_DESCRIBE_INDEX__REGION_HELP) final String regionPath) { 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 { this.securityService.authorizeRegionManage(indexInfo.getRegionPath()); final ResultCollector<?, ?> rc = this.executeFunctionOnGroups(describeIndexFunction, new String[] {}, indexInfo); final List<LuceneIndexDetails> funcResults = (List<LuceneIndexDetails>) rc.getResult(); return funcResults.stream().filter(indexDetails -> indexDetails != null) .collect(Collectors.toList()); } @CliCommand(value = LuceneCliStrings.LUCENE_SEARCH_INDEX, help = LuceneCliStrings.LUCENE_SEARCH_INDEX__HELP) @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, help = LuceneCliStrings.LUCENE_SEARCH_INDEX__NAME__HELP) final String indexName, @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, 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, 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, @CliOption(key = LuceneCliStrings.LUCENE_SEARCH_INDEX__KEYSONLY, mandatory = false, unspecifiedDefaultValue = "false", help = LuceneCliStrings.LUCENE_SEARCH_INDEX__KEYSONLY__HELP) boolean keysOnly) { try { LuceneQueryInfo queryInfo = new LuceneQueryInfo(indexName, regionPath, queryString, defaultField, limit, keysOnly); if (pageSize == -1) { 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 (Throwable t) { SystemFailure.checkFailure(); getCache().getLogger().info(t); return ResultBuilder.createGemFireErrorResult(String .format(LuceneCliStrings.LUCENE_SEARCH_INDEX__ERROR_MESSAGE, toString(t, isDebugging()))); } } 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 { securityService.authorizeRegionManage(queryInfo.getRegionPath()); final String[] groups = {}; final ResultCollector<?, ?> rc = this.executeSearch(queryInfo); final List<Set<LuceneSearchResults>> functionResults = (List<Set<LuceneSearchResults>>) rc.getResult(); return functionResults.stream().flatMap(set -> set.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).getExeptionFlag()) { 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<?, ?> executeFunctionOnGroups(FunctionAdapter function, String[] groups, final LuceneIndexInfo indexInfo) throws IllegalArgumentException, CommandResultException { final Set<DistributedMember> targetMembers; if (function != createIndexFunction) { targetMembers = CliUtil.getMembersForeRegionViaFunction(getCache(), indexInfo.getRegionPath(), true); if (targetMembers.isEmpty()) { throw new IllegalArgumentException("Region not found."); } } else { targetMembers = CliUtil.findMembersOrThrow(groups, null); } return CliUtil.executeFunction(function, indexInfo, targetMembers); } protected ResultCollector<?, ?> executeSearch(final LuceneQueryInfo queryInfo) throws Exception { final Set<DistributedMember> targetMembers = CliUtil.getMembersForeRegionViaFunction(getCache(), queryInfo.getRegionPath(), false); if (targetMembers.isEmpty()) throw new IllegalArgumentException("Region not found."); return CliUtil.executeFunction(searchIndexFunction, queryInfo, targetMembers); } @CliAvailabilityIndicator({LuceneCliStrings.LUCENE_SEARCH_INDEX, LuceneCliStrings.LUCENE_CREATE_INDEX, LuceneCliStrings.LUCENE_DESCRIBE_INDEX, LuceneCliStrings.LUCENE_LIST_INDEX}) public boolean indexCommandsAvailable() { return (!CliUtil.isGfshVM() || (getGfsh() != null && getGfsh().isConnectedAndReady())); } }