blob: c4b9d9c0f7723022d35a99dc77826c50e42cbf2f [file] [log] [blame]
package org.apache.archiva.components.rest.util;/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
/**
*
* Helper class that returns combined filter and comparison objects for ordering.
*
* The query term may be consist of simple query terms separated by whitespace or attribute queries
* in the form <code>attribute:query</code>, which means only the attribute is searched for the query string.
* <br />
* Example:
* <dl>
* <dt>`user1 test`</dt>
* <dd>
* searches for the tokens user1 and test in the default attributes.
* </dd>
* <dt>`user1 name:test`</dt>
* <dd>searches for the token user1 in the default attributes and for the token test in the attribute name.</dd>
* </dl>
*
*
* @since 3.0
* @author Martin Stockhammer <martin_s@apache.org>
*/
public class QueryHelper<T>
{
private final Map<String, BiPredicate<String, T>> FILTER_MAP;
private final Map<String, Comparator<T>> ORDER_MAP;
private final String[] DEFAULT_SEARCH_FIELDS;
private final Predicate<T> DEFAULT_FILTER = ( T att ) -> false;
/**
* Creates a new query helper with the given filters and comparators.
*
* @param filterMap a map of filters, where the key is the attribute name and the value is a predicate that matches
* the filter value and the object instance.
* @param orderMap a map of comparators, where key is the attribute name and the value is a comparator for the given
* object instance
* @param defaultSearchFields A array of attribute names, that are used as default search fields.
*/
public QueryHelper(Map<String, BiPredicate<String, T>> filterMap, Map<String, Comparator<T>> orderMap,
String[] defaultSearchFields)
{
this.FILTER_MAP = filterMap;
this.DEFAULT_SEARCH_FIELDS = defaultSearchFields;
this.ORDER_MAP = new HashMap<>( orderMap );
}
public <U extends Comparable<? super U>> void addNullsafeFieldComparator( String fieldName, Function<? super T, U> keyExtractor) {
ORDER_MAP.put( fieldName, Comparator.comparing( keyExtractor, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) );
}
public void addStringFilter(String attribute, Function<? super T, String> keyExtractor) {
this.FILTER_MAP.put( attribute, ( String q, T r ) -> StringUtils.containsIgnoreCase( keyExtractor.apply( r ), q ) );
}
public void addBooleanFilter(String attribute, Function<? super T, Boolean> keyExtractor) {
this.FILTER_MAP.put( attribute, ( String q, T r ) -> Boolean.valueOf( q ) == keyExtractor.apply( r ) );
}
/**
* Get the comparator for a specific attribute.
* @param attributeName the name of the attribute.
* @return
*/
public Comparator<T> getAttributeComparator( String attributeName )
{
return ORDER_MAP.get( attributeName );
}
/**
* Get the combined order for the given attributes in the given order.
*
* @param orderBy the attributes to compare. The first attribute in the list will be used first for comparing.
* @param ascending
* @return
*/
public Comparator<T> getComparator( List<String> orderBy, boolean ascending )
{
if ( ascending )
{
return orderBy.stream( ).map( ( String name ) -> getAttributeComparator( name ) ).filter( Objects::nonNull )
.reduce( Comparator::thenComparing )
.orElseThrow( () -> new IllegalArgumentException( "No attribute ordering found" ) );
}
else
{
return orderBy.stream( ).map( ( String name ) -> getAttributeComparator( name ) == null ? null : getAttributeComparator( name )
.reversed( ) ).filter( Objects::nonNull ).reduce( Comparator::thenComparing )
.orElseThrow( () -> new IllegalArgumentException( "No attribute ordering found" ) );
}
}
/**
* Returns a query filter for a specific attribute and query token.
* @param attribute the attribute name to filter for.
* @param queryToken the search token.
* @return The predicate used to filter the token
*/
public Predicate<T> getAttributeQueryFilter( final String attribute, final String queryToken )
{
if ( FILTER_MAP.containsKey( attribute ) )
{
return ( T u ) -> FILTER_MAP.get( attribute ).test( queryToken, u );
}
else
{
return DEFAULT_FILTER;
}
}
/**
* Returns the combined query filter for the given query terms.
* The query terms may be either simple strings separated by whitespace or use the
* <code>attribute:query</code> syntax, that searches only the attribute for the query term.
* @param queryTerms the query string
* @return the combined query filter
*/
public Predicate<T> getQueryFilter( String queryTerms )
{
return Arrays.stream( queryTerms.split( "\\s+" ) )
.map( s -> {
if ( s.contains( ":" ) )
{
String attr = StringUtils.substringBefore( s, ":" );
String term = StringUtils.substringAfter( s, ":" );
return getAttributeQueryFilter( attr, term );
}
else
{
return Arrays.stream( DEFAULT_SEARCH_FIELDS )
.map( att -> getAttributeQueryFilter( att, s ) ).reduce( Predicate::or ).get( );
}
}
).reduce( Predicate::or ).get( );
}
}