/* This file is part of Cyclos (www.cyclos.org). A project of the Social Trade Organisation (www.socialtrade.org). Cyclos 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 2 of the License, or (at your option) any later version. Cyclos 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 Cyclos; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package nl.strohalm.cyclos.utils.lucene; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.List; import nl.strohalm.cyclos.entities.exceptions.QueryParseException; import nl.strohalm.cyclos.utils.DateHelper; import nl.strohalm.cyclos.utils.Period; import nl.strohalm.cyclos.utils.conversion.CoercionHelper; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.CachingWrapperFilter; import org.apache.lucene.search.ChainedFilter; import org.apache.lucene.search.DocIdSet; import org.apache.lucene.search.Filter; import org.apache.lucene.search.FilteredQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.QueryWrapperFilter; import org.apache.lucene.search.TermRangeFilter; import org.apache.lucene.search.TermsFilter; /** * A filter collection * @author luis */ public class Filters extends Filter implements Cloneable { private static final long serialVersionUID = -5904251497348657910L; /** * Returns all filters in a AND operation */ public static Filter and(final Filter... filters) { return chain(ChainedFilter.AND, filters); } /** * Returns all filters in a ANDNOT operation */ public static Filter andNot(final Filter... filters) { return chain(ChainedFilter.ANDNOT, filters); } /** * Returns a filter that applies a query to an specific field */ public static Filter fieldQuery(final Analyzer analyzer, final String field, final String query) { if (StringUtils.isEmpty(query)) { return null; } QueryParser parser = new QueryParser(LuceneUtils.LUCENE_VERSION, field, analyzer); try { Query q = parser.parse(query); return new QueryWrapperFilter(q); } catch (ParseException e) { throw new QueryParseException(); } } /** * Returns a FilteredQuery using the given filter */ public static Query filter(final Query query, final Filter filter) { return new FilteredQuery(query, new CachingWrapperFilter(filter)); } /** * Returns all filters in a OR operation */ public static Filter or(final Filter... filters) { return chain(ChainedFilter.OR, filters); } /** * Returns a filter for a date range including the min and max values */ public static TermRangeFilter range(final String field, final Calendar min, final Calendar max) { return range(field, min, max, true, true); } /** * Returns a filter for a date range, optionally including the min and max values */ public static TermRangeFilter range(final String field, final Calendar min, final Calendar max, final boolean includeMin, final boolean includeMax) { if (min == null && max == null) { return null; } final String minStr = min == null ? LuceneFormatter.MIN_DATE : LuceneFormatter.format(min); final String maxStr = max == null ? LuceneFormatter.MAX_DATE : LuceneFormatter.format(max); return new TermRangeFilter(field, minStr, maxStr, includeMin, includeMax); } /** * Returns a filter for a number range including the min and max values */ public static TermRangeFilter range(final String field, final Number min, final Number max) { return range(field, min, max, true, true); } /** * Returns a filter for a number range, optionally including the min and max values */ public static TermRangeFilter range(final String field, final Number min, final Number max, final boolean includeMin, final boolean includeMax) { if (min == null && max == null) { return null; } final String minStr = min == null ? LuceneFormatter.MIN_DECIMAL : LuceneFormatter.format(min); final String maxStr = max == null ? LuceneFormatter.MAX_DECIMAL : LuceneFormatter.format(max); return new TermRangeFilter(field, minStr, maxStr, includeMin, includeMax); } /** * Returns a filter for a string range including the min and max values (both are required, or no filter will be applied */ public static TermRangeFilter range(final String field, final String min, final String max) { return range(field, min, max, true, true); } /** * Returns a filter for a string range, optionally including the min and max values (both are required, or no filter will be applied */ public static TermRangeFilter range(final String field, final String min, final String max, final boolean includeMin, final boolean includeMax) { if (StringUtils.isEmpty(min) || StringUtils.isEmpty(max)) { return null; } return new TermRangeFilter(field, min, max, includeMin, includeMax); } /** * Returns a filter that forces a given field to be one of the given values */ public static Filter terms(final String field, final Collection<?> values) { if (CollectionUtils.isEmpty(values)) { return null; } final TermsFilter filter = new TermsFilter(); int count = 0; for (final Object object : values) { final String term = object == null ? null : StringUtils.trimToNull("" + object); if (term != null) { filter.addTerm(new Term(field, term)); count++; } } return count == 0 ? null : filter; } /** * Returns a filter that forces a given field to be one of the given values */ public static Filter terms(final String field, final Object... values) { return values == null ? null : terms(field, Arrays.asList(values)); } /** * Returns all filters in a OR operation */ private static Filter chain(final int logic, Filter... filters) { filters = normalize(filters); if (ArrayUtils.isEmpty(filters)) { return null; } return new ChainedFilter(filters, logic); } /** * Normalizes the filters, returning null on empty array and removing null values */ private static Filter[] normalize(final Filter[] filters) { if (ArrayUtils.isEmpty(filters)) { return null; } final List<Filter> list = new ArrayList<Filter>(filters.length); for (final Filter filter : filters) { if (filter != null) { list.add(filter); } } return list.isEmpty() ? null : list.toArray(new Filter[list.size()]); } private final List<Filter> filters = new ArrayList<Filter>(); /** * Adds a custom filter */ public void add(final Filter filter) { if (filter != null) { filters.add(filter); } } /** * Adds a query text for a given field. Internally uses a {@link QueryWrapperFilter}, parsing the query with the given analyzer */ public void addFieldQuery(final Analyzer analyzer, final String field, final String query) { add(fieldQuery(analyzer, field, query)); } /** * Adds a period for the given query */ public void addPeriod(final String field, final Period period) { if (period != null) { addRange(field, period.getBegin(), DateHelper.truncateNextDay(period.getEnd()), true, false); } } /** * Adds a date range filter including min and max values */ public void addRange(final String field, final Calendar min, final Calendar max) { addRange(field, min, max, true, true); } /** * Adds a date range filter, optionally including min and max values */ public void addRange(final String field, final Calendar min, final Calendar max, final boolean includeMin, final boolean includeMax) { add(range(field, min, max, includeMin, includeMax)); } /** * Adds a numeric range filter including min and max values */ public void addRange(final String field, final Number min, final Number max) { addRange(field, min, max, true, true); } /** * Adds a numeric range filter, optionally including min and max values */ public void addRange(final String field, final Number min, final Number max, final boolean includeMin, final boolean includeMax) { add(range(field, min, max, includeMin, includeMax)); } /** * Returns a filter that forces a given field to be one of the given values */ public void addTerms(final String field, final Collection<?> values) { if (CollectionUtils.isEmpty(values)) { return; } boolean used = false; final TermsFilter filter = new TermsFilter(); for (final Object object : values) { final String term = CoercionHelper.coerce(String.class, object); if (StringUtils.isNotEmpty(term)) { filter.addTerm(new Term(field, term)); used = true; } } if (used) { add(filter); } } /** * Returns a filter that forces a given field to be one of the given values */ public void addTerms(final String field, final Object... values) { if (values != null && values.length > 0) { addTerms(field, Arrays.asList(values)); } } @Override public Object clone() { Filters clone = new Filters(); clone.filters.addAll(filters); return clone; } @Override public DocIdSet getDocIdSet(final IndexReader reader) throws IOException { if (!isValid()) { return null; } final Filter[] array = filters.toArray(new Filter[filters.size()]); return and(array).getDocIdSet(reader); } /** * Returns if this filter collection is valid (there's at least one filter on it) */ public boolean isValid() { return CollectionUtils.isNotEmpty(filters); } }