Skip to content

Commit

Permalink
Major refactoring to support or/and operations in filters. (#58)
Browse files Browse the repository at this point in the history
* Major refactoring to support or/and operations in filters.

BREAKING API CHANGES!

- Moving static utility functions into `Filters`
- Moving conditional logic of `Filters` into own sub-classes
- Support of Rule-parameters and construction of custom `FilterRule`s in serialization
- Support of or/and-filters in serialization
- Re-design or proto3 data-model
- Fixing Sonar issues
- `DtoPageRequest`: Support of and/or
- Better example in API
- Support OR as root-list in request
- jacoco test report generation
  • Loading branch information
p3t authored Oct 24, 2024
1 parent bbcd412 commit 9bcbddc
Show file tree
Hide file tree
Showing 46 changed files with 1,814 additions and 455 deletions.
185 changes: 122 additions & 63 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies {
implementation("org.kordamp.gradle:project-gradle-plugin:$kordampVersion")
implementation("org.kordamp.gradle:spotbugs-gradle-plugin:$kordampVersion")
implementation("org.kordamp.gradle:coveralls-gradle-plugin:$kordampVersion")
implementation("org.sonarqube:org.sonarqube.gradle.plugin:5.1.0.4882")
// implementation("org.kordamp.gradle:base-gradle-plugin:$kordampVersion")
// implementation("org.kordamp.gradle:jacoco-gradle-plugin:$kordampVersion")
implementation("gradle.plugin.org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.12.2")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent

// File: buildSrc/src/main/kotlin/java-project-conventions.gradle.kts
plugins {
id("java-library")
Expand All @@ -9,6 +12,7 @@ plugins {
id("org.kordamp.gradle.spotbugs")
id("com.github.kt3k.coveralls")
id("org.kordamp.gradle.coveralls")
id("org.sonarqube")
}

group = "io.vigier.cursorpaging"
Expand All @@ -35,14 +39,29 @@ tasks.named<Jar>("bootJar") {
tasks {
jacocoTestReport {
dependsOn(tasks.test) // tests are required to run before generating the report
reports {
xml.required = true
html.required = true
}
}
delombok {
enabled = false
}
jar {
archiveClassifier.set("") // needed to remove "-plain" when bootJar = false
}
test {
finalizedBy(tasks.jacocoTestReport)
testLogging {
events(TestLogEvent.FAILED, TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.STANDARD_OUT)
showExceptions = true
showCauses = true
showStackTraces = true
exceptionFormat = TestExceptionFormat.FULL
}
}
}

config {
info {
inceptionYear = "2024"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import io.vigier.cursorpaging.jpa.PageRequest;
import io.vigier.cursorpaging.jpa.api.DtoPageRequest;
import io.vigier.cursorpaging.jpa.serializer.Base64String;
import io.vigier.cursorpaging.jpa.serializer.EntitySerializer;
import io.vigier.cursorpaging.jpa.serializer.RequestSerializer;
import io.vigier.cursorpaging.jpa.validation.MaxSize;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -55,7 +55,7 @@ public class DataRecordController {
// Skipping the service layer ;-)
private final DataRecordRepository dataRecordRepository;
private final DtoDataRecordMapper dtoDataRecordMapper;
private final EntitySerializer<DataRecord> serializer;
private final RequestSerializer<DataRecord> serializer;

@Operation( summary = "Get data records, page by page" )
@GetMapping( produces = MediaType.APPLICATION_JSON_VALUE )
Expand Down Expand Up @@ -123,16 +123,17 @@ public RepresentationModel<?> getCursor(
mediaType = MediaType.APPLICATION_JSON_VALUE, //
schema = @Schema( implementation = DtoPageRequest.class, example = """
{
"orderBy": {
"NAME": "ASC"
},
"filterBy": {
"NAME": [
"Bravo", "Tango"
]
},
"pageSize": 100
}""" ) ) ) //
"orderBy": { "id": "ASC" },
"filterBy": {
"AND": [
{ "EQ": { "name": [ "Bravo" ] } },
{ "GT": { "created_at": [ "1999-01-30T10:15:30Z" ] } }
]
},
"pageSize": 10,
"withTotalCount": false
}
""" ) ) ) //
@RequestBody final DtoPageRequest request ) {
request.addOrderByIfAbsent( DataRecord_.ID, Order.ASC );
final PageRequest<DataRecord> pageRequest = request.toPageRequest( DataRecordAttribute::forName );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public enum DataRecordAttribute {
// Not using the "lowercase" properties here, because there are tests where no EntityManager is instantiated
// (i.e. the MockMVC test), but this one is needed to populate them :-(

ID( Attribute.of( DataRecord_.NAME, UUID.class ) ),
ID( Attribute.of( DataRecord_.ID, UUID.class ) ),
NAME( Attribute.of( DataRecord_.NAME, String.class ) ),
CREATED_AT( Attribute.path( DataRecord_.AUDIT_INFO, AuditInfo.class, AuditInfo_.CREATED_AT, Instant.class ) ),
MODIFIED_AT( Attribute.path( DataRecord_.AUDIT_INFO, AuditInfo.class, AuditInfo_.MODIFIED_AT, Instant.class ) );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,30 @@

import io.vigier.cursorpaging.example.webapp.model.DataRecord;
import io.vigier.cursorpaging.jpa.serializer.Encrypter;
import io.vigier.cursorpaging.jpa.serializer.EntitySerializer;
import io.vigier.cursorpaging.jpa.serializer.EntitySerializerFactory;
import io.vigier.cursorpaging.jpa.serializer.RequestSerializer;
import io.vigier.cursorpaging.jpa.serializer.RequestSerializerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;

@Configuration
public class EntitySerializerConfig {
public class RequestSerializerConfig {

@Value( "${cursorpaging.jpa.serializer.encrypter.secret:1234567890ABCDEFGHIJKlmnopqrst--}" )
private String encrypterSecret;

@Bean
public EntitySerializerFactory entitySerializerFactory( final ConversionService conversionService ) {
return EntitySerializerFactory.builder()
public RequestSerializerFactory requestSerializerFactory( final ConversionService conversionService ) {
return RequestSerializerFactory.builder()
.conversionService( conversionService )
.encrypter( Encrypter.getInstance( encrypterSecret ) )
.build();
}

@Bean
public EntitySerializer<DataRecord> dataRecordEntitySerializer( final EntitySerializerFactory serializerFactory ) {
public RequestSerializer<DataRecord> dataRecordRequestSerializer(
final RequestSerializerFactory serializerFactory ) {
return serializerFactory.forEntity( DataRecord.class );
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<html lang="en">
<!DOCTYPE html>
<html lang="en" title="Spring-CursorPaging WebApp">
<head>
<title>Spring-CursorPaging Example WebApp</title>
</head>
<body>
<h2>Cursor Paging Test App</h2>
<p>API</p>
<a href="swagger-ui/index.html">swagger-ui</a>
<h1><i>Spring-CursorPaging</i> Example API</h1>
<a href="swagger-ui/index.html" style="border: blue 1px solid">swagger-ui</a>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package io.vigier.cursorpaging.example.webapp;

import java.sql.Connection;
import java.sql.SQLException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.retry.annotation.Retryable;
import org.springframework.test.web.servlet.MockMvc;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
Expand Down Expand Up @@ -35,14 +38,29 @@ private static PostgreSQLContainer<?> postgresContainer() {
DockerImageName.parse( "postgres:latest" ) );
container.start();
log.info( "PostgreSQL exposed ports: {}", container.getExposedPorts() );
// waitForConnectionReady( container );
waitForConnectionReady( container );

return container;
}

@Retryable( retryFor = SQLException.class,
maxAttempts = 5,
backoff = @org.springframework.retry.annotation.Backoff( delay = 1000 ) )
private static void waitForConnectionReady( final PostgreSQLContainer<?> container ) throws SQLException {
final String jdbcUrl = container.getJdbcUrl();
try ( final Connection con = container.createConnection( jdbcUrl.substring( jdbcUrl.indexOf( '?' ) ) ) ) {
if ( !con.isValid( 1000 ) ) {
throw new IllegalStateException( "Cannot connect to: " + jdbcUrl );
}
log.info( "Connection successfully to JDBC URL {}", jdbcUrl );
} catch ( final SQLException e ) {
log.error( "Error connecting to: " + jdbcUrl, e );
throw e;
}
}
@Test
void contextLoads() {

// Fails when Spring's ApplicationContext cannot be created
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
import io.vigier.cursorpaging.jpa.Order;
import io.vigier.cursorpaging.jpa.PageRequest;
import io.vigier.cursorpaging.jpa.api.DtoPageRequest;
import io.vigier.cursorpaging.jpa.api.DtoPageRequest.DtoAndFilter;
import io.vigier.cursorpaging.jpa.api.DtoPageRequest.DtoEqFilter;
import io.vigier.cursorpaging.jpa.serializer.Base64String;
import io.vigier.cursorpaging.jpa.serializer.EntitySerializer;
import io.vigier.cursorpaging.jpa.serializer.RequestSerializer;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.hamcrest.Matchers;
Expand Down Expand Up @@ -48,7 +52,7 @@ class DataRecordControllerTest {
private DtoDataRecordMapper dtoDataRecordMapper;

@MockBean
private EntitySerializer<DataRecord> serializer;
private RequestSerializer<DataRecord> serializer;

@Test
void shouldValidateMaxPageSize() throws Exception {
Expand Down Expand Up @@ -89,9 +93,16 @@ void shouldReturnCountUsingCursor() throws Exception {

@Test
void shouldCreateNewCursorOnPost() throws Exception {
final var request = new DtoPageRequest().withFilterBy( DataRecord_.NAME, "Tango", "Bravo" )
.withOrderBy( DataRecord_.NAME, Order.ASC )
.withPageSize( 10 );
final var request = DtoPageRequest.builder()
.filterBy( DtoAndFilter.builder()
.filter( DtoEqFilter.builder()
.attribute( DataRecord_.NAME )
.values( List.of( "Tango", "Bravo" ) )
.build() )
.build() )
.orderBy( Map.of( DataRecord_.NAME, Order.ASC ) )
.pageSize( 10 )
.build();
final String json = new ObjectMapper().writeValueAsString( request );
log.debug( "Json:, {}", json );

Expand All @@ -103,9 +114,10 @@ void shouldCreateNewCursorOnPost() throws Exception {
.andExpect( status().isCreated() ) //
.andExpect( jsonPath( "$.orderBy.name" ).value( "ASC" ) )
.andExpect( jsonPath( "$.orderBy.id" ).value( "ASC" ) )
.andExpect( jsonPath( "$.filterBy.name" ).isArray() )
.andExpect( jsonPath( "$.filterBy.name[0]" ).value( "Tango" ) )
.andExpect( jsonPath( "$.filterBy.name[1]" ).value( "Bravo" ) )
.andExpect( jsonPath( "$.filterBy.AND" ).isArray() )
.andExpect( jsonPath( "$.filterBy.AND[0].EQ.name" ).isArray() )
.andExpect( jsonPath( "$.filterBy.AND[0].EQ.name[0]" ).value( "Tango" ) )
.andExpect( jsonPath( "$.filterBy.AND[0].EQ.name[1]" ).value( "Bravo" ) )
.andExpect( jsonPath( "$.pageSize" ).value( 10 ) ) //
.andExpect( jsonPath( "$._links.first.href" ).exists() )
.andExpect( jsonPath( "$._links.first.href" ).value( Matchers.containsString( CURSOR ) ) );
Expand Down
1 change: 1 addition & 0 deletions cursorpaging-jpa-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {
implementation("com.google.protobuf:protobuf-java:4.28.2")
api("org.springframework:spring-core")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("com.fasterxml.jackson.core:jackson-databind")
api("jakarta.validation:jakarta.validation-api")
api("jakarta.persistence:jakarta.persistence-api:3.2.0")

Expand Down
Loading

0 comments on commit 9bcbddc

Please sign in to comment.