Skip to content

Commit

Permalink
GEODE-11:Gfsh command for lucene query
Browse files Browse the repository at this point in the history
Added a gfsh command to execute a basic search operation on the lucene index. Added junit and dunit tests to verify.

This closes apache#213
  • Loading branch information
Aparna Dharmakkan authored and gesterzhou committed Jul 20, 2016
1 parent 3f6acdc commit 603bae8
Show file tree
Hide file tree
Showing 8 changed files with 481 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ public class LuceneCliStrings {
public static final String LUCENE_DESCRIBE_INDEX__NAME__HELP = "Name of the lucene index to describe.";
public static final String LUCENE_DESCRIBE_INDEX__REGION_HELP = "Name/Path of the region where the lucene index to be described exists.";




//Search lucene index commands
public static final String LUCENE_SEARCH_INDEX = "lucene search";
public static final String LUCENE_SEARCH_INDEX__HELP = "Search lucene index";
public static final String LUCENE_SEARCH_INDEX__ERROR_MESSAGE = "An error occurred while searching lucene index across the Geode cluster: %1$s";
public static final String LUCENE_SEARCH_INDEX__NAME__HELP = "Name of the lucene index to search.";
public static final String LUCENE_SEARCH_INDEX__REGION_HELP = "Name/Path of the region where the lucene index exists.";
public static final String LUCENE_SEARCH_INDEX__QUERY_STRING="queryStrings";
public static final String LUCENE_SEARCH_INDEX__QUERY_STRING__HELP="Query string to search the lucene index";
public static final String LUCENE_SEARCH_INDEX__DEFAULT_FIELD="defaultField";
public static final String LUCENE_SEARCH_INDEX__DEFAULT_FIELD__HELP="Default field to search in";
public static final String LUCENE_SEARCH_INDEX__NO_RESULTS_MESSAGE="No results";
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,11 @@
*/
package com.gemstone.gemfire.cache.lucene.internal.cli;

import static com.gemstone.gemfire.cache.operations.OperationContext.*;

import java.util.ArrayList;

import java.util.HashMap;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import org.apache.geode.security.GeodePermission.Operation;
Expand All @@ -38,9 +34,12 @@
import com.gemstone.gemfire.cache.execute.FunctionAdapter;
import com.gemstone.gemfire.cache.execute.FunctionInvocationTargetException;
import com.gemstone.gemfire.cache.execute.ResultCollector;
import com.gemstone.gemfire.cache.lucene.LuceneResultStruct;
import com.gemstone.gemfire.cache.lucene.PageableLuceneQueryResults;
import com.gemstone.gemfire.cache.lucene.internal.cli.functions.LuceneCreateIndexFunction;
import com.gemstone.gemfire.cache.lucene.internal.cli.functions.LuceneDescribeIndexFunction;
import com.gemstone.gemfire.cache.lucene.internal.cli.functions.LuceneListIndexFunction;
import com.gemstone.gemfire.cache.lucene.internal.cli.functions.LuceneSearchIndexFunction;
import com.gemstone.gemfire.distributed.DistributedMember;
import com.gemstone.gemfire.internal.cache.execute.AbstractExecution;
import com.gemstone.gemfire.internal.security.GeodeSecurityUtil;
Expand All @@ -52,8 +51,6 @@
import com.gemstone.gemfire.management.internal.cli.functions.CliFunctionResult;
import com.gemstone.gemfire.management.internal.cli.i18n.CliStrings;
import com.gemstone.gemfire.management.internal.cli.result.CommandResultException;
import com.gemstone.gemfire.management.internal.cli.result.ErrorResultData;
import com.gemstone.gemfire.management.internal.cli.result.InfoResultData;
import com.gemstone.gemfire.management.internal.cli.result.ResultBuilder;
import com.gemstone.gemfire.management.internal.cli.result.TabularResultData;
import com.gemstone.gemfire.management.internal.configuration.domain.XmlEntity;
Expand All @@ -70,6 +67,7 @@
public class LuceneIndexCommands extends AbstractCommandsSupport {
private static final LuceneCreateIndexFunction createIndexFunction = new LuceneCreateIndexFunction();
private static final LuceneDescribeIndexFunction describeIndexFunction = new LuceneDescribeIndexFunction();
private static final LuceneSearchIndexFunction searchIndexFunction = new LuceneSearchIndexFunction();

@CliCommand(value = LuceneCliStrings.LUCENE_LIST_INDEX, help = LuceneCliStrings.LUCENE_LIST_INDEX__HELP)
@CliMetaData(shellOnly = false, relatedTopic={CliStrings.TOPIC_GEODE_REGION, CliStrings.TOPIC_GEODE_DATA })
Expand Down Expand Up @@ -251,8 +249,80 @@ protected List<LuceneIndexDetails> getIndexDetails(LuceneIndexInfo indexInfo) th
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) {
try {

LuceneQueryInfo queryInfo=new LuceneQueryInfo(indexName,regionPath,queryString, defaultField);
return getSearchResults(queryInfo);

}
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 (Throwable t) {
SystemFailure.checkFailure();
getCache().getLogger().error(t);
return ResultBuilder.createGemFireErrorResult(String.format(LuceneCliStrings.LUCENE_SEARCH_INDEX__ERROR_MESSAGE,
toString(t, isDebugging())));
}
}

private Result getSearchResults(final LuceneQueryInfo queryInfo) throws Exception {
GeodeSecurityUtil.authorizeRegionManage(queryInfo.getRegionPath());
final String[] groups = {};
final ResultCollector<?, ?> rc = this.executeFunctionOnGroups(searchIndexFunction, groups, queryInfo);
final List<Set<LuceneSearchResults>> functionResults = (List<Set<LuceneSearchResults>>) rc.getResult();

List<LuceneSearchResults> results = functionResults.stream()
.flatMap(set -> set.stream())
.sorted()
.collect(Collectors.toList());
if (results.size() != 0) {
final TabularResultData data = ResultBuilder.createTabularResultData();
for (LuceneSearchResults struct : results) {
data.accumulate("key", struct.getKey());
data.accumulate("value", struct.getValue());
data.accumulate("score", struct.getScore());
}
return ResultBuilder.buildResult(data);
}
else {
return ResultBuilder.createInfoResult(LuceneCliStrings.LUCENE_SEARCH_INDEX__NO_RESULTS_MESSAGE);
}
//@TODO : Pagination
}

protected ResultCollector<?, ?> executeFunctionOnGroups(FunctionAdapter function, String[]groups, final LuceneIndexInfo indexInfo) throws CommandResultException {
final Set<DistributedMember> targetMembers = CliUtil.findAllMatchingMembers(groups, null);
return CliUtil.executeFunction(function, indexInfo, targetMembers);
}

protected ResultCollector<?, ?> executeFunctionOnGroups(FunctionAdapter function, String[]groups, final LuceneQueryInfo queryInfo) throws CommandResultException {
final Set<DistributedMember> targetMembers = CliUtil.findAllMatchingMembers(groups, null);
return CliUtil.executeFunction(function, queryInfo, targetMembers);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 com.gemstone.gemfire.cache.lucene.internal.cli;

import java.io.Serializable;

public class LuceneQueryInfo implements Serializable {
private static final long serialVersionUID = 1L;
private String indexName;
private String regionPath;
private String queryString;
private String defaultField;

public LuceneQueryInfo(final String indexName,
final String regionPath,
final String queryString,
final String defaultField)
{
this.indexName = indexName;
this.regionPath = regionPath;
this.queryString = queryString;
this.defaultField = defaultField;
}

public String getIndexName() {
return indexName;
}

public String getRegionPath() {
return regionPath;
}

public String getQueryString() {
return queryString;
}

public String getDefaultField() {
return defaultField;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 com.gemstone.gemfire.cache.lucene.internal.cli;

import java.io.Serializable;

public class LuceneSearchResults<K,V> implements Comparable<LuceneSearchResults>, Serializable {

private String key;
private String value;
private float score;

public LuceneSearchResults(final String key, final String value, final float score) {
this.key = key;
this.value = value;
this.score = score;
}

public String getKey() {
return key;
}

public String getValue() {
return value;
}

public float getScore() {
return score;
}

@Override
public int compareTo(final LuceneSearchResults searchResults) {
return getScore() < searchResults.getScore() ? -1 : 1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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 com.gemstone.gemfire.cache.lucene.internal.cli.functions;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.CacheFactory;
import com.gemstone.gemfire.cache.execute.FunctionAdapter;
import com.gemstone.gemfire.cache.execute.FunctionContext;
import com.gemstone.gemfire.cache.lucene.LuceneQuery;
import com.gemstone.gemfire.cache.lucene.LuceneQueryException;
import com.gemstone.gemfire.cache.lucene.LuceneResultStruct;
import com.gemstone.gemfire.cache.lucene.LuceneService;
import com.gemstone.gemfire.cache.lucene.LuceneServiceProvider;
import com.gemstone.gemfire.cache.lucene.PageableLuceneQueryResults;
import com.gemstone.gemfire.cache.lucene.internal.cli.LuceneIndexDetails;
import com.gemstone.gemfire.cache.lucene.internal.cli.LuceneIndexInfo;
import com.gemstone.gemfire.cache.lucene.internal.cli.LuceneQueryInfo;
import com.gemstone.gemfire.cache.lucene.internal.cli.LuceneSearchResults;
import com.gemstone.gemfire.cache.query.RegionNotFoundException;
import com.gemstone.gemfire.internal.InternalEntity;

/**
* The LuceneSearchIndexFunction class is a function used to collect the information on a particular lucene index.
* </p>
* @see Cache
* @see com.gemstone.gemfire.cache.execute.Function
* @see FunctionAdapter
* @see FunctionContext
* @see InternalEntity
* @see LuceneIndexDetails
* @see LuceneIndexInfo
*/
@SuppressWarnings("unused")
public class LuceneSearchIndexFunction<K,V> extends FunctionAdapter implements InternalEntity {

protected Cache getCache() {
return CacheFactory.getAnyInstance();
}

public String getId() {
return LuceneSearchIndexFunction.class.getName();
}

public void execute(final FunctionContext context) {

Set<LuceneSearchResults> result=new HashSet<>();
final Cache cache = getCache();
final LuceneQueryInfo queryInfo = (LuceneQueryInfo) context.getArguments();

LuceneService luceneService = LuceneServiceProvider.get(getCache());
try {
if (cache.getRegion(queryInfo.getRegionPath())!=null) {
final LuceneQuery<K, V> query = luceneService.createLuceneQueryFactory().create(
queryInfo.getIndexName(), queryInfo.getRegionPath(), queryInfo.getQueryString(), queryInfo.getDefaultField());
PageableLuceneQueryResults pageableLuceneQueryResults = query.findPages();
while (pageableLuceneQueryResults.hasNext()) {
List<LuceneResultStruct> page = pageableLuceneQueryResults.next();
page.stream()
.forEach(searchResult ->
result.add(new LuceneSearchResults<K,V>(searchResult.getKey().toString(),searchResult.getValue().toString(),searchResult.getScore())));
}
}
context.getResultSender().lastResult(result);
}
catch (LuceneQueryException e) {
context.getResultSender().lastResult(e);
}
}
}
Loading

0 comments on commit 603bae8

Please sign in to comment.