diff --git a/cursorpaging-jpa/src/main/java/io/vigier/cursorpaging/jpa/Filter.java b/cursorpaging-jpa/src/main/java/io/vigier/cursorpaging/jpa/Filter.java index 6350d1b..40fa09f 100644 --- a/cursorpaging-jpa/src/main/java/io/vigier/cursorpaging/jpa/Filter.java +++ b/cursorpaging-jpa/src/main/java/io/vigier/cursorpaging/jpa/Filter.java @@ -2,6 +2,7 @@ import jakarta.persistence.metamodel.SingularAttribute; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import lombok.Builder; import lombok.EqualsAndHashCode; @@ -9,6 +10,7 @@ import lombok.Singular; import lombok.With; import lombok.experimental.Accessors; +import org.springframework.util.StringUtils; /** * A filter, which can be used to filter a query (remove elements from the result). @@ -81,10 +83,17 @@ public static Filter attributeIs( final SingularAttribute 1 ) { - qb.andWhere( qb.isIn( attribute, values ) ); - } else { - qb.andWhere( qb.equalTo( attribute, values.get( 0 ) ) ); + final List> filterValues = values.stream().filter( Objects::nonNull ).toList(); + if ( filterValues.size() > 1 ) { + qb.andWhere( qb.isIn( attribute, filterValues ) ); + } else if ( filterValues.size() == 1 ) { + qb.andWhere( qb.equalTo( attribute, filterValues.get( 0 ) ) ); } } + + public boolean isEmpty() { + return values.isEmpty() || values.stream() + .allMatch( v -> Objects.isNull( v ) || (v instanceof final CharSequence cs && !StringUtils.hasText( + cs )) ); + } } diff --git a/cursorpaging-jpa/src/main/java/io/vigier/cursorpaging/jpa/PageRequest.java b/cursorpaging-jpa/src/main/java/io/vigier/cursorpaging/jpa/PageRequest.java index dadf039..db3e18a 100644 --- a/cursorpaging-jpa/src/main/java/io/vigier/cursorpaging/jpa/PageRequest.java +++ b/cursorpaging-jpa/src/main/java/io/vigier/cursorpaging/jpa/PageRequest.java @@ -3,6 +3,8 @@ import jakarta.persistence.Transient; import jakarta.persistence.metamodel.SingularAttribute; import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; import java.util.List; import java.util.function.Consumer; import lombok.Builder; @@ -44,8 +46,8 @@ public class PageRequest { /** * The filters to apply to the query (removing results) */ - @Singular - private final List filters; + @Builder.Default + private final List filters = new LinkedList<>(); /** * The filter rules to apply to the query (removing results). Note that filter rules are not passed to the @@ -120,6 +122,38 @@ public PageRequestBuilder desc( final Attribute attribute ) { return addPosition( Position.create( b -> b.attribute( attribute ).order( Order.DESC ) ) ); } + /** + * Add a filter to the request. Filter which do not contain a filter value or empty char-sequences as values are + * silently ignored for convenience reasons when creating page requests out of query parameters. + * + * @param filter A new filter definition + * @return the builder + */ + public PageRequestBuilder filter( final Filter filter ) { + if ( this.filters$value == null ) { + this.filters$value = new LinkedList<>(); + } + if ( filter != null && !filter.isEmpty() ) { + this.filters$value.add( filter ); + this.filters$set = true; + } + return this; + } + + /** + * Add a list of filters to the page request. Filter which do not contain a filter value or empty char-sequences + * as values are silently ignored for convenience reasons when creating page requests out of query parameters. + * + * @param filters the list of filters to be added + * @return the builder + */ + public PageRequestBuilder filters( final Collection filters ) { + if ( filters != null ) { + filters.forEach( this::filter ); + } + return this; + } + private PageRequestBuilder addPosition( final Position pos ) { if ( this.positions == null ) { this.positions = new ArrayList<>( 3 ); diff --git a/cursorpaging-jpa/src/test/java/io/vigier/cursorpaging/jpa/PageRequestTest.java b/cursorpaging-jpa/src/test/java/io/vigier/cursorpaging/jpa/PageRequestTest.java new file mode 100644 index 0000000..34c533c --- /dev/null +++ b/cursorpaging-jpa/src/test/java/io/vigier/cursorpaging/jpa/PageRequestTest.java @@ -0,0 +1,33 @@ +package io.vigier.cursorpaging.jpa; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PageRequestTest { + + @ParameterizedTest + @NullAndEmptySource + @ValueSource( strings = { " ", "\t", "\n" } ) + void shouldIgnoreEmptyFilter( final String value ) { + final var pageRequest = PageRequest.create( b -> b.asc( Attribute.of( "id", Long.class ) ) + .filter( Filter.create( f -> f.attribute( Attribute.of( "test", String.class ) ).value( value ) ) ) ); + + assertThat( pageRequest.filters() ).isEmpty(); + } + + @Test + void shouldAddPositionAndFilterIfValuePresent() { + final var pageRequest = PageRequest.create( b -> b.asc( Attribute.of( "id", Long.class ) ) + .filter( Filter.create( f -> f.attribute( Attribute.of( "test", String.class ) ).value( "value" ) ) ) ); + + assertThat( pageRequest.filters() ).hasSize( 1 ); + assertThat( pageRequest.positions() ).hasSize( 1 ).first().satisfies( p -> { + assertThat( p.attribute().name() ).isEqualTo( "id" ); + assertThat( p.order() ).isEqualTo( Order.ASC ); + } ); + } +} \ No newline at end of file