/*
* Weblounge: Web Content Management System
* Copyright (c) 2003 - 2011 The Weblounge Team
* http://entwinemedia.com/weblounge
*
* This program 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
* of the License, or (at your option) any later version.
*
* This program 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package ch.entwine.weblounge.search.impl.elasticsearch;
import static ch.entwine.weblounge.search.impl.IndexSchema.ALTERNATE_VERSION;
import static ch.entwine.weblounge.search.impl.IndexSchema.CONTENT_EXTERNAL_REPRESENTATION;
import static ch.entwine.weblounge.search.impl.IndexSchema.CONTENT_FILENAME;
import static ch.entwine.weblounge.search.impl.IndexSchema.CONTENT_MIMETYPE;
import static ch.entwine.weblounge.search.impl.IndexSchema.CONTENT_SOURCE;
import static ch.entwine.weblounge.search.impl.IndexSchema.CREATED;
import static ch.entwine.weblounge.search.impl.IndexSchema.CREATED_BY;
import static ch.entwine.weblounge.search.impl.IndexSchema.FULLTEXT;
import static ch.entwine.weblounge.search.impl.IndexSchema.FULLTEXT_FUZZY;
import static ch.entwine.weblounge.search.impl.IndexSchema.LOCKED_BY;
import static ch.entwine.weblounge.search.impl.IndexSchema.MODIFIED;
import static ch.entwine.weblounge.search.impl.IndexSchema.MODIFIED_BY;
import static ch.entwine.weblounge.search.impl.IndexSchema.PAGELET_PROPERTIES;
import static ch.entwine.weblounge.search.impl.IndexSchema.PAGELET_TYPE;
import static ch.entwine.weblounge.search.impl.IndexSchema.PAGELET_TYPE_COMPOSER;
import static ch.entwine.weblounge.search.impl.IndexSchema.PAGELET_TYPE_COMPOSER_POSITION;
import static ch.entwine.weblounge.search.impl.IndexSchema.PAGELET_TYPE_POSITION;
import static ch.entwine.weblounge.search.impl.IndexSchema.PATH;
import static ch.entwine.weblounge.search.impl.IndexSchema.PATH_PREFIX;
import static ch.entwine.weblounge.search.impl.IndexSchema.PUBLISHED_BY;
import static ch.entwine.weblounge.search.impl.IndexSchema.PUBLISHED_FROM;
import static ch.entwine.weblounge.search.impl.IndexSchema.SERIES;
import static ch.entwine.weblounge.search.impl.IndexSchema.STATIONARY;
import static ch.entwine.weblounge.search.impl.IndexSchema.SUBJECT;
import static ch.entwine.weblounge.search.impl.IndexSchema.TEMPLATE;
import static ch.entwine.weblounge.search.impl.IndexSchema.TEXT;
import static ch.entwine.weblounge.search.impl.IndexSchema.TEXT_FUZZY;
import static ch.entwine.weblounge.search.impl.IndexSchema.TYPE;
import static ch.entwine.weblounge.search.impl.IndexSchema.VERSION;
import static ch.entwine.weblounge.common.content.SearchQuery.Quantifier.All;
import ch.entwine.weblounge.common.content.SearchQuery;
import ch.entwine.weblounge.common.content.SearchQuery.Quantifier;
import ch.entwine.weblounge.common.content.SearchTerms;
import ch.entwine.weblounge.common.content.page.Pagelet;
import ch.entwine.weblounge.common.content.page.PageletURI;
import ch.entwine.weblounge.common.impl.content.SearchQueryImpl;
import ch.entwine.weblounge.common.security.User;
import ch.entwine.weblounge.search.impl.IndexSchema;
import ch.entwine.weblounge.search.impl.IndexUtils;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.FilterBuilder;
import org.elasticsearch.index.query.FilterBuilders;
import org.elasticsearch.index.query.FuzzyLikeThisQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilderException;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.index.query.TermsQueryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Weblounge implementation of the elastic search query builder.
*/
public class ElasticSearchSearchQuery implements QueryBuilder {
/** The logging facility */
private static final Logger logger = LoggerFactory.getLogger(ElasticSearchSearchQuery.class);
/** Term queries on fields */
private Map<String, Set<Object>> searchTerms = null;
/** Negative term queries on fields */
private Map<String, Set<Object>> negativeSearchTerms = null;
/** Fields that must be empty */
private Set<String> emptyFields = null;
/** Fields that need to match all values */
private List<ValueGroup> groups = null;
/** Fields that must not be empty */
private Set<String> nonEmptyFields = null;
/** Fields that query a date range */
private Set<DateRange> dateRanges = null;
/** Filter expression */
private String filter = null;
/** Text query */
private String text = null;
/** Fuzzy text query */
private String fuzzyText = null;
/** Fulltext query */
private String fulltext = null;
/** Fuzzy fulltext query */
private String fuzzyFulltext = null;
/** True if more recent documents should be boosted */
private boolean recencyBoost = false;
/** The boolean query */
private QueryBuilder queryBuilder = null;
/**
* Creates a new elastic search query based on the Weblounge query.
*
* @param query
* the weblounge query
*/
public ElasticSearchSearchQuery(SearchQuery query) {
init(query);
createQuery();
}
/**
* {@inheritDoc}
*
* @see org.elasticsearch.index.query.BaseQueryBuilder#doXContent(org.elasticsearch.common.xcontent.XContentBuilder,
* org.elasticsearch.common.xcontent.ToXContent.Params)
*/
protected void init(SearchQuery query) {
// Resource id
if (query.getIdentifier().length > 0) {
and(IndexSchema.RESOURCE_ID, query.getIdentifier(), true);
}
// Version / Preferred version
if (query.getPreferredVersion() >= 0) {
andNot(ALTERNATE_VERSION, Long.toString(query.getPreferredVersion()), false);
} else if (query.getVersion() >= 0) {
and(VERSION, Long.toString(query.getVersion()), false);
}
// Path
if (query.getPath() != null) {
and(PATH, query.getPath(), true);
}
if (query.getPathPrefix() != null) {
and(PATH_PREFIX, query.getPathPrefix(), true);
}
// Type
if (query.getTypes().length > 0) {
and(TYPE, query.getTypes(), true);
}
// Without type
if (query.getWithoutTypes().length > 0) {
andNot(TYPE, query.getWithoutTypes(), true);
}
// Subjects
if (query.getSubjects() != null) {
for (SearchTerms<String> terms : query.getSubjects()) {
for (String subject : terms.getTerms()) {
and(SUBJECT, subject, true);
}
if (Quantifier.All.equals(terms.getQuantifier())) {
if (groups == null)
groups = new ArrayList<ValueGroup>();
groups.add(new ValueGroup(SUBJECT, (Object[]) terms.getTerms().toArray(new String[terms.size()])));
}
}
}
// Series
if (query.getSeries().length > 0) {
and(SERIES, query.getSeries(), true);
}
// Template
if (query.getTemplate() != null) {
and(TEMPLATE, query.getTemplate(), true);
}
// Stationary
if (query.isStationary()) {
and(STATIONARY, Boolean.TRUE.toString(), false);
}
// Creator
if (query.getCreator() != null) {
and(CREATED_BY, IndexUtils.serializeUserId(query.getCreator()), true);
}
// Creation date
if (query.getCreationDate() != null) {
if (query.getCreationDateEnd() != null)
and(CREATED, query.getCreationDate(), query.getCreationDateEnd());
else
and(CREATED, IndexUtils.beginningOfDay(query.getCreationDate()), IndexUtils.endOfDay(query.getCreationDate()));
}
// Modifier
if (query.getModifier() != null) {
and(MODIFIED_BY, IndexUtils.serializeUserId(query.getModifier()), true);
}
// Modification date
if (query.getModificationDate() != null) {
if (query.getCreationDateEnd() != null)
and(MODIFIED, query.getModificationDate(), query.getModificationDateEnd());
else {
and(MODIFIED, IndexUtils.beginningOfDay(query.getModificationDate()), IndexUtils.endOfDay(query.getModificationDate()));
}
}
// Without Modification
if (query.getWithoutModification()) {
andEmpty(MODIFIED);
}
// Publisher
if (query.getPublisher() != null) {
and(PUBLISHED_BY, IndexUtils.serializeUserId(query.getPublisher()), true);
}
// Publication date
if (query.getPublishingDate() != null) {
if (query.getCreationDateEnd() != null)
and(PUBLISHED_FROM, query.getPublishingDate(), query.getPublishingDateEnd());
else
and(PUBLISHED_FROM, IndexUtils.beginningOfDay(query.getPublishingDate()), IndexUtils.endOfDay(query.getPublishingDate()));
}
// Without Publication
if (query.getWithoutPublication()) {
andEmpty(PUBLISHED_FROM);
}
// Lock owner
if (query.getLockOwner() != null) {
User user = query.getLockOwner();
if (SearchQueryImpl.ANY_USER.equals(user.getLogin()))
andNotEmpty(LOCKED_BY);
else
and(LOCKED_BY, IndexUtils.serializeUserId(user), true);
}
// Pagelet elements
// for (Map.Entry<String, String> entry : query.getElements().entrySet()) {
// TODO: Language?
// solrQuery.append(" ").append(PAGELET_CONTENTS).append(":");
// solrQuery.append("\"").append(entry.getKey()).append("=\"");
// for (String contentValue : StringUtils.split(entry.getValue())) {
// solrQuery.append(" \"").append(IndexUtils.clean(contentValue)).append("\"");
// }
// }
// Pagelet properties
for (Map.Entry<String, String> entry : query.getProperties().entrySet()) {
StringBuffer searchTerm = new StringBuffer();
searchTerm.append(entry.getKey());
searchTerm.append("=").append(entry.getValue());
and(PAGELET_PROPERTIES, searchTerm.toString(), true);
}
// Pagelet types
if (query.getPagelets() != null) {
for (SearchTerms<Pagelet> terms : query.getPagelets()) {
String field = null;
for (Pagelet pagelet : terms.getTerms()) {
String oldField = field;
StringBuffer searchTerm = new StringBuffer();
searchTerm.append(pagelet.getModule()).append("/").append(pagelet.getIdentifier());
// Are we looking for the pagelet in a certain composer or position?
PageletURI uri = pagelet.getURI();
if (uri != null) {
if (StringUtils.isNotBlank(uri.getComposer()) && uri.getPosition() >= 0) {
field = MessageFormat.format(PAGELET_TYPE_COMPOSER_POSITION, uri.getComposer(), uri.getPosition());
and(field, searchTerm.toString(), true);
} else if (StringUtils.isNotBlank(uri.getComposer())) {
field = MessageFormat.format(PAGELET_TYPE_COMPOSER, uri.getComposer());
and(field, searchTerm.toString(), true);
} else if (uri.getPosition() >= 0) {
field = MessageFormat.format(PAGELET_TYPE_POSITION, uri.getPosition());
and(field, searchTerm.toString(), true);
} else {
field = PAGELET_TYPE;
and(field, searchTerm.toString(), true);
}
} else {
field = PAGELET_TYPE;
and(field, searchTerm.toString(), true);
}
if (All.equals(terms.getQuantifier()) && oldField != null && !oldField.equals(field)) {
logger.warn("Queries based on pagelets and the 'all' quantifier need to use the same field");
}
}
// Add filters to support AND queries
if (All.equals(terms.getQuantifier())) {
if (groups == null)
groups = new ArrayList<ValueGroup>();
List<String> pagelets = new ArrayList<String>(terms.size());
for (Pagelet p : terms.getTerms())
pagelets.add(p.toString());
groups.add(new ValueGroup(field, (Object[]) pagelets.toArray(new String[pagelets.size()])));
}
}
}
// Content filenames
if (query.getFilename() != null) {
and(CONTENT_FILENAME, query.getFilename(), true);
}
// Content source
if (query.getSource() != null) {
and(CONTENT_SOURCE, query.getSource(), true);
}
// Content external location
if (query.getExternalLocation() != null) {
and(CONTENT_EXTERNAL_REPRESENTATION, query.getExternalLocation().toExternalForm(), true);
}
// Content mime types
if (query.getMimetype() != null) {
and(CONTENT_MIMETYPE, query.getMimetype(), true);
}
// Fulltext
if (query.getFulltext() != null) {
for (SearchTerms<String> terms : query.getFulltext()) {
StringBuffer queryText = new StringBuffer();
for (String term : terms.getTerms()) {
if (queryText.length() > 0)
queryText.append(" ");
queryText.append(term);
}
if (query.isFuzzySearch())
fuzzyFulltext = queryText.toString();
else
fulltext = queryText.toString();
if (All.equals(terms.getQuantifier())) {
if (groups == null)
groups = new ArrayList<ValueGroup>();
if (query.isFuzzySearch()) {
logger.warn("All quantifier not supported in conjunction with wildcard fulltext");
}
groups.add(new ValueGroup(FULLTEXT, (Object[]) terms.getTerms().toArray(new String[terms.size()])));
}
}
}
// Text
if (query.getTerms() != null) {
for (SearchTerms<String> terms : query.getTerms()) {
StringBuffer queryText = new StringBuffer();
for (String term : terms.getTerms()) {
if (queryText.length() > 0)
queryText.append(" ");
queryText.append(term);
}
if (query.isFuzzySearch())
fuzzyText = queryText.toString();
else
this.text = queryText.toString();
if (All.equals(terms.getQuantifier())) {
if (groups == null)
groups = new ArrayList<ValueGroup>();
if (query.isFuzzySearch()) {
logger.warn("All quantifier not supported in conjunction with wildcard text");
}
groups.add(new ValueGroup(TEXT, (Object[]) terms.getTerms().toArray(new String[terms.size()])));
}
}
}
// Filter query
if (query.getFilter() != null) {
this.filter = query.getFilter();
}
// Recency boost
this.recencyBoost = query.getRecencyPriority();
}
/**
* Create the actual query. We start with a query that matches everything,
* then move to the boolean conditions, finally add filter queries.
*/
private void createQuery() {
queryBuilder = new MatchAllQueryBuilder();
// The boolean query builder
BoolQueryBuilder booleanQuery = new BoolQueryBuilder();
// Terms
if (searchTerms != null) {
for (Map.Entry<String, Set<Object>> entry : searchTerms.entrySet()) {
Set<Object> values = entry.getValue();
if (values.size() == 1)
booleanQuery.must(new TermsQueryBuilder(entry.getKey(), values.iterator().next()));
else
booleanQuery.must(new TermsQueryBuilder(entry.getKey(), values.toArray(new String[values.size()])));
}
this.queryBuilder = booleanQuery;
}
// Negative terms
if (negativeSearchTerms != null) {
for (Map.Entry<String, Set<Object>> entry : negativeSearchTerms.entrySet()) {
Set<Object> values = entry.getValue();
if (values.size() == 1)
booleanQuery.mustNot(new TermsQueryBuilder(entry.getKey(), values.iterator().next()));
else
booleanQuery.mustNot(new TermsQueryBuilder(entry.getKey(), values.toArray(new String[values.size()])));
}
this.queryBuilder = booleanQuery;
}
// Date ranges
if (dateRanges != null) {
for (DateRange dr : dateRanges) {
booleanQuery.must(dr.getQueryBuilder());
}
this.queryBuilder = booleanQuery;
}
// Text
if (text != null) {
MatchQueryBuilder textQueryBuilder = QueryBuilders.matchQuery(TEXT, text);
booleanQuery.must(textQueryBuilder);
this.queryBuilder = booleanQuery;
}
// Fuzzy text
if (fuzzyText != null) {
FuzzyLikeThisQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyLikeThisQuery(TEXT_FUZZY).likeText(fuzzyText);
booleanQuery.must(fuzzyQueryBuilder);
this.queryBuilder = booleanQuery;
}
// Fulltext
if (fulltext != null) {
MatchQueryBuilder textQueryBuilder = QueryBuilders.matchQuery(FULLTEXT, fulltext);
booleanQuery.must(textQueryBuilder);
this.queryBuilder = booleanQuery;
}
// Fuzzy fulltext
if (fuzzyFulltext != null) {
FuzzyLikeThisQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyLikeThisQuery(FULLTEXT_FUZZY).likeText(fuzzyFulltext);
booleanQuery.must(fuzzyQueryBuilder);
this.queryBuilder = booleanQuery;
}
// Recency boost. We differentiate between various (random) levels of
// recency
if (recencyBoost) {
Calendar date = Calendar.getInstance();
// Last week
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(MODIFIED);
date.add(Calendar.WEEK_OF_YEAR, -1);
rangeQueryBuilder.gt(date.getTimeInMillis()).boost(10);
booleanQuery.should(rangeQueryBuilder);
// Last month
rangeQueryBuilder = QueryBuilders.rangeQuery(MODIFIED);
date.add(Calendar.WEEK_OF_YEAR, -3);
rangeQueryBuilder.gt(date.getTimeInMillis()).boost(5);
booleanQuery.should(rangeQueryBuilder);
// Last year
rangeQueryBuilder = QueryBuilders.rangeQuery(MODIFIED);
date.add(Calendar.MONTH, -5);
rangeQueryBuilder.gt(date.getTimeInMillis()).boost(2);
booleanQuery.should(rangeQueryBuilder);
}
QueryBuilder unfilteredQuery = queryBuilder;
List<FilterBuilder> filters = new ArrayList<FilterBuilder>();
// Add filtering for AND terms
if (groups != null) {
for (ValueGroup group : groups) {
filters.addAll(group.getFilterBuilders());
}
}
// Non-Empty fields
if (nonEmptyFields != null) {
for (String field : nonEmptyFields) {
filters.add(FilterBuilders.existsFilter(field));
}
}
// Empty fields
if (emptyFields != null) {
for (String field : emptyFields) {
filters.add(FilterBuilders.missingFilter(field));
}
}
// Filter expressions
if (filter != null) {
filters.add(FilterBuilders.termFilter(FULLTEXT, filter));
}
// Apply the filters
if (filters.size() == 1) {
this.queryBuilder = QueryBuilders.filteredQuery(unfilteredQuery, filters.get(0));
} else if (filters.size() > 1) {
FilterBuilder andFilter = FilterBuilders.andFilter(filters.toArray(new FilterBuilder[filters.size()]));
this.queryBuilder = QueryBuilders.filteredQuery(unfilteredQuery, andFilter);
}
}
/**
* Stores <code>fieldValue</code> as a search term on the
* <code>fieldName</code> field.
*
* @param fieldName
* the field name
* @param fieldValue
* the field value
* @param clean
* <code>true</code> to escape solr special characters in the field
* value
*/
protected void and(String fieldName, Object fieldValue, boolean clean) {
// Fix the field name, just in case
fieldName = StringUtils.trim(fieldName);
// Make sure the data structures are set up accordingly
if (searchTerms == null)
searchTerms = new HashMap<String, Set<Object>>();
Set<Object> termValues = searchTerms.get(fieldName);
if (termValues == null) {
termValues = new HashSet<Object>();
searchTerms.put(fieldName, termValues);
}
// Add the term
termValues.add(fieldValue);
}
/**
* Stores <code>fieldValues</code> as search terms on the
* <code>fieldName</code> field.
*
* @param fieldName
* the field name
* @param fieldValues
* the field value
* @param clean
* <code>true</code> to escape solr special characters in the field
* value
*/
protected void and(String fieldName, Object[] fieldValues, boolean clean) {
for (Object v : fieldValues) {
and(fieldName, v, clean);
}
}
/**
* Stores <code>fieldValue</code> as a search term on the
* <code>fieldName</code> field.
*
* @param fieldName
* the field name
* @param startDate
* the start date
* @param endDate
* the end date
*/
protected void and(String fieldName, Date startDate, Date endDate) {
// Fix the field name, just in case
fieldName = StringUtils.trim(fieldName);
// Make sure the data structures are set up accordingly
if (dateRanges == null)
dateRanges = new HashSet<DateRange>();
// Add the term
DateRange dateRange = new DateRange(fieldName, startDate, endDate);
dateRanges.add(dateRange);
}
/**
* Stores <code>fieldValue</code> as a negative search term on the
* <code>fieldName</code> field.
*
* @param fieldName
* the field name
* @param fieldValue
* the field value
* @param clean
* <code>true</code> to escape solr special characters in the field
* value
*/
protected void andNot(String fieldName, Object fieldValue, boolean clean) {
// Fix the field name, just in case
fieldName = StringUtils.trim(fieldName);
// Make sure the data structures are set up accordingly
if (negativeSearchTerms == null)
negativeSearchTerms = new HashMap<String, Set<Object>>();
Set<Object> termValues = negativeSearchTerms.get(fieldName);
if (termValues == null) {
termValues = new HashSet<Object>();
negativeSearchTerms.put(fieldName, termValues);
}
// Add the term
termValues.add(fieldValue);
}
/**
* Stores <code>fieldValues</code> as negative search terms on the
* <code>fieldName</code> field.
*
* @param fieldName
* the field name
* @param fieldValues
* the field value
* @param clean
* <code>true</code> to escape solr special characters in the field
* value
*/
protected void andNot(String fieldName, Object[] fieldValues, boolean clean) {
for (Object v : fieldValues) {
andNot(fieldName, v, clean);
}
}
/**
* Encodes the field name as part of the AND clause of a solr query:
* <tt>AND -fieldName : [* TO *]</tt>.
*
* @param fieldName
* the field name
*/
protected void andEmpty(String fieldName) {
if (emptyFields == null)
emptyFields = new HashSet<String>();
emptyFields.add(StringUtils.trim(fieldName));
}
/**
* Encodes the field name as part of the AND clause of a solr query:
* <tt>AND fieldName : ["" TO *]</tt>.
*
* @param fieldName
* the field name
*/
protected void andNotEmpty(String fieldName) {
if (nonEmptyFields == null)
nonEmptyFields = new HashSet<String>();
nonEmptyFields.add(StringUtils.trim(fieldName));
}
/**
* {@inheritDoc}
*
* @see org.elasticsearch.common.xcontent.ToXContent#toXContent(org.elasticsearch.common.xcontent.XContentBuilder,
* org.elasticsearch.common.xcontent.ToXContent.Params)
*/
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params)
throws IOException {
return queryBuilder.toXContent(builder, params);
}
/**
* {@inheritDoc}
*
* @see org.elasticsearch.index.query.QueryBuilder#buildAsBytes()
*/
@Override
public BytesReference buildAsBytes() throws QueryBuilderException {
return queryBuilder.buildAsBytes();
}
/**
* {@inheritDoc}
*
* @see org.elasticsearch.index.query.QueryBuilder#buildAsBytes(org.elasticsearch.common.xcontent.XContentType)
*/
@Override
public BytesReference buildAsBytes(XContentType contentType) {
return queryBuilder.buildAsBytes(contentType);
}
/**
* Utility class to hold date range specifications and turn them into elastic
* search queries.
*/
private static final class DateRange {
/** The field name */
private String field = null;
/** The start date */
private Date startDate = null;
/** The end date */
private Date endDate = null;
/**
* Creates a new date range specification with the given field name, start
* and end dates. <code>null</code> may be passed in for start or end dates
* that should remain unspecified.
*
* @param field
* the field name
* @param start
* the start date
* @param end
* the end date
*/
DateRange(String field, Date start, Date end) {
this.field = field;
this.startDate = start;
this.endDate = end;
}
/**
* Returns the range query that is represented by this date range.
*
* @return the range query builder
*/
QueryBuilder getQueryBuilder() {
RangeQueryBuilder rqb = new RangeQueryBuilder(field);
if (startDate != null)
rqb.from(startDate.getTime());
if (endDate != null)
rqb.to(endDate.getTime());
return rqb;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof DateRange) {
return ((DateRange) obj).field.equals(field);
}
return false;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return field.hashCode();
}
}
/**
* Stores a group of values which will later be added to the query using AND.
*/
private static final class ValueGroup {
/** The field name */
private String field = null;
/** The values to store */
private Object[] values = null;
/**
* Creates a new value group for the given field and values.
*
* @param field
* the field name
* @param values
* the values
*/
ValueGroup(String field, Object... values) {
this.field = field;
this.values = values;
}
/**
* Returns the filter that will make sure only documents are returned that
* match all of the values at once.
*
* @return the filter builder
*/
List<FilterBuilder> getFilterBuilders() {
List<FilterBuilder> filters = new ArrayList<FilterBuilder>(values.length);
for (Object v : values) {
filters.add(FilterBuilders.termFilter(field, v.toString()));
}
return filters;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof DateRange) {
return ((DateRange) obj).field.equals(field);
}
return false;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return field.hashCode();
}
}
}