/** * Given an ApiFilter, returns a stream of term queries, one for each value in the filter. * * @param luceneFieldName Name of the lucene field to filter on * @param filter The filter to be turned into term queries * * @return A stream of term queries */ private Stream<TermQuery> filterToTermQueries(String luceneFieldName, ApiFilter filter) { return filter.getValues().stream() .map(value -> new Term(luceneFieldName, value)) .map(TermQuery::new); }
@Override public Pagination<DimensionRow> findFilteredDimensionRowsPaged( Set<ApiFilter> filters, PaginationParameters paginationParameters ) { return new AllPagesPagination<>( filters.stream() .flatMap(f -> f.getValues().stream()) .map(this::makeDimensionRow) .collect(Collectors.toCollection(TreeSet::new)), paginationParameters ); } }
@Override public TreeSet<DimensionRow> notinFilterOperation(TreeSet<DimensionRow> dimensionRows, ApiFilter filter) { TreeSet<DimensionRow> filteredDimensionRows = new TreeSet<>(dimensionRows); for (DimensionRow dimensionRow : dimensionRows) { String value = dimensionRow.get(filter.getDimensionField()); if (filter.getValues().contains(value)) { filteredDimensionRows.remove(dimensionRow); } } return filteredDimensionRows; }
/** * Contains filter operation. * * @param luceneFieldName Name of the lucene field to filter on * @param filter New filter to add to the query * * @return A builder that knows how to build the appropriate BooleanQuery */ private BooleanQuery containsFilterQuery(String luceneFieldName, ApiFilter filter) { return filter.getValues().stream() .map(value -> new Term(luceneFieldName, "*" + value + "*")) .map(WildcardQuery::new) .collect(getBooleanQueryCollector(BooleanClause.Occur.SHOULD)) .build(); }
/** * Startswith-filter operation. * * @param luceneFieldName Name of the lucene field to filter on * @param filter New filter to add to the query * * @return A builder that knows how to build the appropriate BooleanQuery */ private BooleanQuery startswithFilterQuery(String luceneFieldName, ApiFilter filter) { return filter.getValues().stream() .map(value -> new Term(luceneFieldName, value)) .map(PrefixQuery::new) .collect(getBooleanQueryCollector(BooleanClause.Occur.SHOULD)) .build(); }
@Override public TreeSet<DimensionRow> inFilterOperation(TreeSet<DimensionRow> dimensionRows, ApiFilter filter) { return dimensionRows.stream() .filter(row -> filter.getValues().contains(row.get(filter.getDimensionField()))) .collect(Collectors.toCollection(TreeSet<DimensionRow>::new)); }
@Override public TreeSet<DimensionRow> startswithFilterOperation( TreeSet<DimensionRow> dimensionRows, ApiFilter filter ) { TreeSet<DimensionRow> filteredDimensionRows = new TreeSet<>(); // regex string containing all starts with filter values StringBuilder startsWithRegex = new StringBuilder("("); for (String filterValue : filter.getValues()) { startsWithRegex.append(filterValue).append("|"); } startsWithRegex.replace(startsWithRegex.length() - 1, startsWithRegex.length(), ").*"); String startsWithRegexString = startsWithRegex.toString(); for (DimensionRow dimensionRow : dimensionRows) { String value = dimensionRow.get(filter.getDimensionField()); if (value.matches(startsWithRegexString)) { filteredDimensionRows.add(dimensionRow); } } return filteredDimensionRows; }
/** * Contains filter operation. * * @param dimensionRows The unfiltered set of dimension rows * @param filter The api filter * * @return Tree set of DimensionRows */ @Override public TreeSet<DimensionRow> containsFilterOperation(TreeSet<DimensionRow> dimensionRows, ApiFilter filter) { TreeSet<DimensionRow> filteredDimensionRows = new TreeSet<>(); // regex string containing all contains filter values StringBuilder containsRegex = new StringBuilder(".*("); for (String filterValue : filter.getValues()) { containsRegex.append(filterValue).append("|"); } containsRegex.replace(containsRegex.length() - 1, containsRegex.length(), ").*"); for (DimensionRow dimensionRow : dimensionRows) { String value = dimensionRow.get(filter.getDimensionField()); if (value.matches(containsRegex.toString())) { filteredDimensionRows.add(dimensionRow); } } return filteredDimensionRows; }
/** * Take two Api filters which differ only by value sets and union their value sets. * * @param one The first ApiFilter * @param two The second ApiFilter * * @return an ApiFilter with the union of values */ public ApiFilter union(ApiFilter one, ApiFilter two) { if (Objects.equals(one.getDimension(), two.getDimension()) && Objects.equals(one.getDimensionField(), two.getDimensionField()) && Objects.equals(one.getOperation(), two.getOperation()) ) { Set<String> values = Stream.concat( one.getValues().stream(), two.getValues().stream() ) .collect(Collectors.toSet()); return one.withValues(values); } throw new IllegalArgumentException(String.format("Unmergable ApiFilters '%s' and '%s'", one, two)); } }
/** * Validity rules for non-aggregatable dimensions that are only referenced in filters. * A query that references a non-aggregatable dimension in a filter without grouping by this dimension, is valid * only if the requested dimension field is a key for this dimension and only a single value is requested * with an inclusive operator ('in' or 'eq'). * * @return A predicate that determines a given dimension is non aggregatable and also not constrained to one row * per result */ protected static Predicate<ApiFilter> isNonAggregatableInFilter() { return apiFilter -> !apiFilter.getDimensionField().equals(apiFilter.getDimension().getKey()) || apiFilter.getValues().size() != 1 || !( apiFilter.getOperation().equals(DefaultFilterOperation.in) || apiFilter.getOperation().equals(DefaultFilterOperation.eq) ); }
/** * Constructor. * * @param filter The filter being recorded */ public Filter(ApiFilter filter) { this.dimension = filter.getDimension().getApiName(); this.field = filter.getDimensionField().getName(); this.operator = filter.getOperation().getName(); this.numberOfValues = filter.getValues().size(); }