/** * License Agreement for OpenSearchServer * * Copyright (C) 2008-2015 Emmanuel Keller / Jaeksoft * * http://www.open-search-server.com * * This file is part of OpenSearchServer. * * OpenSearchServer is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenSearchServer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenSearchServer. * If not, see <http://www.gnu.org/licenses/>. **/ package com.jaeksoft.searchlib.request; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import javax.xml.xpath.XPathExpressionException; import org.apache.commons.lang3.StringUtils; import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; import org.w3c.dom.DOMException; import org.w3c.dom.Node; import org.xml.sax.SAXException; import com.jaeksoft.searchlib.SearchLibException; import com.jaeksoft.searchlib.config.Config; import com.jaeksoft.searchlib.facet.FacetField; import com.jaeksoft.searchlib.facet.Range; import com.jaeksoft.searchlib.function.expression.SyntaxError; import com.jaeksoft.searchlib.query.ParseException; import com.jaeksoft.searchlib.snippet.SnippetField; import com.jaeksoft.searchlib.snippet.SnippetFieldList; import com.jaeksoft.searchlib.util.DomUtils; import com.jaeksoft.searchlib.util.XPathParser; import com.jaeksoft.searchlib.util.XmlWriter; import com.jaeksoft.searchlib.webservice.query.search.SearchFieldQuery.SearchField.Mode; import com.jaeksoft.searchlib.webservice.query.search.SearchQueryAbstract.Facet.OrderByEnum; import com.jaeksoft.searchlib.webservice.query.search.SearchQueryAbstract.FragmenterEnum; import com.jaeksoft.searchlib.webservice.query.search.SearchQueryAbstract.OperatorEnum; public class SearchFieldRequest extends AbstractLocalSearchRequest implements RequestInterfaces.ReturnedFieldInterface, RequestInterfaces.FilterListInterface { public final static String SEARCHFIELD_QUERY_NODE_NAME = "query"; private List<SearchField> searchFields; private Map<String, String> queryStringMap; public SearchFieldRequest() { super(null, RequestTypeEnum.SearchFieldRequest); } public SearchFieldRequest(Config config) { super(config, RequestTypeEnum.SearchFieldRequest); } @Override protected void setDefaultValues() { super.setDefaultValues(); searchFields = new ArrayList<SearchField>(0); queryStringMap = null; } public void setQueryString(String field, String queryString) { if (queryStringMap == null) queryStringMap = new TreeMap<String, String>(); if (StringUtils.isEmpty(queryString)) queryStringMap.remove(field); else queryStringMap.put(field, queryString); } public void setQueryString(Map<String, String> queryStringMap) { if (this.queryStringMap == null) this.queryStringMap = new TreeMap<String, String>(); else this.queryStringMap.clear(); this.queryStringMap.putAll(queryStringMap); } final protected String getQueryString(String field) { if (queryStringMap == null) return null; return queryStringMap.get(field); } @Override public void copyFrom(AbstractRequest request) { super.copyFrom(request); SearchFieldRequest searchFieldRequest = (SearchFieldRequest) request; this.searchFields = new ArrayList<SearchField>(0); if (searchFieldRequest.searchFields != null) for (SearchField searchField : searchFieldRequest.searchFields) this.searchFields.add(searchField.clone()); if (searchFieldRequest.queryStringMap == null) this.queryStringMap = null; else this.queryStringMap = new TreeMap<String, String>( searchFieldRequest.queryStringMap); } final private static Query getBooleanShouldQuery(Collection<Query> queries) { if (queries == null) return null; switch (queries.size()) { case 1: return queries.iterator().next(); default: BooleanQuery booleanQuery = new BooleanQuery(); for (Query query : queries) booleanQuery.add(query, Occur.SHOULD); return booleanQuery; } } final private static Query getBooleanMustQuery( final Map<Integer, List<Query>> queriesMap) { switch (queriesMap.size()) { case 0: return null; case 1: return getBooleanShouldQuery(queriesMap.values().iterator().next()); default: BooleanQuery booleanQuery = new BooleanQuery(); for (Collection<Query> queries : queriesMap.values()) booleanQuery.add(getBooleanShouldQuery(queries), Occur.MUST); return booleanQuery; } } final private Query buildQuery(String queryString, Occur occur, boolean snippet) throws IOException { Set<String> fields = config.getSchema().getFieldList().getFieldSet(); SnippetFieldList snippetFieldList = snippet ? getSnippetFieldList() : null; Map<Integer, List<Query>> queriesMap = new TreeMap<Integer, List<Query>>(); for (SearchField searchField : searchFields) { Integer booleanGroup = searchField.getBooleanGroup(); if (booleanGroup == null) booleanGroup = 0; List<Query> queries = queriesMap.get(booleanGroup); if (queries == null) { queries = new ArrayList<Query>(5); queriesMap.put(booleanGroup, queries); } String field = searchField.getField(); String query = getQueryString(field); if (query == null) query = queryString; if (snippetFieldList != null && snippetFieldList.get(field) == null) continue; searchField.addQuery(fields, analyzer, query, queries, phraseSlop, occur); } return getBooleanMustQuery(queriesMap); } @Override protected Query newSnippetQuery(String queryString) throws IOException, ParseException, SyntaxError, SearchLibException { return buildQuery(queryString, Occur.SHOULD, true); } private boolean queryIsEmpty(String queryString) { if (!StringUtils.isEmpty(queryString)) return false; if (queryStringMap == null) return true; for (String query : queryStringMap.values()) if (!StringUtils.isEmpty(query)) return false; return true; } private Query newComplexQuery(String queryString, Occur occur) throws ParseException, SyntaxError, SearchLibException, IOException { if (emptyReturnsAll && queryIsEmpty(queryString)) return new MatchAllDocsQuery(); return buildQuery(queryString, occur, false); } @Override protected Query newComplexQuery(String queryString) throws ParseException, SyntaxError, SearchLibException, IOException { Occur occur = defaultOperator == OperatorEnum.AND ? Occur.MUST : Occur.SHOULD; return newComplexQuery(queryString, occur); } @Override public void fromXmlConfigNoLock(Config config, XPathParser xpp, Node requestNode) throws XPathExpressionException, DOMException, ParseException, InstantiationException, IllegalAccessException, ClassNotFoundException { super.fromXmlConfigNoLock(config, xpp, requestNode); Node fieldQueryNode = DomUtils.getFirstNode(requestNode, SEARCHFIELD_QUERY_NODE_NAME); if (fieldQueryNode != null) { List<Node> fieldNodeList = DomUtils.getNodes(fieldQueryNode, SearchField.SEARCHFIELD_NODE_NAME); if (fieldNodeList != null) for (Node fieldNode : fieldNodeList) searchFields.add(new SearchField(fieldNode)); } } @Override public void writeSubXmlConfig(XmlWriter xmlWriter) throws SAXException { xmlWriter.startElement(SEARCHFIELD_QUERY_NODE_NAME); for (SearchField searchField : searchFields) searchField.writeXmlConfig(xmlWriter); xmlWriter.endElement(); } @Override public String getInfo() { rwl.r.lock(); try { return searchFields.toString(); } finally { rwl.r.unlock(); } } public Collection<SearchField> getSearchFields() { rwl.r.lock(); try { return searchFields; } finally { rwl.r.unlock(); } } public void add(SearchField searchField) { rwl.w.lock(); try { searchFields.add(searchField); resetNoLock(); } finally { rwl.w.unlock(); } } /** * Add a new search field to the request * * @param fieldName * The name of the field * @param phrase * Activate the phrase search * @param boost * Set the boost for the term search * @param phraseBoost * Set the boost for the phrase search */ public void addSearchField(String fieldName, Mode mode, double termBoost, double phraseBoost, Integer phraseSlop, Integer booleanGroup) { add(new SearchField(fieldName, mode, termBoost, phraseBoost, phraseSlop, booleanGroup)); } public void addSearchField(String fieldName, double termBoost) { add(new SearchField(fieldName, Mode.PATTERN, termBoost, 1.0, null, null)); } public void remove(SearchField searchField) { rwl.w.lock(); try { searchFields.remove(searchField); resetNoLock(); } finally { rwl.w.unlock(); } } /** * Add a snippet field * * @param fieldName * The name of the field * @param fragmenter * The fragmentation method * @param maxSize * The maximum size of the snippet in character * @param separator * The string sequence used to highlight keywords * @param maxNumber * The maximum number of snippet * @throws SearchLibException */ public void addSnippetField(String fieldName, FragmenterEnum fragmenter, int maxSize, String separator, int maxNumber) throws SearchLibException { SnippetField field = new SnippetField(fieldName); field.setFragmenter(fragmenter.className); field.setMaxSnippetSize(maxSize); field.setSeparator(separator); field.setMaxSnippetNumber(maxNumber); this.getSnippetFieldList().put(field); } /** * Add facet * * @param fieldName * The name of the field * @param minCount * The minimum number of document * @param multivalued * The field can contains several values * @param postCollapsing * The number is calculated after collapsing * @param limit * The maximum number of facet to return * @param orderBy * The sort order for the facet * @param ranges * An optional list of ranges */ public void addFacet(String fieldName, int minCount, boolean multivalued, boolean postCollapsing, Integer limit, OrderByEnum orderBy, List<Range> ranges) { FacetField facetField = new FacetField(fieldName, minCount, multivalued, postCollapsing, limit, orderBy, ranges); getFacetFieldList().put(facetField); } }