org.opencms.search.solr.CmsSolrQuery Maven / Gradle / Ivy
Show all versions of opencms-test Show documentation
/*
* File : $Source$
* Date : $Date$
* Version: $Revision$
*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (C) 2002 - 2008 Alkacon Software (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* For further information about Alkacon Software, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.search.solr;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsPropertyDefinition;
import org.opencms.i18n.CmsEncoder;
import org.opencms.main.OpenCms;
import org.opencms.search.fields.CmsSearchField;
import org.opencms.util.CmsPair;
import org.opencms.util.CmsRequestUtil;
import org.opencms.util.CmsStringUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.common.params.CommonParams;
/**
* A Solr search query.
*/
public class CmsSolrQuery extends SolrQuery {
/** A constant to add the score field to the result documents. */
public static final String ALL_RETURN_FIELDS = "*,score";
/** The default facet date gap. */
public static final String DEFAULT_FACET_DATE_GAP = "+1DAY";
/** The default query. */
public static final String DEFAULT_QUERY = "*:*";
/** The query type. */
public static final String DEFAULT_QUERY_TYPE = "edismax";
/** The default search result count. */
public static final Integer DEFAULT_ROWS = new Integer(10);
/** A constant to add the score field to the result documents. */
public static final String MINIMUM_FIELDS = CmsSearchField.FIELD_PATH
+ ","
+ CmsSearchField.FIELD_TYPE
+ ","
+ CmsSearchField.FIELD_ID;
/** A constant to add the score field to the result documents. */
public static final String STRUCTURE_FIELDS = CmsSearchField.FIELD_PATH
+ ","
+ CmsSearchField.FIELD_TYPE
+ ","
+ CmsSearchField.FIELD_ID
+ ","
+ CmsSearchField.FIELD_CATEGORY
+ ","
+ CmsSearchField.FIELD_DATE_CONTENT
+ ","
+ CmsSearchField.FIELD_DATE_CREATED
+ ","
+ CmsSearchField.FIELD_DATE_EXPIRED
+ ","
+ CmsSearchField.FIELD_DATE_LASTMODIFIED
+ ","
+ CmsSearchField.FIELD_DATE_RELEASED
+ ","
+ CmsSearchField.FIELD_SUFFIX
+ ","
+ CmsSearchField.FIELD_DEPENDENCY_TYPE
+ ","
+ CmsSearchField.FIELD_DESCRIPTION
+ ","
+ CmsPropertyDefinition.PROPERTY_TITLE
+ CmsSearchField.FIELD_DYNAMIC_PROPERTIES
+ ","
+ CmsSearchField.FIELD_RESOURCE_LOCALES
+ ","
+ CmsSearchField.FIELD_CONTENT_LOCALES
+ ","
+ CmsSearchField.FIELD_SCORE
+ ","
+ CmsSearchField.FIELD_PARENT_FOLDERS;
/** The serial version UID. */
private static final long serialVersionUID = -2387357736597627703L;
/** The facet date gap to use for date facets. */
private String m_facetDateGap = DEFAULT_FACET_DATE_GAP;
/** Ignore expiration flag. */
private boolean m_ignoreExpiration;
/** The parameters given by the 'query string'. */
private Map m_queryParameters = new HashMap();
/** The search words. */
private String m_text;
/** The name of the field to search the text in. */
private List m_textSearchFields = new ArrayList();
/**
* Default constructor.
*/
public CmsSolrQuery() {
this(null, null);
}
/**
* Public constructor.
*
* @param cms the current OpenCms context
* @param queryParams the Solr query parameters
*/
public CmsSolrQuery(CmsObject cms, Map queryParams) {
setQuery(DEFAULT_QUERY);
setFields(ALL_RETURN_FIELDS);
setRequestHandler(DEFAULT_QUERY_TYPE);
setRows(DEFAULT_ROWS);
// set the values from the request context
if (cms != null) {
setLocales(Collections.singletonList(cms.getRequestContext().getLocale()));
setSearchRoots(Collections.singletonList(cms.getRequestContext().getSiteRoot() + "/"));
}
if (queryParams != null) {
m_queryParameters = queryParams;
}
ensureParameters();
ensureReturnFields();
ensureExpiration();
}
/**
* Returns the resource type if only one is set as filter query.
*
* @param fqs the field queries to check
*
* @return the type or null
*/
public static String getResourceType(String[] fqs) {
String ret = null;
int count = 0;
if (fqs != null) {
for (String fq : fqs) {
if (fq.startsWith(CmsSearchField.FIELD_TYPE + ":")) {
String val = fq.substring((CmsSearchField.FIELD_TYPE + ":").length());
val = val.replaceAll("\"", "");
if (OpenCms.getResourceManager().hasResourceType(val)) {
count++;
ret = val;
}
}
}
}
return (count == 1) ? ret : null;
}
/**
* Creates and adds a filter query.
*
* @param fieldName the field name to create a filter query on
* @param vals the values that should match for the given field
* @param all true
to combine the given values with 'AND', false
for 'OR'
* @param useQuotes true
to surround the given values with double quotes, false
otherwise
*/
public void addFilterQuery(String fieldName, List vals, boolean all, boolean useQuotes) {
if (getFilterQueries() != null) {
for (String fq : getFilterQueries()) {
if (fq.startsWith(fieldName + ":")) {
removeFilterQuery(fq);
}
}
}
addFilterQuery(createFilterQuery(fieldName, vals, all, useQuotes));
}
/**
* Adds the given fields/orders to the existing sort fields.
*
* @param sortFields the sortFields to set
*/
public void addSortFieldOrders(Map sortFields) {
if ((sortFields != null) && !sortFields.isEmpty()) {
// add the sort fields to the query
for (Map.Entry entry : sortFields.entrySet()) {
addSort(entry.getKey(), entry.getValue());
}
}
}
/**
* @see java.lang.Object#clone()
*/
@Override
public CmsSolrQuery clone() {
CmsSolrQuery sq = new CmsSolrQuery(null, CmsRequestUtil.createParameterMap(toString()));
sq.m_ignoreExpiration = m_ignoreExpiration;
return sq;
}
/**
* Ensures that the initial request parameters will overwrite the member values.
*
* You can initialize the query with an HTTP request parameter then make some method calls
* and finally re-ensure that the initial request parameters will overwrite the changes
* made in the meanwhile.
*/
public void ensureParameters() {
// overwrite already set values with values from query String
if ((m_queryParameters != null) && !m_queryParameters.isEmpty()) {
for (Map.Entry entry : m_queryParameters.entrySet()) {
if (!entry.getKey().equals(CommonParams.FQ)) {
// add or replace all parameters from the query String
setParam(entry.getKey(), entry.getValue());
} else {
// special handling for filter queries
replaceFilterQueries(entry.getValue());
}
}
}
}
/**
* Removes the expiration flag.
*/
public void removeExpiration() {
if (getFilterQueries() != null) {
for (String fq : getFilterQueries()) {
if (fq.startsWith(CmsSearchField.FIELD_DATE_EXPIRED + ":")
|| fq.startsWith(CmsSearchField.FIELD_DATE_RELEASED + ":")) {
removeFilterQuery(fq);
}
}
}
m_ignoreExpiration = true;
}
/**
* Sets the categories only if not set in the query parameters.
*
* @param categories the categories to set
*/
public void setCategories(List categories) {
if ((categories != null) && !categories.isEmpty()) {
addFilterQuery(CmsSearchField.FIELD_CATEGORY + CmsSearchField.FIELD_DYNAMIC_EXACT, categories, true, true);
}
}
/**
* Sets the categories only if not set in the query parameters.
*
* @param categories the categories to set
*/
public void setCategories(String... categories) {
setCategories(Arrays.asList(categories));
}
/**
* Sets date ranges.
*
* This call will overwrite all existing date ranges for the given keys (name of the date facet field).
*
* The parameter Map uses as:
*
* keys:
Solr field name {@link org.opencms.search.fields.CmsSearchField} and
* values:
pairs with min date as first and max date as second {@link org.opencms.util.CmsPair}
*
* Alternatively you can use Solr standard query syntax like:
*
* +created:[* TO NOW]
* +lastmodified:[' + date + ' TO NOW]
*
* whereby date is Solr formated:
* {@link org.opencms.search.solr.CmsSolrDocument#DF}
*
*
* @param dateRanges the ranges map with field name as key and a CmsPair with min date as first and max date as second
*/
public void setDateRanges(Map> dateRanges) {
if ((dateRanges != null) && !dateRanges.isEmpty()) {
// remove the date ranges
for (Map.Entry> entry : dateRanges.entrySet()) {
removeFacetField(entry.getKey());
}
// add the date ranges
for (Map.Entry> entry : dateRanges.entrySet()) {
addDateRangeFacet(
entry.getKey(),
entry.getValue().getFirst(),
entry.getValue().getSecond(),
m_facetDateGap);
}
}
}
/**
* Sets the facetDateGap.
*
* @param facetDateGap the facetDateGap to set
*/
public void setFacetDateGap(String facetDateGap) {
m_facetDateGap = facetDateGap;
}
/**
* Sets the highlightFields.
*
* @param highlightFields the highlightFields to set
*/
public void setHighlightFields(List highlightFields) {
setParam("hl.fl", CmsStringUtil.listAsString(highlightFields, ","));
}
/**
* Sets the highlightFields.
*
* @param highlightFields the highlightFields to set
*/
public void setHighlightFields(String... highlightFields) {
setParam("hl.fl", CmsStringUtil.arrayAsString(highlightFields, ","));
}
/**
* Sets the locales only if not set in the query parameters.
*
* @param locales the locales to set
*/
public void setLocales(List locales) {
m_textSearchFields = new ArrayList();
if ((locales == null) || locales.isEmpty()) {
m_textSearchFields.add(CmsSearchField.FIELD_TEXT);
if (getFilterQueries() != null) {
for (String fq : getFilterQueries()) {
if (fq.startsWith(CmsSearchField.FIELD_CONTENT_LOCALES + ":")) {
removeFilterQuery(fq);
}
}
}
} else {
List localeStrings = new ArrayList();
for (Locale locale : locales) {
localeStrings.add(locale.toString());
if (!m_textSearchFields.contains("text")
&& !OpenCms.getLocaleManager().getAvailableLocales().contains(locale)) {
// if the locale is not configured in the opencms-system.xml
// there will no localized text fields, so take the general one
m_textSearchFields.add("text");
} else {
m_textSearchFields.add("text_" + locale);
}
}
addFilterQuery(CmsSearchField.FIELD_CONTENT_LOCALES, localeStrings, false, false);
}
if (m_text != null) {
setText(m_text);
}
}
/**
* Sets the locales only if not set in the query parameters.
*
* @param locales the locales to set
*/
public void setLocales(Locale... locales) {
setLocales(Arrays.asList(locales));
}
/**
* @see org.apache.solr.client.solrj.SolrQuery#setRequestHandler(java.lang.String)
*/
@Override
public SolrQuery setRequestHandler(String qt) {
SolrQuery q = super.setRequestHandler(qt);
if (m_text != null) {
setText(m_text);
}
return q;
}
/**
* Sets the resource types only if not set in the query parameters.
*
* @param resourceTypes the resourceTypes to set
*/
public void setResourceTypes(List resourceTypes) {
if ((resourceTypes != null) && !resourceTypes.isEmpty()) {
addFilterQuery(CmsSearchField.FIELD_TYPE, resourceTypes, false, false);
}
}
/**
* Sets the resource types only if not set in the query parameters.
*
* @param resourceTypes the resourceTypes to set
*/
public void setResourceTypes(String... resourceTypes) {
setResourceTypes(Arrays.asList(resourceTypes));
}
/**
* Sets the search roots only if not set as query parameter.
*
* @param searchRoots the searchRoots to set
*/
public void setSearchRoots(List searchRoots) {
if ((searchRoots != null) && !searchRoots.isEmpty()) {
addFilterQuery(CmsSearchField.FIELD_PARENT_FOLDERS, searchRoots, false, true);
}
}
/**
* Sets the search roots only if not set as query parameter.
*
* @param searchRoots the searchRoots to set
*/
public void setSearchRoots(String... searchRoots) {
setSearchRoots(Arrays.asList(searchRoots));
}
/**
* Sets the return fields 'fl' to a predefined set that does not contain content specific fields.
*
* @param structureQuery the true
to return only structural fields
*/
public void setStructureQuery(boolean structureQuery) {
if (structureQuery) {
setFields(STRUCTURE_FIELDS);
}
}
/**
* Sets the text.
*
* @param text the text to set
*/
public void setText(String text) {
m_text = text;
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(text)) {
setQuery(createTextQuery(text));
}
}
/**
* Sets the textSearchFields.
*
* @param textSearchFields the textSearchFields to set
*/
public void setTextSearchFields(List textSearchFields) {
m_textSearchFields = textSearchFields;
if (m_text != null) {
setText(m_text);
}
}
/**
* Sets the textSearchFields.
*
* @param textSearchFields the textSearchFields to set
*/
public void setTextSearchFields(String... textSearchFields) {
setTextSearchFields(Arrays.asList(textSearchFields));
}
/**
* @see org.apache.solr.common.params.ModifiableSolrParams#toString()
*/
@Override
public String toString() {
return CmsEncoder.decode(super.toString());
}
/**
* Creates a filter query on the given field name.
*
* Creates and adds a filter query.
*
* @param fieldName the field name to create a filter query on
* @param vals the values that should match for the given field
* @param all true
to combine the given values with 'AND', false
for 'OR'
* @param useQuotes true
to surround the given values with double quotes, false
otherwise
*
* @return a filter query String e.g. fq=fieldname:val1
*/
private String createFilterQuery(String fieldName, List vals, boolean all, boolean useQuotes) {
String filterQuery = null;
if ((vals != null)) {
if (vals.size() == 1) {
if (useQuotes) {
filterQuery = fieldName + ":" + "\"" + vals.get(0) + "\"";
} else {
filterQuery = fieldName + ":" + vals.get(0);
}
} else if (vals.size() > 1) {
filterQuery = fieldName + ":(";
for (int j = 0; j < vals.size(); j++) {
String val;
if (useQuotes) {
val = "\"" + vals.get(j) + "\"";
} else {
val = vals.get(j);
}
filterQuery += val;
if (vals.size() > (j + 1)) {
if (all) {
filterQuery += " AND ";
} else {
filterQuery += " OR ";
}
}
}
filterQuery += ")";
}
}
return filterQuery;
}
/**
* Creates a OR combined 'q' parameter.
*
* @param text
*
* @return returns the 'q' parameter
*/
private String createTextQuery(String text) {
if (m_textSearchFields.isEmpty()) {
m_textSearchFields.add(CmsSearchField.FIELD_TEXT);
}
String q = "{!q.op=OR type=" + getRequestHandler() + " qf=";
boolean first = true;
for (String textField : m_textSearchFields) {
if (!first) {
q += " ";
}
q += textField;
}
q += "}" + text;
return q;
}
/**
* Ensures that expired and not yet released resources are not returned by default.
*/
private void ensureExpiration() {
boolean expirationDateSet = false;
boolean releaseDateSet = false;
if (getFilterQueries() != null) {
for (String fq : getFilterQueries()) {
if (fq.startsWith(CmsSearchField.FIELD_DATE_EXPIRED + ":")) {
expirationDateSet = true;
}
if (fq.startsWith(CmsSearchField.FIELD_DATE_RELEASED + ":")) {
releaseDateSet = true;
}
}
}
if (!expirationDateSet) {
addFilterQuery(CmsSearchField.FIELD_DATE_EXPIRED + ":[NOW TO *]");
}
if (!releaseDateSet) {
addFilterQuery(CmsSearchField.FIELD_DATE_RELEASED + ":[* TO NOW]");
}
}
/**
* Ensures that at least the 'path' and the 'type' are part of the fields returned field list.
*
* @see CommonParams#FL
*/
private void ensureReturnFields() {
String[] fl = getParams(CommonParams.FL);
if ((fl != null) && (fl.length > 0)) {
List result = new ArrayList();
for (String field : fl) {
String commasep = field.replaceAll(" ", ",");
List list = CmsStringUtil.splitAsList(commasep, ',');
if (!list.contains("*")) {
for (String reqField : CmsStringUtil.splitAsList(MINIMUM_FIELDS, ",")) {
if (!list.contains(reqField)) {
list.add(reqField);
}
}
}
result.addAll(list);
}
setParam(CommonParams.FL, CmsStringUtil.arrayAsString(result.toArray(new String[0]), ","));
}
}
/**
* Removes those filter queries that restrict the fields used in the given filter query Strings.
*
* Searches in the given Strings for a ":", then takes the field name part
* and removes the already set filter queries queries that are matching the same field name.
*
* @param fqs the filter query Strings in the format fq=fieldname:value
that should be removed
*/
private void removeFilterQueries(String[] fqs) {
// iterate over the given filter queries to remove
for (String fq : fqs) {
int idx = fq.indexOf(':');
if (idx != -1) {
// get the field name of the fq to remove
String fieldName = fq.substring(0, idx);
// iterate over the fqs of the already existing fqs from the solr query
if (getFilterQueries() != null) {
for (String sfq : getFilterQueries()) {
if (sfq.startsWith(fieldName + ":")) {
// there exists a filter query for exact the same field, remove it
removeFilterQuery(sfq);
}
}
}
}
}
}
/**
* Removes the given filter queries, if already set and then adds the filter queries again.
*
* @param fqs the filter queries to remove
*/
private void replaceFilterQueries(String[] fqs) {
removeFilterQueries(fqs);
addFilterQuery(fqs);
}
}