/**
* Copyright 2008 the original author or authors.
*
* Licensed 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 net.sf.katta.lib.lucene;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import net.sf.katta.client.Client;
import net.sf.katta.client.ClientResult;
import net.sf.katta.client.INodeSelectionPolicy;
import net.sf.katta.protocol.InteractionProtocol;
import net.sf.katta.util.ClientConfiguration;
import net.sf.katta.util.KattaException;
import net.sf.katta.util.ZkConfiguration;
import org.apache.hadoop.io.MapWritable;
import org.apache.log4j.Logger;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
/**
* Default implementation of {@link ILuceneClient}.
*/
public class LuceneClient implements ILuceneClient {
protected final static Logger LOG = Logger.getLogger(LuceneClient.class);
@SuppressWarnings("unused")
private static Method getMethod(String name, Class<?>... parameterTypes) {
try {
return ILuceneServer.class.getMethod("search", parameterTypes);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Could not find method " + name + "(" + Arrays.asList(parameterTypes)
+ ") in ILuceneSearch!");
}
}
private long _timeout = 12000;
private Client _kattaClient;
public LuceneClient() {
_kattaClient = new Client(getServerClass());
}
public LuceneClient(final INodeSelectionPolicy nodeSelectionPolicy) {
_kattaClient = new Client(getServerClass(), nodeSelectionPolicy);
}
public LuceneClient(InteractionProtocol protocol) {
_kattaClient = new Client(getServerClass(), protocol);
}
public LuceneClient(final ZkConfiguration zkConfig) {
_kattaClient = new Client(getServerClass(), zkConfig);
}
public LuceneClient(final INodeSelectionPolicy policy, final ZkConfiguration zkConfig) {
_kattaClient = new Client(getServerClass(), policy, zkConfig);
}
public LuceneClient(final INodeSelectionPolicy policy, final ZkConfiguration zkConfig,
ClientConfiguration clientConfiguration) {
_kattaClient = new Client(getServerClass(), policy, zkConfig, clientConfiguration);
}
public Client getClient() {
return _kattaClient;
}
public long getTimeout() {
return _timeout;
}
public void setTimeout(long timeout) {
this._timeout = timeout;
}
@Override
public Hits search(final Query query, final String[] indexNames) throws KattaException {
return search(query, indexNames, Integer.MAX_VALUE);
}
private static final Method SEARCH_METHOD;
private static final Method SORTED_SEARCH_METHOD;
private static final Method FILTERED_SEARCH_METHOD;
private static final Method FILTERED_SORTED_SEARCH_METHOD;
private static final int SEARCH_METHOD_SHARD_ARG_IDX = 2;
static {
try {
SEARCH_METHOD = ILuceneServer.class.getMethod("search", new Class[] { QueryWritable.class,
DocumentFrequencyWritable.class, String[].class, Long.TYPE, Integer.TYPE });
} catch (NoSuchMethodException e) {
throw new RuntimeException("Could not find method search() in ILuceneSearch!");
}
try {
SORTED_SEARCH_METHOD = ILuceneServer.class.getMethod("search", new Class[] { QueryWritable.class,
DocumentFrequencyWritable.class, String[].class, Long.TYPE, Integer.TYPE, SortWritable.class });
} catch (NoSuchMethodException e) {
throw new RuntimeException("Could not find method search() in ILuceneSearch!");
}
try {
FILTERED_SEARCH_METHOD = ILuceneServer.class.getMethod("search", new Class[] { QueryWritable.class,
DocumentFrequencyWritable.class, String[].class, Long.TYPE, Integer.TYPE, FilterWritable.class });
} catch (NoSuchMethodException e) {
throw new RuntimeException("Could not find method search() in ILuceneSearch!");
}
try {
FILTERED_SORTED_SEARCH_METHOD = ILuceneServer.class.getMethod("search", new Class[] { QueryWritable.class,
DocumentFrequencyWritable.class, String[].class, Long.TYPE, Integer.TYPE, SortWritable.class,
FilterWritable.class });
} catch (NoSuchMethodException e) {
throw new RuntimeException("Could not find method search() in ILuceneSearch!");
}
}
@Override
public Hits search(final Query query, final String[] indexNames, final int count) throws KattaException {
return search(query, indexNames, count, null, null);
}
@Override
public Hits search(final Query query, final String[] indexNames, final int count, final Sort sort)
throws KattaException {
return search(query, indexNames, count, sort, null);
}
@Override
public Hits search(final Query query, final String[] indexNames, final int count, final Sort sort, final Filter filter)
throws KattaException {
final DocumentFrequencyWritable docFreqs = getDocFrequencies(query, indexNames);
ClientResult<HitsMapWritable> results;
if (sort == null && filter == null) {
results = _kattaClient.broadcastToIndices(_timeout, true, SEARCH_METHOD, SEARCH_METHOD_SHARD_ARG_IDX, indexNames,
new QueryWritable(query), docFreqs, null, _timeout, Integer.valueOf(count));
} else if (sort != null && filter == null) {
results = _kattaClient.broadcastToIndices(_timeout, true, SORTED_SEARCH_METHOD, SEARCH_METHOD_SHARD_ARG_IDX,
indexNames, new QueryWritable(query), docFreqs, null, _timeout, Integer.valueOf(count), new SortWritable(
sort));
} else if (sort == null && filter != null) {
results = _kattaClient.broadcastToIndices(_timeout, true, FILTERED_SEARCH_METHOD, SEARCH_METHOD_SHARD_ARG_IDX,
indexNames, new QueryWritable(query), docFreqs, null, _timeout, Integer.valueOf(count),
new FilterWritable(filter));
} else {
results = _kattaClient.broadcastToIndices(_timeout, true, FILTERED_SORTED_SEARCH_METHOD,
SEARCH_METHOD_SHARD_ARG_IDX, indexNames, new QueryWritable(query), docFreqs, null, _timeout,
Integer.valueOf(count), new SortWritable(sort), new FilterWritable(filter));
}
if (results.isError()) {
throw results.getKattaException();
}
Hits result = new Hits();
HitsMapWritable exampleHitWritable = null;
if (!results.getMissingShards().isEmpty()) {
LOG.warn("incomplete result - missing shard-results: " + results.getMissingShards() + ", "
+ results.getShardCoverage());
result.setMissingShards(results.getMissingShards());
}
for (HitsMapWritable hmw : results.getResults()) {
List<Hit> hits = hmw.getHitList();
if (exampleHitWritable == null && !hits.isEmpty()) {
exampleHitWritable = hmw;
}
result.addTotalHits(hmw.getTotalHits());
result.addHits(hits);
}
long start = 0;
if (LOG.isDebugEnabled()) {
start = System.currentTimeMillis();
}
if (result.size() > 0) {
if (sort == null) {
result.sort(count);
} else {
result.fieldSort(sort, exampleHitWritable.getSortFieldTypes(), count);
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("Time for sorting: " + (System.currentTimeMillis() - start) + " ms");
}
return result;
}
private static final Method COUNT_METHOD;
private static final Method FILTER_COUNT_METHOD;
private static final int COUNT_METHOD_SHARD_ARG_IDX = 1;
private static final int FILTER_COUNT_METHOD_SHARD_ARG_IDX = 2;
static {
try {
COUNT_METHOD = ILuceneServer.class.getMethod("getResultCount", new Class[] { QueryWritable.class, String[].class,
Long.TYPE });
} catch (NoSuchMethodException e) {
throw new RuntimeException("Could not find method getResultCount() in ILuceneSearch!");
}
try {
FILTER_COUNT_METHOD = ILuceneServer.class.getMethod("getResultCount", new Class[] { QueryWritable.class,
FilterWritable.class, String[].class, Long.TYPE });
} catch (NoSuchMethodException e) {
throw new RuntimeException("Could not find method getResultCount() in ILuceneSearch!");
}
}
@Override
public int count(final Query query, final String[] indexNames) throws KattaException {
ClientResult<Integer> results = _kattaClient.broadcastToIndices(_timeout, true, COUNT_METHOD,
COUNT_METHOD_SHARD_ARG_IDX, indexNames, new QueryWritable(query), null, _timeout);
if (results.isError()) {
throw results.getKattaException();
}
int count = 0;
for (Integer n : results.getResults()) {
count += n.intValue();
}
return count;
}
@Override
public int count(final Query query, Filter filter, final String[] indexNames) throws KattaException {
ClientResult<Integer> results = _kattaClient.broadcastToIndices(_timeout, true, FILTER_COUNT_METHOD,
FILTER_COUNT_METHOD_SHARD_ARG_IDX, indexNames, new QueryWritable(query), new FilterWritable(filter), null,
_timeout);
if (results.isError()) {
throw results.getKattaException();
}
int count = 0;
for (Integer n : results.getResults()) {
count += n.intValue();
}
return count;
}
private static final Method DOC_FREQ_METHOD;
private static final int DOC_FREQ_METHOD_SHARD_ARG_IDX = 1;
static {
try {
DOC_FREQ_METHOD = ILuceneServer.class.getMethod("getDocFreqs",
new Class[] { QueryWritable.class, String[].class });
} catch (NoSuchMethodException e) {
throw new RuntimeException("Could not find method getDocFreqs() in ILuceneSearch!");
}
}
protected DocumentFrequencyWritable getDocFrequencies(final Query query, final String[] indexNames)
throws KattaException {
ClientResult<DocumentFrequencyWritable> results = _kattaClient.broadcastToIndices(_timeout, true, DOC_FREQ_METHOD,
DOC_FREQ_METHOD_SHARD_ARG_IDX, indexNames, new QueryWritable(query), null);
if (results.isError()) {
throw results.getKattaException();
}
DocumentFrequencyWritable result = null;
for (DocumentFrequencyWritable df : results.getResults()) {
if (result == null) {
// Start with first result.
result = df;
} else {
// Aggregate rest of results into first result.
result.addNumDocs(df.getNumDocs());
result.putAll(df.getAll());
}
}
if (result == null) {
result = new DocumentFrequencyWritable(); // TODO: ?
}
return result;
}
/*
* public MapWritable getDetails(String[] shards, int docId, String[] fields)
* throws IOException; public MapWritable getDetails(String[] shards, int
* docId) throws IOException;
*/
private static final Method GET_DETAILS_METHOD;
private static final Method GET_DETAILS_FIELDS_METHOD;
private static final int GET_DETAILS_METHOD_SHARD_ARG_IDX = 0;
private static final int GET_DETAILS_FIELDS_METHOD_SHARD_ARG_IDX = 0;
static {
try {
GET_DETAILS_METHOD = ILuceneServer.class.getMethod("getDetails", new Class[] { String[].class, Integer.TYPE });
GET_DETAILS_FIELDS_METHOD = ILuceneServer.class.getMethod("getDetails", new Class[] { String[].class,
Integer.TYPE, String[].class });
} catch (NoSuchMethodException e) {
throw new RuntimeException("Could not find method getDetails() in ILuceneSearch!");
}
}
@Override
public MapWritable getDetails(final Hit hit) throws KattaException {
return getDetails(hit, null);
}
@Override
public MapWritable getDetails(final Hit hit, final String[] fields) throws KattaException {
List<String> shards = new ArrayList<String>();
shards.add(hit.getShard());
int docId = hit.getDocId();
//
Object[] args;
Method method;
int shardArgIdx;
if (fields == null) {
args = new Object[] { null, Integer.valueOf(docId) };
method = GET_DETAILS_METHOD;
shardArgIdx = GET_DETAILS_METHOD_SHARD_ARG_IDX;
} else {
args = new Object[] { null, Integer.valueOf(docId), fields };
method = GET_DETAILS_FIELDS_METHOD;
shardArgIdx = GET_DETAILS_FIELDS_METHOD_SHARD_ARG_IDX;
}
ClientResult<MapWritable> results = _kattaClient.broadcastToShards(_timeout, true, method, shardArgIdx, shards,
args);
if (results.isError()) {
throw results.getKattaException();
}
return results.getResults().isEmpty() ? null : results.getResults().iterator().next();
}
@Override
public List<MapWritable> getDetails(List<Hit> hits) throws KattaException, InterruptedException {
return getDetails(hits, null);
}
@Override
public List<MapWritable> getDetails(List<Hit> hits, final String[] fields) throws KattaException,
InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(Math.min(10, hits.size() + 1));
List<MapWritable> results = new ArrayList<MapWritable>();
List<Future<MapWritable>> futures = new ArrayList<Future<MapWritable>>();
for (final Hit hit : hits) {
futures.add(executorService.submit(new Callable<MapWritable>() {
@Override
public MapWritable call() throws Exception {
return getDetails(hit, fields);
}
}));
}
for (Future<MapWritable> future : futures) {
try {
results.add(future.get());
} catch (ExecutionException e) {
throw new KattaException("Could not get hit details.", e.getCause());
}
}
executorService.shutdown();
return results;
}
@Override
public double getQueryPerMinute() {
return _kattaClient.getQueryPerMinute();
}
@Override
public void close() {
_kattaClient.close();
}
protected Client getKattaClient() {
return _kattaClient;
}
protected Class<? extends ILuceneServer> getServerClass() {
return ILuceneServer.class;
}
}