/*
* Copyright (c) 2011 LinkedIn, Inc
*
* 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 com.flaptor.indextank.api;
import static java.lang.Double.parseDouble;
import static java.lang.Integer.parseInt;
import static java.lang.String.valueOf;
import java.util.List;
import java.util.Map;
import org.json.simple.JSONObject;
import com.flaptor.indextank.BoostingIndexer;
import com.flaptor.indextank.api.util.Timestamp;
import com.flaptor.indextank.index.Document;
import com.flaptor.indextank.index.scorer.BoostsScorer;
import com.flaptor.indextank.index.scorer.DynamicDataManager;
import com.flaptor.indextank.index.scorer.FunctionRangeFilter;
import com.flaptor.indextank.index.scorer.IntersectionMatchFilter;
import com.flaptor.indextank.index.scorer.MatchFilter;
import com.flaptor.indextank.index.scorer.VariablesRangeFilter;
import com.flaptor.indextank.query.IndexEngineParser;
import com.flaptor.indextank.query.NoSuchQueryVariableException;
import com.flaptor.indextank.query.ParseException;
import com.flaptor.indextank.query.Query;
import com.flaptor.indextank.query.QueryVariables;
import com.flaptor.indextank.query.QueryVariablesImpl;
import com.flaptor.indextank.rpc.CategoryFilter;
import com.flaptor.indextank.rpc.IndextankException;
import com.flaptor.indextank.rpc.InvalidQueryException;
import com.flaptor.indextank.rpc.MissingQueryVariableException;
import com.flaptor.indextank.rpc.RangeFilter;
import com.flaptor.indextank.search.DocumentSearcher;
import com.flaptor.indextank.search.SearchResults;
import com.flaptor.util.Pair;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
public class IndexEngineApi {
protected final EmbeddedIndexEngine engine;
protected final String characterEncoding;
public IndexEngineApi(EmbeddedIndexEngine engine) {
this.engine = engine;
characterEncoding = engine.getCharacterEncoding();
}
public SearchResults search(String queryStr,
int start, int len,
int scoringFunctionIndex,
Map<Integer, Double> queryVariables,
List<CategoryFilter> facetsFilter,
List<RangeFilter> variableRangeFilters,
List<RangeFilter> functionRangeFilters,
Map<String,String> extraParameters) throws IndexEngineApiException {
DocumentSearcher searcher = engine.getSearcher();
try {
Query query = generateQuery(queryStr, start, len,
QueryVariablesImpl.fromMap(queryVariables),
convertToMultimap(facetsFilter),
new IntersectionMatchFilter(
convertToVariableRangeFilter(variableRangeFilters),
convertToFunctionRangeFilter(functionRangeFilters)
)
);
SearchResults search = searcher.search(query, start, len, scoringFunctionIndex, extraParameters);
return search;
} catch (NoSuchQueryVariableException e) {
throw new IndexEngineApiException("Missing query variable with index '" + e.getMissingVariableIndex() + "'", e);
} catch (ParseException e) {
throw new IndexEngineApiException("Invalid query", e);
} catch (RuntimeException e) {
throw new IndexEngineApiException(e);
} catch (InterruptedException e) {
throw new IndexEngineApiException("Interrupted while searching.", e);
}
}
private VariablesRangeFilter convertToVariableRangeFilter(List<RangeFilter> rangeFilters) {
DynamicDataManager dynamicDataManager = engine.getDynamicDataManager();
Multimap<Integer, Pair<Float, Float>> filters = HashMultimap.create();
for (RangeFilter filter : rangeFilters) {
filters.put(filter.get_key(), new Pair<Float, Float>(filter.is_no_floor() ? null : (float)filter.get_floor(), filter.is_no_ceil() ? null : (float)filter.get_ceil()));
}
return new VariablesRangeFilter(dynamicDataManager, filters);
}
private FunctionRangeFilter convertToFunctionRangeFilter(List<RangeFilter> rangeFilters) {
DynamicDataManager dynamicDataManager = engine.getDynamicDataManager();
BoostsScorer scorer = engine.getScorer();
Multimap<Integer, Pair<Float, Float>> filters = HashMultimap.create();
for (RangeFilter filter : rangeFilters) {
filters.put(filter.get_key(), new Pair<Float, Float>(filter.is_no_floor() ? null : (float)filter.get_floor(), filter.is_no_ceil() ? null : (float)filter.get_ceil()));
}
return new FunctionRangeFilter(scorer, dynamicDataManager, filters);
}
private Query generateQuery(String str, int start, int len, QueryVariables vars, Multimap<String, String> facetsFilter, MatchFilter rangeFilters) throws ParseException {
IndexEngineParser parser = engine.getParser();
return new Query(parser.parseQuery(str), str, vars, facetsFilter, rangeFilters);
}
private Multimap<String, String> convertToMultimap(List<CategoryFilter> facetsFilter) {
Multimap<String, String> result = HashMultimap.create();
for (CategoryFilter facetFilter : facetsFilter) {
result.put(facetFilter.get_category(), facetFilter.get_value());
}
return result;
}
public void putDocument(String id, JSONObject fields, JSONObject variables, JSONObject categories) {
BoostingIndexer indexer = engine.getIndexer();
indexer.add(id, new Document(prepareProperties(fields)), Timestamp.inSeconds(), prepareBoosts(variables));
indexer.updateCategories(id, prepareProperties(categories));
}
public void deleteDocument(String id) {
BoostingIndexer indexer = engine.getIndexer();
indexer.del(id);
}
private Map<String, String> prepareProperties(JSONObject jo) {
Map<String, String> properties = Maps.newHashMap();
if(jo == null) {
return properties;
}
for(Object o: jo.entrySet()) {
@SuppressWarnings("rawtypes")
Map.Entry e = (Map.Entry) o;
Object key = e.getKey();
Object value = e.getValue();
properties.put(valueOf(key), valueOf(value));
}
return properties;
}
private Map<Integer, Double> prepareBoosts(JSONObject jo) {
Map<Integer, Double> dynamicBoosts = Maps.newHashMap();
if(jo == null) {
return dynamicBoosts;
}
for(Object o: jo.entrySet()) {
@SuppressWarnings("rawtypes")
Map.Entry e = (Map.Entry) o;
Object key = e.getKey();
Object value = e.getValue();
dynamicBoosts.put(parseInt(valueOf(key)), parseDouble(valueOf(value)));
}
return dynamicBoosts;
}
public List<String> complete(String query, String field) {
return engine.getSuggestor().complete(query, field);
}
public String getCharacterEncoding() {
return characterEncoding;
}
}