This is an automated email from the ASF dual-hosted git repository. jensdeppe pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/geode.git
The following commit(s) were added to refs/heads/develop by this push: new 8bcb04e GEODE-5971 Refactor Lucene Index commands to support ResultModel and GfshCommand (#3323) 8bcb04e is described below commit 8bcb04ef40377a317c688b43102bc60645a8440d Author: Jens Deppe <jde...@pivotal.io> AuthorDate: Tue Mar 26 11:15:24 2019 -0700 GEODE-5971 Refactor Lucene Index commands to support ResultModel and GfshCommand (#3323) --- .../geode/test/dunit/internal/ProcessManager.java | 1 + .../lucene/internal/cli/LuceneCommandBase.java | 114 +++++ .../internal/cli/LuceneCreateIndexCommand.java | 127 ++++++ .../internal/cli/LuceneDescribeIndexCommand.java | 53 +++ .../internal/cli/LuceneDestroyIndexCommand.java | 133 ++++++ .../lucene/internal/cli/LuceneIndexCommands.java | 487 --------------------- .../internal/cli/LuceneListIndexCommand.java | 80 ++++ .../internal/cli/LuceneSearchIndexCommand.java | 207 +++++++++ .../lucene/internal/cli/LuceneSearchResults.java | 2 +- .../org.springframework.shell.core.CommandMarker | 6 +- .../internal/cli/LuceneIndexCommandsJUnitTest.java | 317 +++++++++----- 11 files changed, 919 insertions(+), 608 deletions(-) diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/ProcessManager.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/ProcessManager.java index c0e1237..4401497 100755 --- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/ProcessManager.java +++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/internal/ProcessManager.java @@ -235,6 +235,7 @@ class ProcessManager implements ChildVMLauncher { // remove current-version product classes and resources from the classpath dunitClasspath = removeModulesFromPath(dunitClasspath, "geode-core", "geode-cq", "geode-common", + "geode-json", "geode-lucene", "geode-wan"); classPath = versionManager.getClasspath(version) + File.pathSeparator + dunitClasspath; } diff --git a/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneCommandBase.java b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneCommandBase.java new file mode 100644 index 0000000..bc9cc56 --- /dev/null +++ b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneCommandBase.java @@ -0,0 +1,114 @@ +/* + * 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.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.geode.cache.execute.Function; +import org.apache.geode.cache.execute.ResultCollector; +import org.apache.geode.cache.lucene.internal.cli.functions.LuceneDescribeIndexFunction; +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.internal.cache.InternalCache; +import org.apache.geode.management.cli.GfshCommand; +import org.apache.geode.management.internal.cli.CliUtil; +import org.apache.geode.management.internal.cli.exceptions.UserErrorException; +import org.apache.geode.management.internal.cli.i18n.CliStrings; +import org.apache.geode.management.internal.cli.result.model.ResultModel; +import org.apache.geode.management.internal.cli.result.model.TabularResultModel; +import org.apache.geode.management.internal.cli.shell.Gfsh; + +public abstract class LuceneCommandBase extends GfshCommand { + + private static final LuceneDescribeIndexFunction describeIndexFunction = + new LuceneDescribeIndexFunction(); + + protected ResultModel toTabularResult(final List<LuceneIndexDetails> indexDetailsList, + boolean stats) { + if (!indexDetailsList.isEmpty()) { + ResultModel result = new ResultModel(); + TabularResultModel indexData = result.addTable("lucene-indexes"); + + 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("Serializer", indexDetails.getSerializerString()); + indexData.accumulate("Status", indexDetails.getStatus().toString()); + + if (stats) { + LuceneIndexStatus luceneIndexStatus = indexDetails.getStatus(); + if (luceneIndexStatus == LuceneIndexStatus.NOT_INITIALIZED + || luceneIndexStatus == LuceneIndexStatus.INDEXING_IN_PROGRESS) { + 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").toString()); + indexData.accumulate("Updates", + indexDetails.getIndexStats().get("updates").toString()); + indexData.accumulate("Commits", + indexDetails.getIndexStats().get("commits").toString()); + indexData.accumulate("Documents", + indexDetails.getIndexStats().get("documents").toString()); + } + } + } + return result; + } else { + return ResultModel + .createInfo(LuceneCliStrings.LUCENE_LIST_INDEX__INDEXES_NOT_FOUND_MESSAGE); + } + } + + @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()); + } + + protected ResultCollector<?, ?> executeFunctionOnRegion(Function function, + LuceneFunctionSerializable functionArguments, boolean returnAllMembers) { + Set<DistributedMember> targetMembers = CliUtil.getRegionAssociatedMembers( + functionArguments.getRegionPath(), (InternalCache) getCache(), returnAllMembers); + if (targetMembers.isEmpty()) { + throw new UserErrorException(CliStrings.format( + LuceneCliStrings.LUCENE_DESTROY_INDEX__MSG__COULDNOT_FIND_MEMBERS_FOR_REGION_0, + new Object[] {functionArguments.getRegionPath()})); + } + return executeFunction(function, functionArguments, targetMembers); + } + + protected 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(); + } +} diff --git a/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneCreateIndexCommand.java b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneCreateIndexCommand.java new file mode 100755 index 0000000..b1d307d --- /dev/null +++ b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneCreateIndexCommand.java @@ -0,0 +1,127 @@ +/* + * 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.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +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.cache.execute.Function; +import org.apache.geode.cache.execute.ResultCollector; +import org.apache.geode.cache.lucene.internal.cli.functions.LuceneCreateIndexFunction; +import org.apache.geode.cache.lucene.internal.security.LucenePermission; +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.distributed.internal.InternalConfigurationPersistenceService; +import org.apache.geode.internal.cache.InternalCache; +import org.apache.geode.management.cli.CliMetaData; +import org.apache.geode.management.cli.ConverterHint; +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.remote.CommandExecutor; +import org.apache.geode.management.internal.cli.result.CommandResultException; +import org.apache.geode.management.internal.cli.result.model.ResultModel; +import org.apache.geode.management.internal.cli.result.model.TabularResultModel; +import org.apache.geode.management.internal.configuration.domain.XmlEntity; +import org.apache.geode.security.ResourcePermission.Operation; +import org.apache.geode.security.ResourcePermission.Resource; + +@SuppressWarnings("unused") +public class LuceneCreateIndexCommand extends LuceneCommandBase { + + private static final LuceneCreateIndexFunction createIndexFunction = + new LuceneCreateIndexFunction(); + + /** + * 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 ResultModel 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, + @CliOption(key = LuceneCliStrings.LUCENE_CREATE_INDEX__SERIALIZER, + help = LuceneCliStrings.LUCENE_CREATE_INDEX__SERIALIZER_HELP) final String serializer) + throws CommandResultException { + + // Every lucene index potentially writes to disk. + authorize(Resource.CLUSTER, Operation.MANAGE, LucenePermission.TARGET); + + final InternalCache cache = (InternalCache) 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, serializer); + + final ResultCollector<?, ?> rc = + this.executeFunctionOnAllMembers(createIndexFunction, indexInfo); + final List<CliFunctionResult> funcResults = (List<CliFunctionResult>) rc.getResult(); + final XmlEntity xmlEntity = funcResults.stream().filter(CliFunctionResult::isSuccessful) + .map(CliFunctionResult::getXmlEntity).filter(Objects::nonNull).findFirst().orElse(null); + final ResultModel result = new ResultModel(); + final TabularResultModel tabularResult = result.addTable("lucene-indexes"); + for (final CliFunctionResult cliFunctionResult : funcResults) { + tabularResult.accumulate("Member", cliFunctionResult.getMemberIdOrName()); + + if (cliFunctionResult.isSuccessful()) { + tabularResult.accumulate("Status", "Successfully created lucene index"); + } else { + tabularResult.accumulate("Status", "Failed: " + cliFunctionResult.getMessage()); + } + } + + // if at least one member returns with successful deletion, we will need to update cc + InternalConfigurationPersistenceService configurationPersistenceService = + getConfigurationPersistenceService(); + if (xmlEntity != null) { + if (configurationPersistenceService == null) { + result.addInfo().addLine(CommandExecutor.SERVICE_NOT_RUNNING_CHANGE_NOT_PERSISTED); + } else { + configurationPersistenceService.addXmlEntity(xmlEntity, null); + } + } + + return result; + } + + protected ResultCollector<?, ?> executeFunctionOnAllMembers(Function function, + final LuceneFunctionSerializable functionArguments) + throws IllegalArgumentException { + Set<DistributedMember> targetMembers = getAllNormalMembers(); + return executeFunction(function, functionArguments, targetMembers); + } + + @CliAvailabilityIndicator(LuceneCliStrings.LUCENE_CREATE_INDEX) + public boolean indexCommandsAvailable() { + return super.indexCommandsAvailable(); + } +} diff --git a/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneDescribeIndexCommand.java b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneDescribeIndexCommand.java new file mode 100644 index 0000000..59278ab --- /dev/null +++ b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneDescribeIndexCommand.java @@ -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. + */ + +package org.apache.geode.cache.lucene.internal.cli; + +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.cache.lucene.internal.security.LucenePermission; +import org.apache.geode.management.cli.CliMetaData; +import org.apache.geode.management.cli.ConverterHint; +import org.apache.geode.management.internal.cli.i18n.CliStrings; +import org.apache.geode.management.internal.cli.result.model.ResultModel; +import org.apache.geode.security.ResourcePermission; + +public class LuceneDescribeIndexCommand extends LuceneCommandBase { + + @CliCommand(value = LuceneCliStrings.LUCENE_DESCRIBE_INDEX, + help = LuceneCliStrings.LUCENE_DESCRIBE_INDEX__HELP) + @CliMetaData(relatedTopic = {CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA}) + public ResultModel 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) + throws Exception { + + authorize(ResourcePermission.Resource.CLUSTER, ResourcePermission.Operation.READ, + LucenePermission.TARGET); + LuceneIndexInfo indexInfo = new LuceneIndexInfo(indexName, regionPath); + return toTabularResult(getIndexDetails(indexInfo), true); + } + + @CliAvailabilityIndicator(LuceneCliStrings.LUCENE_DESCRIBE_INDEX) + public boolean indexCommandsAvailable() { + return super.indexCommandsAvailable(); + } +} diff --git a/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneDestroyIndexCommand.java b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneDestroyIndexCommand.java new file mode 100644 index 0000000..2fae4c1 --- /dev/null +++ b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneDestroyIndexCommand.java @@ -0,0 +1,133 @@ +/* + * 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.List; +import java.util.Objects; +import java.util.Set; + +import org.apache.commons.lang3.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.cache.execute.ResultCollector; +import org.apache.geode.cache.lucene.internal.cli.functions.LuceneDestroyIndexFunction; +import org.apache.geode.cache.lucene.internal.security.LucenePermission; +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.distributed.internal.InternalConfigurationPersistenceService; +import org.apache.geode.internal.Version; +import org.apache.geode.management.cli.CliMetaData; +import org.apache.geode.management.cli.ConverterHint; +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.remote.CommandExecutor; +import org.apache.geode.management.internal.cli.result.model.ResultModel; +import org.apache.geode.management.internal.cli.result.model.TabularResultModel; +import org.apache.geode.management.internal.configuration.domain.XmlEntity; +import org.apache.geode.security.ResourcePermission; + +public class LuceneDestroyIndexCommand extends LuceneCommandBase { + + private static final LuceneDestroyIndexFunction destroyIndexFunction = + new LuceneDestroyIndexFunction(); + + @CliCommand(value = LuceneCliStrings.LUCENE_DESTROY_INDEX, + help = LuceneCliStrings.LUCENE_DESTROY_INDEX__HELP) + @CliMetaData(relatedTopic = {CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA}) + public ResultModel 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 (indexName != null && StringUtils.isEmpty(indexName)) { + return ResultModel.createInfo( + CliStrings.format(LuceneCliStrings.LUCENE_DESTROY_INDEX__MSG__INDEX_CANNOT_BE_EMPTY)); + } + + authorize(ResourcePermission.Resource.CLUSTER, ResourcePermission.Operation.MANAGE, + LucenePermission.TARGET); + + // Get members >= Geode 1.8 (when the new destroy code path went into the product) + Set<DistributedMember> validVersionMembers = + getNormalMembersWithSameOrNewerVersion(Version.GEODE_1_7_0); + if (validVersionMembers.isEmpty()) { + return ResultModel.createInfo(CliStrings.format( + LuceneCliStrings.LUCENE_DESTROY_INDEX__MSG__COULD_NOT_FIND__MEMBERS_GREATER_THAN_VERSION_0, + Version.GEODE_1_7_0)); + } + + // Execute the destroy index function + LuceneDestroyIndexInfo indexInfo = new LuceneDestroyIndexInfo(indexName, regionPath); + ResultCollector<?, ?> rc = + executeFunction(destroyIndexFunction, indexInfo, validVersionMembers); + + // Get the result + List<CliFunctionResult> cliFunctionResults = (List<CliFunctionResult>) rc.getResult(); + ResultModel result = getDestroyIndexResult(cliFunctionResults, indexName, regionPath); + + // Get and process the xml entity + XmlEntity xmlEntity = findXmlEntity(cliFunctionResults); + + // if at least one member returns with successful deletion, we will need to update cc + InternalConfigurationPersistenceService configurationPersistenceService = + getConfigurationPersistenceService(); + if (xmlEntity != null) { + if (configurationPersistenceService == null) { + result.addInfo().addLine(CommandExecutor.SERVICE_NOT_RUNNING_CHANGE_NOT_PERSISTED); + } else { + configurationPersistenceService.deleteXmlEntity(xmlEntity, null); + } + } + + return result; + } + + private ResultModel getDestroyIndexResult(List<CliFunctionResult> cliFunctionResults, + String indexName, String regionPath) { + + ResultModel result = new ResultModel(); + TabularResultModel tabularResult = result.addTable("lucene-indexes"); + 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 result; + } + + private XmlEntity findXmlEntity(List<CliFunctionResult> functionResults) { + return functionResults.stream().filter(CliFunctionResult::isSuccessful) + .map(CliFunctionResult::getXmlEntity).filter(Objects::nonNull).findFirst().orElse(null); + } + + @CliAvailabilityIndicator(LuceneCliStrings.LUCENE_DESTROY_INDEX) + public boolean indexCommandsAvailable() { + return super.indexCommandsAvailable(); + } +} diff --git a/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneIndexCommands.java b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneIndexCommands.java deleted file mode 100755 index 0e25672..0000000 --- a/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneIndexCommands.java +++ /dev/null @@ -1,487 +0,0 @@ -/* - * 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.Arrays; -import java.util.Collection; -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.lang3.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.cache.execute.Execution; -import org.apache.geode.cache.execute.Function; -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.distributed.internal.InternalConfigurationPersistenceService; -import org.apache.geode.internal.Version; -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.commands.InternalGfshCommand; -import org.apache.geode.management.internal.cli.exceptions.UserErrorException; -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.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 InternalGfshCommand - * @see LuceneIndexDetails - * @see LuceneListIndexFunction - */ -@SuppressWarnings("unused") -public class LuceneIndexCommands extends InternalGfshCommand { - 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) { - - authorize(Resource.CLUSTER, Operation.READ, LucenePermission.TARGET); - return toTabularResult(getIndexListing(), stats); - } - - @SuppressWarnings("unchecked") - protected List<LuceneIndexDetails> getIndexListing() { - final Execution functionExecutor = getMembersFunctionExecutor(getAllMembers()); - - 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("Serializer", indexDetails.getSerializerString()); - indexData.accumulate("Status", indexDetails.getStatus().toString()); - - if (stats) { - LuceneIndexStatus luceneIndexStatus = indexDetails.getStatus(); - if (luceneIndexStatus == LuceneIndexStatus.NOT_INITIALIZED - || luceneIndexStatus == LuceneIndexStatus.INDEXING_IN_PROGRESS) { - 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, - @CliOption(key = LuceneCliStrings.LUCENE_CREATE_INDEX__SERIALIZER, - help = LuceneCliStrings.LUCENE_CREATE_INDEX__SERIALIZER_HELP) final String serializer) - throws CommandResultException { - - Result result; - // Every lucene index potentially writes to disk. - authorize(Resource.CLUSTER, Operation.MANAGE, LucenePermission.TARGET); - - final InternalCache cache = (InternalCache) 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, serializer); - - final ResultCollector<?, ?> rc = - this.executeFunctionOnAllMembers(createIndexFunction, indexInfo); - final List<CliFunctionResult> funcResults = (List<CliFunctionResult>) rc.getResult(); - final XmlEntity xmlEntity = funcResults.stream().filter(CliFunctionResult::isSuccessful) - .map(CliFunctionResult::getXmlEntity).filter(Objects::nonNull).findFirst().orElse(null); - 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"); - } else { - tabularResult.accumulate("Status", "Failed: " + cliFunctionResult.getMessage()); - } - } - - result = ResultBuilder.buildResult(tabularResult); - if (xmlEntity != null) { - persistClusterConfiguration(result, () -> { - ((InternalConfigurationPersistenceService) getConfigurationPersistenceService()) - .addXmlEntity(xmlEntity, null); - }); - } - 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) - throws Exception { - - authorize(Resource.CLUSTER, Operation.READ, LucenePermission.TARGET); - LuceneIndexInfo indexInfo = new LuceneIndexInfo(indexName, regionPath); - return toTabularResult(getIndexDetails(indexInfo), true); - } - - @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) - throws Exception { - authorize(Resource.DATA, Operation.READ, regionPath); - LuceneQueryInfo queryInfo = - new LuceneQueryInfo(indexName, regionPath, queryString, defaultField, limit, keysOnly); - int pageSize = Integer.MAX_VALUE; - searchResults = getSearchResults(queryInfo); - return displayResults(pageSize, keysOnly); - - } - - @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 (indexName != null && StringUtils.isEmpty(indexName)) { - return ResultBuilder.createInfoResult( - CliStrings.format(LuceneCliStrings.LUCENE_DESTROY_INDEX__MSG__INDEX_CANNOT_BE_EMPTY)); - } - - authorize(Resource.CLUSTER, Operation.MANAGE, LucenePermission.TARGET); - - // Get members >= Geode 1.8 (when the new destroy code path went into the product) - Set<DistributedMember> validVersionMembers = - getNormalMembersWithSameOrNewerVersion(Version.GEODE_1_7_0); - if (validVersionMembers.isEmpty()) { - return ResultBuilder.createInfoResult(CliStrings.format( - LuceneCliStrings.LUCENE_DESTROY_INDEX__MSG__COULD_NOT_FIND__MEMBERS_GREATER_THAN_VERSION_0, - Version.GEODE_1_7_0)); - } - - // Execute the destroy index function - LuceneDestroyIndexInfo indexInfo = new LuceneDestroyIndexInfo(indexName, regionPath); - ResultCollector<?, ?> rc = - executeFunction(destroyIndexFunction, indexInfo, validVersionMembers); - - // Get the result - List<CliFunctionResult> cliFunctionResults = (List<CliFunctionResult>) rc.getResult(); - Result result = getDestroyIndexResult(cliFunctionResults, indexName, regionPath); - - // Get and process the xml entity - XmlEntity xmlEntity = findXmlEntity(cliFunctionResults); - if (xmlEntity != null) { - persistClusterConfiguration(result, () -> { - // Delete the xml entity to remove the index(es) in all groups - ((InternalConfigurationPersistenceService) getConfigurationPersistenceService()) - .deleteXmlEntity(xmlEntity, null); - }); - } - - return result; - } - - 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 UserErrorException(searchResults.get(i).getExceptionMessage()); - } - } - return ResultBuilder.buildResult(data); - } - - protected ResultCollector<?, ?> executeFunctionOnAllMembers(Function function, - final LuceneFunctionSerializable functionArguments) throws IllegalArgumentException { - Set<DistributedMember> targetMembers = getAllNormalMembers(); - 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.getRegionAssociatedMembers( - functionArguments.getRegionPath(), (InternalCache) getCache(), returnAllMembers); - if (targetMembers.isEmpty()) { - throw new UserErrorException(CliStrings.format( - LuceneCliStrings.LUCENE_DESTROY_INDEX__MSG__COULDNOT_FIND_MEMBERS_FOR_REGION_0, - new Object[] {functionArguments.getRegionPath()})); - } - return 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(); - } -} diff --git a/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneListIndexCommand.java b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneListIndexCommand.java new file mode 100644 index 0000000..800f3e1 --- /dev/null +++ b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneListIndexCommand.java @@ -0,0 +1,80 @@ +/* + * 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.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +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.cache.execute.Execution; +import org.apache.geode.cache.execute.ResultCollector; +import org.apache.geode.cache.lucene.internal.cli.functions.LuceneListIndexFunction; +import org.apache.geode.cache.lucene.internal.security.LucenePermission; +import org.apache.geode.internal.cache.execute.AbstractExecution; +import org.apache.geode.management.cli.CliMetaData; +import org.apache.geode.management.internal.cli.i18n.CliStrings; +import org.apache.geode.management.internal.cli.result.model.ResultModel; +import org.apache.geode.security.ResourcePermission; + +public class LuceneListIndexCommand extends LuceneCommandBase { + + @CliCommand(value = LuceneCliStrings.LUCENE_LIST_INDEX, + help = LuceneCliStrings.LUCENE_LIST_INDEX__HELP) + @CliMetaData(relatedTopic = {CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA}) + public ResultModel listIndex(@CliOption(key = LuceneCliStrings.LUCENE_LIST_INDEX__STATS, + specifiedDefaultValue = "true", unspecifiedDefaultValue = "false", + help = LuceneCliStrings.LUCENE_LIST_INDEX__STATS__HELP) final boolean stats) { + + authorize(ResourcePermission.Resource.CLUSTER, ResourcePermission.Operation.READ, + LucenePermission.TARGET); + return toTabularResult(getIndexListing(), stats); + } + + @SuppressWarnings("unchecked") + protected List<LuceneIndexDetails> getIndexListing() { + final Execution functionExecutor = getMembersFunctionExecutor(getAllMembers()); + + 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; + } + + @CliAvailabilityIndicator({ + LuceneCliStrings.LUCENE_LIST_INDEX, + }) + public boolean indexCommandsAvailable() { + return super.indexCommandsAvailable(); + } +} diff --git a/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneSearchIndexCommand.java b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneSearchIndexCommand.java new file mode 100644 index 0000000..0373e63 --- /dev/null +++ b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneSearchIndexCommand.java @@ -0,0 +1,207 @@ +/* + * 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.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +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.cache.execute.ResultCollector; +import org.apache.geode.cache.lucene.internal.cli.functions.LuceneSearchIndexFunction; +import org.apache.geode.management.cli.CliMetaData; +import org.apache.geode.management.cli.ConverterHint; +import org.apache.geode.management.internal.cli.exceptions.UserErrorException; +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.ModelCommandResult; +import org.apache.geode.management.internal.cli.result.model.ResultModel; +import org.apache.geode.management.internal.cli.result.model.TabularResultModel; +import org.apache.geode.management.internal.cli.shell.Gfsh; +import org.apache.geode.security.ResourcePermission; + +public class LuceneSearchIndexCommand extends LuceneCommandBase { + + private static final LuceneSearchIndexFunction searchIndexFunction = + new LuceneSearchIndexFunction(); + + private List<LuceneSearchResults> searchResults = null; + + /** + * 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 ResultModel 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) + throws Exception { + authorize(ResourcePermission.Resource.DATA, ResourcePermission.Operation.READ, regionPath); + LuceneQueryInfo queryInfo = + new LuceneQueryInfo(indexName, regionPath, queryString, defaultField, limit, keysOnly); + searchResults = getSearchResults(queryInfo); + + return displayResults(getPageSize(), keysOnly); + } + + protected int getPageSize() { + return Integer.MAX_VALUE; + } + + private List<LuceneSearchResults> getSearchResults(final LuceneQueryInfo queryInfo) + throws Exception { + 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()); + } + + protected ResultCollector<?, ?> executeSearch(final LuceneQueryInfo queryInfo) throws Exception { + return executeFunctionOnRegion(searchIndexFunction, queryInfo, false); + } + + private ResultModel getResults(int fromIndex, int toIndex, boolean keysonly) throws Exception { + ResultModel result = new ResultModel(); + TabularResultModel table = result.addTable("lucene-indexes"); + for (int i = fromIndex; i < toIndex; i++) { + if (!searchResults.get(i).getExceptionFlag()) { + table.accumulate("key", searchResults.get(i).getKey()); + if (!keysonly) { + table.accumulate("value", searchResults.get(i).getValue()); + table.accumulate("score", Float.toString(searchResults.get(i).getScore())); + } + } else { + throw new UserErrorException(searchResults.get(i).getExceptionMessage()); + } + } + return result; + } + + protected Gfsh initGfsh() { + return Gfsh.getCurrentInstance(); + } + + private ResultModel displayResults(int pageSize, boolean keysOnly) throws Exception { + if (searchResults.size() == 0) { + return ResultModel.createInfo(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) { + ResultModel resultModel = getResults(fromIndex, toIndex, keysOnly); + if (!pagination) { + return resultModel; + } + Gfsh.println(); + + CommandResult commandResult = new ModelCommandResult(resultModel); + 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++; + 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 ResultModel.createInfo("Search complete."); + default: + Gfsh.println("Invalid option"); + break; + } + } while (true); + } + + + @CliAvailabilityIndicator(LuceneCliStrings.LUCENE_SEARCH_INDEX) + public boolean indexCommandsAvailable() { + return super.indexCommandsAvailable(); + } +} diff --git a/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneSearchResults.java b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneSearchResults.java index 95167d5..0411c6b 100644 --- a/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneSearchResults.java +++ b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/internal/cli/LuceneSearchResults.java @@ -59,7 +59,7 @@ public class LuceneSearchResults<K, V> implements Comparable<LuceneSearchResults // result. // This comparator is used to list results, order will be higher to lower on score. (Ref. // GEODE-3206) - return Float.compare(getScore(), searchResults.getScore()) * -1; + return Float.compare(searchResults.getScore(), getScore()); } public boolean getExceptionFlag() { diff --git a/geode-lucene/src/main/resources/META-INF/services/org.springframework.shell.core.CommandMarker b/geode-lucene/src/main/resources/META-INF/services/org.springframework.shell.core.CommandMarker index 0879e15..b7e5ba3 100644 --- a/geode-lucene/src/main/resources/META-INF/services/org.springframework.shell.core.CommandMarker +++ b/geode-lucene/src/main/resources/META-INF/services/org.springframework.shell.core.CommandMarker @@ -1,2 +1,6 @@ # Lucene Extensions command -org.apache.geode.cache.lucene.internal.cli.LuceneIndexCommands \ No newline at end of file +org.apache.geode.cache.lucene.internal.cli.LuceneCreateIndexCommand +org.apache.geode.cache.lucene.internal.cli.LuceneDescribeIndexCommand +org.apache.geode.cache.lucene.internal.cli.LuceneDestroyIndexCommand +org.apache.geode.cache.lucene.internal.cli.LuceneListIndexCommand +org.apache.geode.cache.lucene.internal.cli.LuceneSearchIndexCommand \ No newline at end of file diff --git a/geode-lucene/src/test/java/org/apache/geode/cache/lucene/internal/cli/LuceneIndexCommandsJUnitTest.java b/geode-lucene/src/test/java/org/apache/geode/cache/lucene/internal/cli/LuceneIndexCommandsJUnitTest.java index 2202d8c..88b21bd 100644 --- a/geode-lucene/src/test/java/org/apache/geode/cache/lucene/internal/cli/LuceneIndexCommandsJUnitTest.java +++ b/geode-lucene/src/test/java/org/apache/geode/cache/lucene/internal/cli/LuceneIndexCommandsJUnitTest.java @@ -14,8 +14,6 @@ */ package org.apache.geode.cache.lucene.internal.cli; -import static org.apache.commons.lang3.SystemUtils.LINE_SEPARATOR; -import static org.apache.geode.management.internal.cli.result.ResultData.TYPE_TABULAR; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.Mockito.any; @@ -43,7 +41,6 @@ import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.core.KeywordAnalyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -71,18 +68,20 @@ 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.ResultBuilder; import org.apache.geode.management.internal.cli.result.TabularResultData; +import org.apache.geode.management.internal.cli.result.model.ResultModel; +import org.apache.geode.management.internal.cli.result.model.TabularResultModel; import org.apache.geode.management.internal.cli.shell.Gfsh; import org.apache.geode.test.junit.categories.LuceneTest; /** * The LuceneIndexCommandsJUnitTest class is a test suite of test cases testing the contract and - * functionality of the LuceneIndexCommands class. + * functionality of the Lucene Index Commands. * - * @see LuceneIndexCommands - * @see LuceneIndexDetails - * @see LuceneListIndexFunction - * @see org.junit.Test - * @since GemFire 7.0 + * @see LuceneCreateIndexCommand + * @see LuceneDescribeIndexCommand + * @see LuceneDestroyIndexCommand + * @see LuceneListIndexCommand + * @see LuceneSearchIndexCommand */ @Category(LuceneTest.class) @RunWith(JUnitParamsRunner.class) @@ -125,21 +124,22 @@ public class LuceneIndexCommandsJUnitTest { .thenReturn(mockResultCollector); when(mockResultCollector.getResult()).thenReturn(results); - final LuceneIndexCommands commands = createIndexCommands(this.mockCache, mockFunctionExecutor); + final LuceneListIndexCommand command = + new LuceneTestListIndexCommand(this.mockCache, mockFunctionExecutor); - CommandResult result = (CommandResult) commands.listIndex(false); - TabularResultData data = (TabularResultData) result.getResultData(); - assertThat(data.retrieveAllValues("Index Name")) + ResultModel result = command.listIndex(false); + TabularResultModel data = result.getTableSection("lucene-indexes"); + assertThat(data.getValuesInColumn("Index Name")) .isEqualTo(Arrays.asList("memberFive", "memberSix", "memberTen")); - assertThat(data.retrieveAllValues("Region Path")) + assertThat(data.getValuesInColumn("Region Path")) .isEqualTo(Arrays.asList("/Employees", "/Employees", "/Employees")); - assertThat(data.retrieveAllValues("Indexed Fields")).isEqualTo(Arrays.asList( + assertThat(data.getValuesInColumn("Indexed Fields")).isEqualTo(Arrays.asList( "[field1, field2, field3]", "[field1, field2, field3]", "[field1, field2, field3]")); - assertThat(data.retrieveAllValues("Field Analyzer")) + assertThat(data.getValuesInColumn("Field Analyzer")) .isEqualTo(Arrays.asList("{field1=StandardAnalyzer, field2=KeywordAnalyzer}", "{field1=StandardAnalyzer, field2=KeywordAnalyzer}", "{field1=StandardAnalyzer, field2=KeywordAnalyzer}")); - assertThat(data.retrieveAllValues("Status")) + assertThat(data.getValuesInColumn("Status")) .isEqualTo(Arrays.asList("INITIALIZED", "NOT_INITIALIZED", "INITIALIZED")); } @@ -176,25 +176,26 @@ public class LuceneIndexCommandsJUnitTest { .thenReturn(mockResultCollector); when(mockResultCollector.getResult()).thenReturn(results); - final LuceneIndexCommands commands = createIndexCommands(this.mockCache, mockFunctionExecutor); + final LuceneListIndexCommand command = + new LuceneTestListIndexCommand(this.mockCache, mockFunctionExecutor); - CommandResult result = (CommandResult) commands.listIndex(true); - TabularResultData data = (TabularResultData) result.getResultData(); - assertThat(data.retrieveAllValues("Index Name")) + ResultModel result = command.listIndex(true); + TabularResultModel data = result.getTableSection("lucene-indexes"); + assertThat(data.getValuesInColumn("Index Name")) .isEqualTo(Arrays.asList("memberFive", "memberSix", "memberTen")); - assertThat(data.retrieveAllValues("Region Path")) + assertThat(data.getValuesInColumn("Region Path")) .isEqualTo(Arrays.asList("/Employees", "/Employees", "/Employees")); - assertThat(data.retrieveAllValues("Indexed Fields")).isEqualTo(Arrays.asList( + assertThat(data.getValuesInColumn("Indexed Fields")).isEqualTo(Arrays.asList( "[field1, field2, field3]", "[field1, field2, field3]", "[field1, field2, field3]")); - assertThat(data.retrieveAllValues("Field Analyzer")) + assertThat(data.getValuesInColumn("Field Analyzer")) .isEqualTo(Arrays.asList("{field1=StandardAnalyzer, field2=KeywordAnalyzer}", "{field1=StandardAnalyzer, field2=KeywordAnalyzer}", "{field1=StandardAnalyzer, field2=KeywordAnalyzer}")); - assertThat(data.retrieveAllValues("Query Executions")).isEqualTo(Arrays.asList("1", "2", "3")); - assertThat(data.retrieveAllValues("Commits")).isEqualTo(Arrays.asList("10", "20", "30")); - assertThat(data.retrieveAllValues("Updates")).isEqualTo(Arrays.asList("5", "10", "15")); - assertThat(data.retrieveAllValues("Documents")).isEqualTo(Arrays.asList("1", "2", "3")); - assertThat(data.retrieveAllValues("Serializer")) + assertThat(data.getValuesInColumn("Query Executions")).isEqualTo(Arrays.asList("1", "2", "3")); + assertThat(data.getValuesInColumn("Commits")).isEqualTo(Arrays.asList("10", "20", "30")); + assertThat(data.getValuesInColumn("Updates")).isEqualTo(Arrays.asList("5", "10", "15")); + assertThat(data.getValuesInColumn("Documents")).isEqualTo(Arrays.asList("1", "2", "3")); + assertThat(data.getValuesInColumn("Serializer")) .isEqualTo(Arrays.asList(HeterogeneousLuceneSerializer.class.getSimpleName(), HeterogeneousLuceneSerializer.class.getSimpleName(), HeterogeneousLuceneSerializer.class.getSimpleName())); @@ -203,7 +204,8 @@ public class LuceneIndexCommandsJUnitTest { @Test public void testCreateIndex() throws Exception { final ResultCollector mockResultCollector = mock(ResultCollector.class); - final LuceneIndexCommands commands = spy(createIndexCommands(this.mockCache, null)); + final LuceneCreateIndexCommand command = + spy(new LuceneTestCreateIndexCommand(this.mockCache, null)); final List<CliFunctionResult> cliFunctionResults = new ArrayList<>(); cliFunctionResults @@ -213,7 +215,7 @@ public class LuceneIndexCommandsJUnitTest { cliFunctionResults .add(new CliFunctionResult("member3", CliFunctionResult.StatusState.OK, "Index Created")); - doReturn(mockResultCollector).when(commands).executeFunctionOnAllMembers( + doReturn(mockResultCollector).when(command).executeFunctionOnAllMembers( any(LuceneCreateIndexFunction.class), any(LuceneIndexInfo.class)); doReturn(cliFunctionResults).when(mockResultCollector).getResult(); @@ -225,13 +227,13 @@ public class LuceneIndexCommandsJUnitTest { String serializer = PrimitiveSerializer.class.getCanonicalName(); - CommandResult result = (CommandResult) commands.createIndex(indexName, regionPath, - searchableFields, fieldAnalyzers, serializer); + ResultModel result = command.createIndex(indexName, regionPath, searchableFields, + fieldAnalyzers, serializer); assertThat(result.getStatus()).isEqualTo(Status.OK); - TabularResultData data = (TabularResultData) result.getResultData(); - assertThat(data.retrieveAllValues("Member")) + TabularResultModel data = result.getTableSection("lucene-indexes"); + assertThat(data.getValuesInColumn("Member")) .isEqualTo(Arrays.asList("member1", "member2", "member3")); - assertThat(data.retrieveAllValues("Status")) + assertThat(data.getValuesInColumn("Status")) .isEqualTo(Arrays.asList("Successfully created lucene index", "Failed: Index creation failed", "Successfully created lucene index")); } @@ -240,7 +242,8 @@ public class LuceneIndexCommandsJUnitTest { public void testDescribeIndex() throws Exception { final String serverName = "mockServer"; final ResultCollector mockResultCollector = mock(ResultCollector.class, "ResultCollector"); - final LuceneIndexCommands commands = spy(createIndexCommands(this.mockCache, null)); + final LuceneDescribeIndexCommand command = + spy(new LuceneTestDescribeIndexCommand(this.mockCache, null)); String[] searchableFields = {"field1", "field2", "field3"}; Map<String, Analyzer> fieldAnalyzers = new HashMap<>(); @@ -253,34 +256,35 @@ public class LuceneIndexCommandsJUnitTest { indexDetails.add(createIndexDetails("memberFive", "/Employees", searchableFields, fieldAnalyzers, mockIndexStats, LuceneIndexStatus.INITIALIZED, serverName, serializer)); - doReturn(mockResultCollector).when(commands).executeFunctionOnRegion( + doReturn(mockResultCollector).when(command).executeFunctionOnRegion( any(LuceneDescribeIndexFunction.class), any(LuceneIndexInfo.class), eq(true)); doReturn(indexDetails).when(mockResultCollector).getResult(); - CommandResult result = (CommandResult) commands.describeIndex("memberFive", "/Employees"); + ResultModel result = command.describeIndex("memberFive", "/Employees"); - TabularResultData data = (TabularResultData) result.getResultData(); - assertThat(data.retrieveAllValues("Index Name")) + TabularResultModel data = result.getTableSection("lucene-indexes"); + assertThat(data.getValuesInColumn("Index Name")) .isEqualTo(Collections.singletonList("memberFive")); - assertThat(data.retrieveAllValues("Region Path")) + assertThat(data.getValuesInColumn("Region Path")) .isEqualTo(Collections.singletonList("/Employees")); - assertThat(data.retrieveAllValues("Indexed Fields")) + assertThat(data.getValuesInColumn("Indexed Fields")) .isEqualTo(Collections.singletonList("[field1, field2, field3]")); - assertThat(data.retrieveAllValues("Field Analyzer")) + assertThat(data.getValuesInColumn("Field Analyzer")) .isEqualTo(Collections.singletonList("{field1=StandardAnalyzer, field2=KeywordAnalyzer}")); - assertThat(data.retrieveAllValues("Status")) + assertThat(data.getValuesInColumn("Status")) .isEqualTo(Collections.singletonList("INITIALIZED")); - assertThat(data.retrieveAllValues("Query Executions")) + assertThat(data.getValuesInColumn("Query Executions")) .isEqualTo(Collections.singletonList("1")); - assertThat(data.retrieveAllValues("Commits")).isEqualTo(Collections.singletonList("10")); - assertThat(data.retrieveAllValues("Updates")).isEqualTo(Collections.singletonList("5")); - assertThat(data.retrieveAllValues("Documents")).isEqualTo(Collections.singletonList("1")); + assertThat(data.getValuesInColumn("Commits")).isEqualTo(Collections.singletonList("10")); + assertThat(data.getValuesInColumn("Updates")).isEqualTo(Collections.singletonList("5")); + assertThat(data.getValuesInColumn("Documents")).isEqualTo(Collections.singletonList("1")); } @Test public void testSearchIndex() throws Exception { final ResultCollector mockResultCollector = mock(ResultCollector.class, "ResultCollector"); - final LuceneIndexCommands commands = spy(createIndexCommands(this.mockCache, null)); + final LuceneSearchIndexCommand command = + spy(new LuceneTestSearchIndexCommand(this.mockCache, null)); final List<Set<LuceneSearchResults>> queryResultsList = new ArrayList<>(); HashSet<LuceneSearchResults> queryResults = new HashSet<>(); @@ -288,23 +292,24 @@ public class LuceneIndexCommandsJUnitTest { queryResults.add(createQueryResults("B", "Result1", Float.valueOf("1.2"))); queryResults.add(createQueryResults("C", "Result1", Float.valueOf("1.1"))); queryResultsList.add(queryResults); - doReturn(mockResultCollector).when(commands).executeSearch(any(LuceneQueryInfo.class)); + doReturn(mockResultCollector).when(command).executeSearch(any(LuceneQueryInfo.class)); doReturn(queryResultsList).when(mockResultCollector).getResult(); - CommandResult result = - (CommandResult) commands.searchIndex("index", "region", "Result1", "field1", -1, false); - TabularResultData data = (TabularResultData) result.getResultData(); - assertThat(data.retrieveAllValues("key")).isEqualTo(Arrays.asList("A", "B", "C")); - assertThat(data.retrieveAllValues("value")) + ResultModel result = + command.searchIndex("index", "region", "Result1", "field1", -1, false); + TabularResultModel data = result.getTableSection("lucene-indexes"); + assertThat(data.getValuesInColumn("key")).isEqualTo(Arrays.asList("A", "B", "C")); + assertThat(data.getValuesInColumn("value")) .isEqualTo(Arrays.asList("Result1", "Result1", "Result1")); - assertThat(data.retrieveAllValues("score")).isEqualTo(Arrays.asList("1.3", "1.2", "1.1")); + assertThat(data.getValuesInColumn("score")).isEqualTo(Arrays.asList("1.3", "1.2", "1.1")); } - @Ignore + @Test public void testSearchIndexWithPaging() throws Exception { final Gfsh mockGfsh = mock(Gfsh.class); final ResultCollector mockResultCollector = mock(ResultCollector.class, "ResultCollector"); - final LuceneIndexCommands commands = spy(createIndexCommands(this.mockCache, null)); + final LuceneSearchIndexCommand command = + spy(new LuceneTestSearchIndexCommand(this.mockCache, null)); ArgumentCaptor<String> resultCaptor = ArgumentCaptor.forClass(String.class); LuceneSearchResults result1 = createQueryResults("A", "Result1", Float.valueOf("1.7")); @@ -317,21 +322,22 @@ public class LuceneIndexCommandsJUnitTest { final List<Set<LuceneSearchResults>> queryResultsList = getSearchResults(result1, result2, result3, result4, result5, result6, result7); - doReturn(mockResultCollector).when(commands).executeSearch(any(LuceneQueryInfo.class)); + doReturn(mockResultCollector).when(command).executeSearch(any(LuceneQueryInfo.class)); doReturn(queryResultsList).when(mockResultCollector).getResult(); - doReturn(mockGfsh).when(commands).initGfsh(); + doReturn(mockGfsh).when(command).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"); + when(command.getPageSize()).thenReturn(2); 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}); + String expectedPage1 = getPage(expectedResults, new int[] {6, 5}); + String expectedPage2 = getPage(expectedResults, new int[] {4, 3}); + String expectedPage3 = getPage(expectedResults, new int[] {2, 1}); + String expectedPage4 = getPage(expectedResults, new int[] {0}); - commands.searchIndex("index", "region", "Result1", "field1", -1, false); + command.searchIndex("index", "region", "Result1", "field1", -1, false); verify(mockGfsh, times(20)).printAsInfo(resultCaptor.capture()); List<String> actualPageResults = resultCaptor.getAllValues(); @@ -370,7 +376,8 @@ public class LuceneIndexCommandsJUnitTest { @Test public void testSearchIndexWithKeysOnly() throws Exception { final ResultCollector mockResultCollector = mock(ResultCollector.class, "ResultCollector"); - final LuceneIndexCommands commands = spy(createIndexCommands(this.mockCache, null)); + final LuceneSearchIndexCommand command = + spy(new LuceneTestSearchIndexCommand(this.mockCache, null)); final List<Set<LuceneSearchResults>> queryResultsList = new ArrayList<>(); HashSet<LuceneSearchResults> queryResults = new HashSet<>(); @@ -378,19 +385,19 @@ public class LuceneIndexCommandsJUnitTest { queryResults.add(createQueryResults("B", "Result1", Float.valueOf("1.2"))); queryResults.add(createQueryResults("C", "Result1", Float.valueOf("1.1"))); queryResultsList.add(queryResults); - doReturn(mockResultCollector).when(commands).executeSearch(any(LuceneQueryInfo.class)); + doReturn(mockResultCollector).when(command).executeSearch(any(LuceneQueryInfo.class)); doReturn(queryResultsList).when(mockResultCollector).getResult(); - CommandResult result = - (CommandResult) commands.searchIndex("index", "region", "Result1", "field1", -1, true); - TabularResultData data = (TabularResultData) result.getResultData(); - assertThat(data.retrieveAllValues("key")).isEqualTo(Arrays.asList("A", "B", "C")); + ResultModel result = command.searchIndex("index", "region", "Result1", "field1", -1, true); + TabularResultModel data = result.getTableSection("lucene-indexes"); + assertThat(data.getValuesInColumn("key")).isEqualTo(Arrays.asList("A", "B", "C")); } @Test public void testSearchIndexWhenSearchResultsHaveSameScore() throws Exception { final ResultCollector mockResultCollector = mock(ResultCollector.class, "ResultCollector"); - final LuceneIndexCommands commands = spy(createIndexCommands(this.mockCache, null)); + final LuceneSearchIndexCommand command = + spy(new LuceneTestSearchIndexCommand(this.mockCache, null)); final List<Set<LuceneSearchResults>> queryResultsList = new ArrayList<>(); HashSet<LuceneSearchResults> queryResults = new HashSet<>(); @@ -415,32 +422,34 @@ public class LuceneIndexCommandsJUnitTest { queryResults.add(createQueryResults("T", "Result1", 1)); queryResultsList.add(queryResults); - doReturn(mockResultCollector).when(commands).executeSearch(any(LuceneQueryInfo.class)); + doReturn(mockResultCollector).when(command).executeSearch(any(LuceneQueryInfo.class)); doReturn(queryResultsList).when(mockResultCollector).getResult(); - CommandResult result = - (CommandResult) commands.searchIndex("index", "region", "Result1", "field1", -1, true); - TabularResultData data = (TabularResultData) result.getResultData(); - assertThat(data.retrieveAllValues("key").size()).isEqualTo(queryResults.size()); + ResultModel result = command.searchIndex("index", "region", "Result1", "field1", -1, true); + TabularResultModel data = result.getTableSection("lucene-indexes"); + assertThat(data.getValuesInColumn("key").size()).isEqualTo(queryResults.size()); } @Test public void testDestroySingleIndexNoRegionMembers() { - LuceneIndexCommands commands = createTestLuceneIndexCommandsForDestroyIndex(); + LuceneDestroyIndexCommand command = + spy(new LuceneTestDestroyIndexCommand(mockCache, null)); final List<CliFunctionResult> cliFunctionResults = new ArrayList<>(); String expectedStatus = CliStrings.format( LuceneCliStrings.LUCENE_DESTROY_INDEX__MSG__COULD_NOT_FIND__MEMBERS_GREATER_THAN_VERSION_0, - new Object[] {Version.GEODE_1_7_0}) + LINE_SEPARATOR; + new Object[] {Version.GEODE_1_7_0}); cliFunctionResults.add(new CliFunctionResult("member0", CliFunctionResult.StatusState.OK)); - doReturn(Collections.emptySet()).when(commands).getNormalMembersWithSameOrNewerVersion(any()); - CommandResult result = (CommandResult) commands.destroyIndex("index", "regionPath"); + doReturn(Collections.emptySet()).when(command).getNormalMembersWithSameOrNewerVersion(any()); + + ResultModel result = command.destroyIndex("index", "regionPath"); verifyDestroyIndexCommandResult(result, cliFunctionResults, expectedStatus); } @Test @Parameters({"true", "false"}) public void testDestroySingleIndexWithRegionMembers(boolean expectedToSucceed) { - LuceneIndexCommands commands = createTestLuceneIndexCommandsForDestroyIndex(); + LuceneDestroyIndexCommand command = + spy(new LuceneTestDestroyIndexCommand(mockCache, null)); String indexName = "index"; String regionPath = "regionPath"; @@ -464,33 +473,36 @@ public class LuceneIndexCommandsJUnitTest { cliFunctionResults.add(new CliFunctionResult("member0", e, e.getMessage())); } - doReturn(mockResultCollector).when(commands).executeFunction( + doReturn(mockResultCollector).when(command).executeFunction( any(LuceneDestroyIndexFunction.class), any(LuceneDestroyIndexInfo.class), anySet()); doReturn(cliFunctionResults).when(mockResultCollector).getResult(); - doReturn(members).when(commands).getNormalMembersWithSameOrNewerVersion(any()); + doReturn(members).when(command).getNormalMembersWithSameOrNewerVersion(any()); - CommandResult result = (CommandResult) commands.destroyIndex(indexName, regionPath); + ResultModel result = command.destroyIndex(indexName, regionPath); verifyDestroyIndexCommandResult(result, cliFunctionResults, expectedStatus); } @Test public void testDestroyAllIndexesNoRegionMembers() { - LuceneIndexCommands commands = createTestLuceneIndexCommandsForDestroyIndex(); + LuceneDestroyIndexCommand commands = + spy(new LuceneTestDestroyIndexCommand(mockCache, null)); doReturn(Collections.emptySet()).when(commands).getNormalMembersWithSameOrNewerVersion(any()); final List<CliFunctionResult> cliFunctionResults = new ArrayList<>(); String expectedStatus = CliStrings.format( LuceneCliStrings.LUCENE_DESTROY_INDEX__MSG__COULD_NOT_FIND__MEMBERS_GREATER_THAN_VERSION_0, - new Object[] {Version.GEODE_1_7_0}) + LINE_SEPARATOR; + new Object[] {Version.GEODE_1_7_0}); cliFunctionResults.add(new CliFunctionResult("member0", CliFunctionResult.StatusState.OK)); - CommandResult result = (CommandResult) commands.destroyIndex(null, "regionPath"); + + ResultModel result = commands.destroyIndex(null, "regionPath"); verifyDestroyIndexCommandResult(result, cliFunctionResults, expectedStatus); } @Test @Parameters({"true", "false"}) public void testDestroyAllIndexesWithRegionMembers(boolean expectedToSucceed) { - LuceneIndexCommands commands = createTestLuceneIndexCommandsForDestroyIndex(); + LuceneDestroyIndexCommand commands = + spy(new LuceneTestDestroyIndexCommand(mockCache, null)); String indexName = null; String regionPath = "regionPath"; @@ -520,43 +532,33 @@ public class LuceneIndexCommandsJUnitTest { doReturn(members).when(commands).getNormalMembersWithSameOrNewerVersion(any()); - CommandResult result = (CommandResult) commands.destroyIndex(indexName, regionPath); + ResultModel result = commands.destroyIndex(indexName, regionPath); verifyDestroyIndexCommandResult(result, cliFunctionResults, expectedStatus); } - private LuceneIndexCommands createTestLuceneIndexCommandsForDestroyIndex() { - final ResultCollector mockResultCollector = mock(ResultCollector.class); - final LuceneIndexCommands commands = spy(createIndexCommands(this.mockCache, null)); - - final List<CliFunctionResult> cliFunctionResults = new ArrayList<>(); - cliFunctionResults - .add(new CliFunctionResult("member", CliFunctionResult.StatusState.OK, "Index Destroyed")); - - doReturn(mockResultCollector).when(commands).executeFunctionOnRegion( - any(LuceneDestroyIndexFunction.class), any(LuceneIndexInfo.class), eq(false)); - doReturn(cliFunctionResults).when(mockResultCollector).getResult(); - return commands; - } - - private void verifyDestroyIndexCommandResult(CommandResult result, + private void verifyDestroyIndexCommandResult(ResultModel result, List<CliFunctionResult> cliFunctionResults, String expectedStatus) { assertThat(result.getStatus()).isEqualTo(Status.OK); - if (result.getType().equals(TYPE_TABULAR)) { - TabularResultData data = (TabularResultData) result.getResultData(); - List<String> members = data.retrieveAllValues("Member"); + + TabularResultModel data = result.getTableSection("lucene-indexes"); + if (data != null) { + List<String> members = data.getValuesInColumn("Member"); assertThat(cliFunctionResults.size()).isEqualTo(members.size()); // Verify each member for (int i = 0; i < members.size(); i++) { assertThat(members.get(i)).isEqualTo("member" + i); } // Verify each status - List<String> status = data.retrieveAllValues("Status"); + List<String> status = data.getValuesInColumn("Status"); for (String statu : status) { assertThat(statu).isEqualTo(expectedStatus); } - } else { + } else if (result.getInfoSection("info") != null) { // Info result. Verify next lines are equal. - assertThat(result.nextLine()).isEqualTo(expectedStatus); + assertThat(result.getInfoSection("info").getContent().get(0)).isEqualTo(expectedStatus); + } else { + // Error result. Verify next lines are equal. + assertThat(result.getInfoSection("error").getContent().get(0)).isEqualTo(expectedStatus); } } @@ -592,11 +594,6 @@ public class LuceneIndexCommandsJUnitTest { return mockIndexStats; } - private LuceneIndexCommands createIndexCommands(final InternalCache cache, - final Execution functionExecutor) { - return new LuceneTestIndexCommands(cache, functionExecutor); - } - private LuceneIndexDetails createIndexDetails(final String indexName, final String regionPath, final String[] searchableFields, final Map<String, Analyzer> fieldAnalyzers, LuceneIndexStats indexStats, LuceneIndexStatus status, final String serverName, @@ -617,11 +614,31 @@ public class LuceneIndexCommandsJUnitTest { return new LuceneSearchResults(key, value, score); } - private static class LuceneTestIndexCommands extends LuceneIndexCommands { + private static class LuceneTestListIndexCommand extends LuceneListIndexCommand { + private final Execution functionExecutor; + LuceneTestListIndexCommand(final InternalCache cache, final Execution functionExecutor) { + assert cache != null : "The InternalCache cannot be null!"; + setCache(cache); + this.functionExecutor = functionExecutor; + } + + @Override + public Set<DistributedMember> getAllMembers() { + return Collections.emptySet(); + } + + @Override + public Execution getMembersFunctionExecutor(final Set<DistributedMember> members) { + assertThat(members).isNotNull(); + return this.functionExecutor; + } + } + + private static class LuceneTestCreateIndexCommand extends LuceneCreateIndexCommand { private final Execution functionExecutor; - LuceneTestIndexCommands(final InternalCache cache, final Execution functionExecutor) { + LuceneTestCreateIndexCommand(final InternalCache cache, final Execution functionExecutor) { assert cache != null : "The InternalCache cannot be null!"; setCache(cache); this.functionExecutor = functionExecutor; @@ -639,4 +656,66 @@ public class LuceneIndexCommandsJUnitTest { } } + private static class LuceneTestDescribeIndexCommand extends LuceneDescribeIndexCommand { + private final Execution functionExecutor; + + LuceneTestDescribeIndexCommand(final InternalCache cache, final Execution functionExecutor) { + assert cache != null : "The InternalCache cannot be null!"; + setCache(cache); + this.functionExecutor = functionExecutor; + } + + @Override + public Set<DistributedMember> getAllMembers() { + return Collections.emptySet(); + } + + @Override + public Execution getMembersFunctionExecutor(final Set<DistributedMember> members) { + assertThat(members).isNotNull(); + return this.functionExecutor; + } + } + + private static class LuceneTestSearchIndexCommand extends LuceneSearchIndexCommand { + private final Execution functionExecutor; + + LuceneTestSearchIndexCommand(final InternalCache cache, final Execution functionExecutor) { + assert cache != null : "The InternalCache cannot be null!"; + setCache(cache); + this.functionExecutor = functionExecutor; + } + + @Override + public Set<DistributedMember> getAllMembers() { + return Collections.emptySet(); + } + + @Override + public Execution getMembersFunctionExecutor(final Set<DistributedMember> members) { + assertThat(members).isNotNull(); + return this.functionExecutor; + } + } + + private static class LuceneTestDestroyIndexCommand extends LuceneDestroyIndexCommand { + private final Execution functionExecutor; + + LuceneTestDestroyIndexCommand(final InternalCache cache, final Execution functionExecutor) { + assert cache != null : "The InternalCache cannot be null!"; + setCache(cache); + this.functionExecutor = functionExecutor; + } + + @Override + public Set<DistributedMember> getAllMembers() { + return Collections.emptySet(); + } + + @Override + public Execution getMembersFunctionExecutor(final Set<DistributedMember> members) { + assertThat(members).isNotNull(); + return this.functionExecutor; + } + } }