/** * This software is licensed to you under the Apache License, Version 2.0 (the * "Apache License"). * * LinkedIn's contributions are made under the Apache License. If you contribute * to the Software, the contributions will be deemed to have been made under the * Apache License, unless you expressly indicate otherwise. Please do not make any * contributions that would be inconsistent with the Apache License. * * You may obtain a copy of the Apache License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, this software * distributed under the Apache License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Apache * License for the specific language governing permissions and limitations for the * software governed under the Apache License. * * © 2012 LinkedIn Corp. All Rights Reserved. */ package com.senseidb.search.req; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.Set; import com.senseidb.search.node.SenseiQueryBuilder; import com.senseidb.search.node.SenseiQueryBuilderFactory; import org.apache.lucene.search.Collector; import org.apache.lucene.search.Query; import org.apache.lucene.search.Searchable; import org.apache.lucene.search.SortField; import org.json.JSONObject; import com.browseengine.bobo.api.BrowseSelection; import com.browseengine.bobo.api.FacetSpec; import com.browseengine.bobo.facets.FacetHandlerInitializerParam; import com.senseidb.search.req.mapred.SenseiMapReduce; import com.senseidb.util.RequestConverter2; public class SenseiRequest implements AbstractSenseiRequest, Cloneable { private static final long serialVersionUID = 1L; private long tid = -1; private HashMap<String,BrowseSelection> _selections; private ArrayList<SortField> _sortSpecs; private Map<String,FacetSpec> _facetSpecMap; private Map<String, Integer> _origFacetSpecMaxCounts; private SenseiQuery _query; private int _offset; private int _count; private int _origOffset; private int _origCount; private boolean _fetchStoredFields; private boolean _origFetchStoredFields; private boolean _fetchStoredValue; private Map<String,FacetHandlerInitializerParam> _facetInitParamMap; private Set<Integer> _partitions; private boolean _showExplanation; private boolean _trace; private boolean _simpleRelevance; private static Random _rand = new Random(System.nanoTime()); private String _routeParam; private String _groupBy; // TODO: Leave here for backward compatible reason, will remove it later. private String[] _groupByMulti; private String[] _distinct; private int _maxPerGroup; private Set<String> _termVectorsToFetch; private List<String> _selectList; // Select list (mostly used in BQL) private transient Set<String> _selectSet; private SenseiMapReduce mapReduceFunction; private List<SenseiError> errors; private Integer scoreMeaningfulDigits; private Searchable _searchable; private SenseiQueryBuilderFactory _queryBuilderFactory; public SenseiRequest(){ _facetInitParamMap = new HashMap<String,FacetHandlerInitializerParam>(); _selections=new HashMap<String,BrowseSelection>(); _sortSpecs=new ArrayList<SortField>(); _facetSpecMap=new HashMap<String,FacetSpec>(); _fetchStoredFields = false; _fetchStoredValue = false; _partitions = null; _showExplanation = false; _trace = false; _routeParam = null; _groupBy = null; _groupByMulti = null; _distinct = null; _maxPerGroup = 0; _termVectorsToFetch = null; _selectList = null; _selectSet = null; _searchable = null; _queryBuilderFactory = null; } public Integer getScoreMeaningfulDigits() { return scoreMeaningfulDigits; } public void setScoreMeaningfulDigits(Integer scoreMeaningfulDigits) { this.scoreMeaningfulDigits = scoreMeaningfulDigits; } public Set<String> getTermVectorsToFetch(){ return _termVectorsToFetch; } public void setTermVectorsToFetch(Set<String> termVectorsToFetch){ _termVectorsToFetch = termVectorsToFetch; } /** * Get the transaction ID. * @return the transaction ID. */ public final long getTid() { return tid; } /** * Set the transaction ID; * @param tid */ public final void setTid(long tid) { this.tid = tid; } public boolean isShowExplanation() { return _showExplanation; } public void setShowExplanation(boolean showExplanation) { _showExplanation = showExplanation; } public boolean isTrace() { return _trace; } public void setSimpleRelevance(boolean simpleRelevance) { _simpleRelevance = simpleRelevance; } public boolean isSimpleRelevance() { return _simpleRelevance; } public void setTrace(boolean trace) { _trace = trace; } public void setPartitions(Set<Integer> partitions){ _partitions = partitions; } public Set<Integer> getPartitions(){ return _partitions; } public void setRouteParam(String routeParam) { _routeParam = routeParam; } public String getRouteParam() { if (_routeParam != null) return _routeParam; _routeParam = String.valueOf(_rand.nextInt()); return _routeParam; } public void setGroupBy(String[] groupBy) { _groupByMulti = groupBy; if (_groupByMulti != null && _groupByMulti.length != 0) _groupBy = _groupByMulti[0]; } public String[] getGroupBy() { if (_groupByMulti == null && _groupBy != null) _groupByMulti = new String[]{_groupBy}; return _groupByMulti; } public void setDistinct(String[] distinct) { _distinct = distinct; } public String[] getDistinct() { return _distinct; } public void setMaxPerGroup(int maxPerGroup) { _maxPerGroup = maxPerGroup; } public int getMaxPerGroup() { return _maxPerGroup; } public Map<String,FacetHandlerInitializerParam> getFacetHandlerInitParamMap(){ return _facetInitParamMap; } public void setFacetHandlerInitParamMap(Map<String,FacetHandlerInitializerParam> paramMap){ _facetInitParamMap = paramMap; } public void putAllFacetHandlerInitializerParams(Map<String,FacetHandlerInitializerParam> params){ _facetInitParamMap.putAll(params); } public void setFacetHandlerInitializerParam(String name,FacetHandlerInitializerParam param){ _facetInitParamMap.put(name, param); } public FacetHandlerInitializerParam getFacetHandlerInitializerParam(String name){ return _facetInitParamMap.get(name); } public Set<String> getSelectionNames(){ return _selections.keySet(); } public void removeSelection(String name){ _selections.remove(name); } public void setFacetSpecs(Map<String,FacetSpec> facetSpecMap) { _facetSpecMap = facetSpecMap; } public Map<String,FacetSpec> getFacetSpecs() { return _facetSpecMap; } public void saveState() { _origOffset = _offset; _origCount = _count; _origFetchStoredFields = _fetchStoredFields; if (_origFacetSpecMaxCounts == null && _facetSpecMap != null) { _origFacetSpecMaxCounts= new HashMap<String, Integer>(); for (Map.Entry<String, FacetSpec> entry : _facetSpecMap.entrySet()) { FacetSpec spec = entry.getValue(); if (spec != null) { _origFacetSpecMaxCounts.put(entry.getKey(), spec.getMaxCount()); } } } } public void restoreState() { _offset = _origOffset; _count = _origCount; _fetchStoredFields = _origFetchStoredFields; if (_facetSpecMap != null) { for (Map.Entry<String, FacetSpec> entry : _facetSpecMap.entrySet()) { FacetSpec spec = entry.getValue(); if (spec != null) { spec.setMaxCount(_origFacetSpecMaxCounts.get(entry.getKey())); } } } } public int getSelectionCount() { return _selections.size(); } public void clearSelections(){ _selections.clear(); } /** * Gets the number of facet specs * @return number of facet pecs * @see #setFacetSpec(String, FacetSpec) * @see #getFacetSpec(String) */ public int getFacetSpecCount(){ return _facetSpecMap.size(); } public void clearSort(){ _sortSpecs.clear(); } public boolean isFetchStoredFields(){ return _fetchStoredFields; } public void setFetchStoredFields(boolean fetchStoredFields){ _fetchStoredFields = fetchStoredFields; } public boolean isFetchStoredValue(){ return _fetchStoredValue; } public void setFetchStoredValue(boolean fetchStoredValue){ _fetchStoredValue = fetchStoredValue; } /** * Sets a facet spec * @param name field name * @param facetSpec Facet spec * @see #getFacetSpec(String) */ public void setFacetSpec(String name,FacetSpec facetSpec){ _facetSpecMap.put(name,facetSpec); } /** * Gets a facet spec * @param name field name * @return facet spec * @see #setFacetSpec(String, FacetSpec) */ public FacetSpec getFacetSpec(String name){ return _facetSpecMap.get(name); } /** * Gets the number of hits to return. Part of the paging parameters. * @return number of hits to return. * @see #setCount(int) */ public int getCount() { return _count; } /** * Sets the number of hits to return. Part of the paging parameters. * @param count number of hits to return. * @see #getCount() */ public void setCount(int count) { _count = count; } /** * Gets the offset. Part of the paging parameters. * @return offset * @see #setOffset(int) */ public int getOffset() { return _offset; } /** * Sets of the offset. Part of the paging parameters. * @param offset offset * @see #getOffset() */ public void setOffset(int offset) { _offset = offset; } /** * Set the search query * @param query query object * @see #getQuery() */ public void setQuery(SenseiQuery query){ _query=query; } /** * Gets the search query * @return query object * @see #setQuery(SenseiQuery) */ public SenseiQuery getQuery(){ return _query; } /** * Adds a browse selection array * @param selections selections to add * @see #addSelection(BrowseSelection) * @see #getSelections() */ public void addSelections(BrowseSelection[] selections) { for (BrowseSelection selection : selections) { addSelection(selection); } } /** * Adds a browse selection * @param sel selection * @see #getSelections() */ public void addSelection(BrowseSelection sel){ _selections.put(sel.getFieldName(),sel); } /** * Gets all added browse selections * @return added selections * @see #addSelection(BrowseSelection) */ public BrowseSelection[] getSelections(){ return _selections.values().toArray(new BrowseSelection[_selections.size()]); } /** * Gets selection by field name * @param fieldname * @return selection on the field */ public BrowseSelection getSelection(String fieldname){ return _selections.get(fieldname); } /** * Add a sort spec * @param sortSpec sort spec * @see #getSort() * @see #setSort(SortField[]) */ public void addSortField(SortField sortSpec){ _sortSpecs.add(sortSpec); } /** * Add a sort spec * @param sortSpecs sort spec * @see #getSort() * @see #setSort(SortField[]) */ public void addSortFields(SortField[] sortSpecs){ for (SortField field : sortSpecs) { addSortField(field); } } /** * Gets the sort criteria * @return sort criteria * @see #setSort(SortField[]) * @see #addSortField(SortField) */ public SortField[] getSort(){ return _sortSpecs.toArray(new SortField[_sortSpecs.size()]); } /** * Sets the sort criteria * @param sorts sort criteria * @see #addSortField(SortField) * @see #getSort() */ public void setSort(SortField[] sorts){ _sortSpecs.clear(); for (int i=0;i<sorts.length;++i){ _sortSpecs.add(sorts[i]); } } /** * Sets the select list. * @param selectList select list */ public void setSelectList(List<String> selectList) { _selectList = selectList; _selectSet = null; } /** * Gets the select list. * @return select list. */ public List<String> getSelectList() { return _selectList; } public Set<String> getSelectSet() { if (_selectSet == null && _selectList != null && !(_selectList.size() == 1 && "*".equals(_selectList.get(0)))) { _selectSet = new HashSet<String>(_selectList); } return _selectSet; } /** * Set the searchable for the query * @param searchable query object * @see #getQuery() */ public void setSearchable(Searchable searchable){ _searchable = searchable; } /** * Gets the search query * @return searchable object * @see #setQuery(SenseiQuery) */ public Searchable getSearchable(){ return _searchable; } public void setQueryBuilderFactory(SenseiQueryBuilderFactory queryBuilderFactory) { _queryBuilderFactory = queryBuilderFactory; } /** * Constructs a new custom collector object * @param q * @return custom collector * @throws Exception */ public Collector buildCollector(Query q) throws Exception { SenseiQueryBuilder queryBuilder = _queryBuilderFactory.getQueryBuilder(_query, _searchable); return queryBuilder.buildCollector(q); } /** Represents sorting by document score (relevancy). */ public static final SortField FIELD_SCORE = new SortField (null, SortField.SCORE); public static final SortField FIELD_SCORE_REVERSE = new SortField (null, SortField.SCORE, true); /** Represents sorting by document number (index order). */ public static final SortField FIELD_DOC = new SortField (null, SortField.DOC); public static final SortField FIELD_DOC_REVERSE = new SortField (null, SortField.DOC, true); @Override public String toString(){ StringBuilder buf=new StringBuilder(); if(_query != null) buf.append("query: ").append(_query.toString()).append('\n'); buf.append("page: [").append(_offset).append(',').append(_count).append("]\n"); if(_sortSpecs != null) buf.append("sort spec: ").append(_sortSpecs).append('\n'); if(_selections != null) buf.append("selections: ").append(_selections).append('\n'); if(_facetSpecMap != null) buf.append("facet spec: ").append(_facetSpecMap).append('\n'); // if (_routeParam != null) buf.append("route param: ").append(getRouteParam()).append('\n'); if (_groupBy != null) buf.append("group by: ").append(_groupBy).append('\n'); buf.append("max per group: ").append(_maxPerGroup).append('\n'); buf.append("fetch stored fields: ").append(_fetchStoredFields).append('\n'); buf.append("fetch stored value: ").append(_fetchStoredValue); return buf.toString(); } @Override public SenseiRequest clone() { SenseiRequest clone = new SenseiRequest(); clone.setTid(this.getTid()); BrowseSelection[] selections = this.getSelections(); for(BrowseSelection selection : selections) clone.addSelection(selection); for(SortField sort : this.getSort()) clone.addSortField(sort); Map<String, FacetSpec> cloneFacetSpecs = new HashMap<String, FacetSpec>(); for(Entry<String, FacetSpec> facetSpec : this.getFacetSpecs().entrySet()) { cloneFacetSpecs.put(facetSpec.getKey(), facetSpec.getValue().clone()); } clone.setFacetSpecs(cloneFacetSpecs); Map<String, FacetHandlerInitializerParam> cloneFacetInit = new HashMap<String, FacetHandlerInitializerParam>(); for(Entry<String, FacetHandlerInitializerParam> facetInit : this.getFacetHandlerInitParamMap().entrySet()) { cloneFacetInit.put(facetInit.getKey(), facetInit.getValue()); // TODO consider cloning values as well } clone.setFacetHandlerInitParamMap(cloneFacetInit); clone.setQuery(this.getQuery()); clone.setOffset(this.getOffset()); clone.setCount(this.getCount()); clone.setFetchStoredFields(this.isFetchStoredFields()); clone.setFetchStoredValue(this.isFetchStoredValue()); clone.setPartitions(this.getPartitions()); clone.setShowExplanation(this.isShowExplanation()); clone.setRouteParam(this.getRouteParam()); clone.setGroupBy(this.getGroupBy()); clone.setDistinct(this.getDistinct()); clone.setMaxPerGroup(this.getMaxPerGroup()); clone.setTermVectorsToFetch(this.getTermVectorsToFetch()); if (this.getSelectList() != null) { clone.setSelectList(new ArrayList<String>(this.getSelectList())); } clone.setMapReduceFunction(this.getMapReduceFunction()); clone.setScoreMeaningfulDigits(this.getScoreMeaningfulDigits()); return clone; } @Override public boolean equals(Object o) { if (!(o instanceof SenseiRequest)) return false; SenseiRequest b = (SenseiRequest)o; if (getCount() != b.getCount()) return false; if (getOffset() != b.getOffset()) return false; if (!facetSpecsAreEqual(getFacetSpecs(), b.getFacetSpecs())) return false; if (!selectionsAreEqual(getSelections(), b.getSelections())) return false; if (!initParamsAreEqual(getFacetHandlerInitParamMap(), b.getFacetHandlerInitParamMap())) return false; if (!Arrays.equals(getSort(), b.getSort())) return false; if (getQuery() == null) { if (b.getQuery() != null) return false; } else { if (!getQuery().toString().equals(b.getQuery().toString())) return false; } if (getGroupBy() == null) { if (b.getGroupBy() != null) return false; } else { if(!Arrays.equals(getGroupBy(), b.getGroupBy())) return false; } if (getMaxPerGroup() != b.getMaxPerGroup()) return false; if (getPartitions() == null) { if (b.getPartitions() != null) return false; } else { if (!setsAreEqual(getPartitions(), b.getPartitions())) return false; } return true; } private boolean initParamsAreEqual(Map<String, FacetHandlerInitializerParam> a, Map<String, FacetHandlerInitializerParam> b) { if (a.size() != b.size()) return false; for (Entry<String,FacetHandlerInitializerParam> entry : a.entrySet()) { String key = entry.getKey(); if (!b.containsKey(key)) return false; if (!areFacetHandlerInitializerParamsEqual(entry.getValue(), b.get(key))) return false; } return true; } private boolean areFacetHandlerInitializerParamsEqual(FacetHandlerInitializerParam a, FacetHandlerInitializerParam b) { if (!setsAreEqual(a.getBooleanParamNames(), b.getBooleanParamNames())) return false; if (!setsAreEqual(a.getIntParamNames(), b.getIntParamNames())) return false; if (!setsAreEqual(a.getDoubleParamNames(), b.getDoubleParamNames())) return false; if (!setsAreEqual(a.getLongParamNames(), b.getLongParamNames())) return false; if (!setsAreEqual(a.getStringParamNames(), b.getStringParamNames())) return false; if (!setsAreEqual(a.getByteArrayParamNames(), b.getByteArrayParamNames())) return false; for (String name : a.getBooleanParamNames()) { if (!Arrays.equals(a.getBooleanParam(name), b.getBooleanParam(name))) return false; } for (String name : a.getIntParamNames()) { if (!Arrays.equals(a.getIntParam(name), b.getIntParam(name))) return false; } for (String name : a.getDoubleParamNames()) { if (!Arrays.equals(a.getDoubleParam(name), b.getDoubleParam(name))) return false; } for (String name : a.getLongParamNames()) { if (!Arrays.equals(a.getLongParam(name), b.getLongParam(name))) return false; } for (String name : a.getStringParamNames()) { if (!Arrays.equals(a.getStringParam(name).toArray(new String[0]), b.getStringParam(name).toArray(new String[0]))) return false; } /* NOT YET SUPPORTED for (String name : a.getByteArrayParamNames()) { assertTrue(Arrays.equals(a.getByteArrayParam(name), b.getByteArrayParam(name))); } */ return true; } private boolean facetSpecsAreEqual(Map<String, FacetSpec> a, Map<String, FacetSpec> b) { if (a.size() != b.size()) return false; for (Entry<String,FacetSpec> entry : a.entrySet()) { String key = entry.getKey(); if (!(b.containsKey(key))) return false; if (!facetSpecsAreEqual(entry.getValue(), b.get(key))) return false; } return true; } private boolean facetSpecsAreEqual(FacetSpec a, FacetSpec b) { return (a.getMaxCount() == b.getMaxCount()) && (a.getMinHitCount() == b.getMinHitCount()) && (a.getOrderBy() == b.getOrderBy()) && (a.isExpandSelection() == b.isExpandSelection()); } private boolean selectionsAreEqual(BrowseSelection[] a, BrowseSelection[] b) { if (a.length != b.length) return false; for (int i = 0; i < a.length; i++) { if (!selectionsAreEqual(a[i], b[i])) return false; } return true; } private boolean selectionsAreEqual(BrowseSelection a, BrowseSelection b) { return (a.getFieldName().equals(b.getFieldName())) && (Arrays.equals(a.getValues(), b.getValues())) && (Arrays.equals(a.getNotValues(), b.getNotValues())) && (a.getSelectionOperation().equals(b.getSelectionOperation())) && (a.getSelectionProperties().equals(b.getSelectionProperties())); } public SenseiMapReduce getMapReduceFunction() { return mapReduceFunction; } public void setMapReduceFunction(SenseiMapReduce mapReduceFunction) { this.mapReduceFunction = mapReduceFunction; } public List<SenseiError> getErrors() { if (errors == null) errors = new ArrayList<SenseiError>(); return errors; } public void addError(SenseiError error) { if (errors == null) errors = new ArrayList<SenseiError>(); errors.add(error); } private <T> boolean setsAreEqual(Set<T> a, Set<T> b) { if (a.size() != b.size()) return false; Iterator<T> iter = a.iterator(); while (iter.hasNext()) { T val = iter.next(); if (!b.contains(val)) return false; } return true; } /** * Builds SenseiRequest based on a JSON object. * * @param json The input JSON object. * @param facetInfoMap Facet information map, which maps a facet name * to a String array in which the first element is the facet * type (like "simple" or "range") and the second element is * the data type (like "int" or "long"). * @return The built SenseiRequest. */ public static SenseiRequest fromJSON(final JSONObject json, final Map<String, String[]> facetInfoMap) throws Exception { return RequestConverter2.fromJSON(json, facetInfoMap); } }