Skip to content

Commit 046ff90

Browse files
authored
FINC-1118 Support jOOQ sort Field<?> mappings (#55)
* put `@NullMarked` closest to the target * add jOOQ dependency and support jOOQ Field in SortMappings * add jOOQ as an optional dependency and support jOOQ Field in SortMappings in new class SortMappingsForJooq * add jOOQ as an optional dependency and support jOOQ Field in SortMappings in new class SortMappingsForJooq * accidentally committed local version 2.2.1-SNAPSHOT * move the JSpecify annotations closest to it's target * implement review feedback * remove unused imports
1 parent 9ba0a26 commit 046ff90

7 files changed

Lines changed: 162 additions & 39 deletions

File tree

pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<properties>
1919
<java.version>25</java.version>
2020
<errorprone.version>2.46.0</errorprone.version>
21+
<jooq.version>3.20.11</jooq.version>
2122
<nullaway.version>0.12.15</nullaway.version>
2223
</properties>
2324

@@ -52,6 +53,15 @@
5253
<artifactId>spring-security-core</artifactId>
5354
</dependency>
5455

56+
<!-- jOOQ -->
57+
<dependency>
58+
<groupId>org.jooq</groupId>
59+
<artifactId>jooq</artifactId>
60+
<version>${jooq.version}</version>
61+
<scope>provided</scope>
62+
<optional>true</optional>
63+
</dependency>
64+
5565
<!-- Utilities -->
5666
<dependency>
5767
<groupId>org.projectlombok</groupId>

src/main/java/it/aboutbits/springboot/toolbox/exception/ConstraintViolationExceptionBuilder.java

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,27 +61,23 @@ public String getMessage() {
6161
return message;
6262
}
6363

64-
@Nullable
6564
@Override
66-
public String getMessageTemplate() {
65+
public @Nullable String getMessageTemplate() {
6766
return null;
6867
}
6968

70-
@Nullable
7169
@Override
72-
public Object getRootBean() {
70+
public @Nullable Object getRootBean() {
7371
return null;
7472
}
7573

76-
@Nullable
7774
@Override
78-
public Class<Object> getRootBeanClass() {
75+
public @Nullable Class<Object> getRootBeanClass() {
7976
return null;
8077
}
8178

82-
@Nullable
8379
@Override
84-
public Object getLeafBean() {
80+
public @Nullable Object getLeafBean() {
8581
return null;
8682
}
8783

@@ -90,9 +86,8 @@ public Object[] getExecutableParameters() {
9086
return new Object[0];
9187
}
9288

93-
@Nullable
9489
@Override
95-
public Object getExecutableReturnValue() {
90+
public @Nullable Object getExecutableReturnValue() {
9691
return null;
9792
}
9893

@@ -101,21 +96,18 @@ public Path getPropertyPath() {
10196
return propertyPath;
10297
}
10398

104-
@Nullable
10599
@Override
106-
public Object getInvalidValue() {
100+
public @Nullable Object getInvalidValue() {
107101
return null;
108102
}
109103

110-
@Nullable
111104
@Override
112-
public ConstraintDescriptor<?> getConstraintDescriptor() {
105+
public @Nullable ConstraintDescriptor<?> getConstraintDescriptor() {
113106
return null;
114107
}
115108

116-
@Nullable
117109
@Override
118-
public <U> U unwrap(Class<U> type) {
110+
public @Nullable <U> U unwrap(Class<U> type) {
119111
return null;
120112
}
121113
}

src/main/java/it/aboutbits/springboot/toolbox/parameter/SortParameter.java

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
@NullMarked
2525
public final class SortParameter<T extends Enum<?> & SortParameter.Definition> {
2626
private static final String DEFAULT_SORT_PROPERTY = "id";
27-
private static final Sort.Direction DEFAULT_SORT_DIRETION = Sort.Direction.ASC;
27+
private static final Sort.Direction DEFAULT_SORT_DIRECTION = Sort.Direction.ASC;
2828

2929
@Accessors(fluent = true)
3030
@Getter
@@ -226,11 +226,11 @@ public SortParameter<T> or(SortParameter<T> fallback) {
226226
* @return an instance of {@link Sort} created using the transformed key-value mapping,
227227
* excluding default sorting behavior.
228228
*/
229-
public Sort buildSortWithoutDefault(Map<T, String> mapping) {
229+
public Sort buildSortWithoutDefault(Map<T, ?> mapping) {
230230
var stringMapping = mapping.entrySet().stream()
231231
.collect(Collectors.toMap(
232232
entry -> entry.getKey().name(),
233-
Map.Entry::getValue
233+
entry -> convertToString(entry.getValue())
234234
));
235235

236236
return buildSort(stringMapping, false);
@@ -246,16 +246,48 @@ public Sort buildSortWithoutDefault(Map<T, String> mapping) {
246246
* The enumeration keys must implement the `name()` method to retrieve their string representation.
247247
* @return an instance of {@link Sort} created using the transformed key-value mapping with default sorting behavior.
248248
*/
249-
public Sort buildSort(Map<T, String> mapping) {
249+
public Sort buildSort(Map<T, ?> mapping) {
250250
var stringMapping = mapping.entrySet().stream()
251251
.collect(Collectors.toMap(
252252
entry -> entry.getKey().name(),
253-
Map.Entry::getValue
253+
entry -> convertToString(entry.getValue())
254254
));
255255

256256
return buildSort(stringMapping, true);
257257
}
258258

259+
private static String convertToString(Object value) {
260+
if (value instanceof String string) {
261+
return string;
262+
}
263+
264+
// Use reflection to get the column name from a jOOQ Field<?> using the Field<?>.getName() method
265+
// This allows us to have jOOQ as an optional dependency and not break existing projects that use Hibernate
266+
try {
267+
var fieldClass = Class.forName("org.jooq.Field");
268+
if (!fieldClass.isInstance(value)) {
269+
throw new IllegalArgumentException(
270+
"Value must be either a String or org.jooq.Field, but was: " + value.getClass().getName()
271+
);
272+
}
273+
274+
var getName = fieldClass.getMethod("getName");
275+
return (String) getName.invoke(value);
276+
} catch (ClassNotFoundException e) {
277+
throw new IllegalArgumentException(
278+
"jOOQ library not found. Cannot process Field<?> value. Make sure jOOQ is on the classpath.",
279+
e
280+
);
281+
} catch (Exception e) {
282+
throw new IllegalArgumentException(
283+
"Cannot get the jOOQ Field name from the mapped SortMappings$Mapping column value [value.class.name=%s]".formatted(
284+
value.getClass().getName()
285+
),
286+
e
287+
);
288+
}
289+
}
290+
259291
private Sort buildSort(Map<String, String> mapping, boolean withDefault) {
260292
if (sortFields.isEmpty()) {
261293
return withDefault ? getMappedDefaultSort(mapping) : Sort.unsorted();
@@ -289,7 +321,7 @@ private Sort buildSort(Map<String, String> mapping, boolean withDefault) {
289321
}
290322

291323
private static Sort getMappedDefaultSort(Map<String, String> mapping) {
292-
return Sort.by(DEFAULT_SORT_DIRETION, mapping.getOrDefault(DEFAULT_SORT_PROPERTY, DEFAULT_SORT_PROPERTY));
324+
return Sort.by(DEFAULT_SORT_DIRECTION, mapping.getOrDefault(DEFAULT_SORT_PROPERTY, DEFAULT_SORT_PROPERTY));
293325
}
294326

295327
public record SortField(

src/main/java/it/aboutbits/springboot/toolbox/persistence/SortMappings.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,16 @@
55

66
import java.util.HashMap;
77

8+
/// A mapping of sort parameter definitions to their corresponding database columns.
9+
///
10+
/// The values in this map are of type [Object] and are expected to be either:
11+
/// - [String] - for column names when using Hibernate/JPA
12+
/// - `org.jooq.Field<?>` - for jOOQ field objects when using jOOQ
13+
///
14+
/// @param <T> the enum type that implements [SortParameter.Definition]
815
@NullMarked
9-
public final class SortMappings<T extends Enum<?> & SortParameter.Definition> extends HashMap<T, String> {
10-
private SortMappings() {
16+
public class SortMappings<T extends Enum<?> & SortParameter.Definition> extends HashMap<T, Object> {
17+
SortMappings() {
1118
super();
1219
}
1320

@@ -31,6 +38,6 @@ public static <T extends Enum<?> & SortParameter.Definition> SortMappings<T> of(
3138
return sortMappings;
3239
}
3340

34-
public record Mapping<T extends Enum<?> & SortParameter.Definition>(T property, String column) {
41+
public record Mapping<T extends Enum<?> & SortParameter.Definition>(T property, Object column) {
3542
}
3643
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package it.aboutbits.springboot.toolbox.persistence;
2+
3+
import it.aboutbits.springboot.toolbox.parameter.SortParameter;
4+
import org.jooq.Field;
5+
import org.jspecify.annotations.NullMarked;
6+
7+
@NullMarked
8+
public final class SortMappingsForJooq<T extends Enum<?> & SortParameter.Definition> extends SortMappings<T> {
9+
private SortMappingsForJooq() {
10+
super();
11+
}
12+
13+
public static <T extends Enum<?> & SortParameter.Definition> Mapping<T> map(
14+
T property,
15+
Field<?> column
16+
) {
17+
return new Mapping<>(property, column);
18+
}
19+
20+
@SafeVarargs
21+
public static <T extends Enum<?> & SortParameter.Definition> SortMappingsForJooq<T> of(
22+
Mapping<T>... mappings
23+
) {
24+
var sortMappings = new SortMappingsForJooq<T>();
25+
26+
for (var mapping : mappings) {
27+
sortMappings.put(mapping.property(), mapping.column());
28+
}
29+
30+
return sortMappings;
31+
}
32+
}

src/main/java/it/aboutbits/springboot/toolbox/swagger/SwaggerMeta.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,22 @@
1313
@JsonInclude(JsonInclude.Include.NON_NULL)
1414
@NullMarked
1515
public class SwaggerMeta {
16-
@Nullable
17-
private String originalTypeFqn = null;
16+
private @Nullable String originalTypeFqn = null;
1817

19-
@Nullable
2018
@JsonProperty("isIdentity")
21-
private Boolean isIdentity = null;
19+
private @Nullable Boolean isIdentity = null;
2220

23-
@Nullable
2421
@JsonProperty("isCustomType")
25-
private Boolean isCustomType = null;
22+
private @Nullable Boolean isCustomType = null;
2623

27-
@Nullable
2824
@JsonProperty("isNestedStructure")
29-
private Boolean isNestedStructure = null;
25+
private @Nullable Boolean isNestedStructure = null;
3026

31-
@Nullable
3227
@JsonProperty("isMap")
33-
private Boolean isMap = null;
28+
private @Nullable Boolean isMap = null;
3429

35-
@Nullable
36-
private String mapKeyTypeFqn = null;
30+
private @Nullable String mapKeyTypeFqn = null;
3731

38-
@Nullable
3932
@JsonProperty("isNullable")
40-
private Boolean isNullable = null;
33+
private @Nullable Boolean isNullable = null;
4134
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package it.aboutbits.springboot.toolbox.persistence;
2+
3+
import it.aboutbits.springboot.toolbox.parameter.SortParameter;
4+
import org.jooq.impl.DSL;
5+
import org.jspecify.annotations.NullMarked;
6+
import org.junit.jupiter.api.DisplayName;
7+
import org.junit.jupiter.api.Test;
8+
import org.springframework.data.domain.Sort;
9+
10+
import java.util.Objects;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
14+
@NullMarked
15+
class SortMappingsForJooqTest {
16+
private enum ESort implements SortParameter.Definition {
17+
Property1,
18+
Property2
19+
}
20+
21+
@Test
22+
@DisplayName("Method map should return a mapping with a jOOQ Field object")
23+
void map_shouldReturnMappingWithFieldObject() {
24+
var field = DSL.field("my_column");
25+
var mapping = SortMappingsForJooq.map(ESort.Property1, field);
26+
27+
assertThat(mapping.property()).isEqualTo(ESort.Property1);
28+
assertThat(mapping.column()).isEqualTo(field);
29+
}
30+
31+
@Test
32+
@DisplayName("Method of should create a SortMappingsForJooq instance")
33+
void of_shouldCreateSortMappingsForJooq() {
34+
var field1 = DSL.field("col1");
35+
var mappings = SortMappingsForJooq.of(
36+
SortMappingsForJooq.map(ESort.Property1, field1)
37+
);
38+
39+
assertThat(mappings).isInstanceOf(SortMappingsForJooq.class);
40+
assertThat(mappings.get(ESort.Property1)).isEqualTo(field1);
41+
}
42+
43+
@Test
44+
@DisplayName("Should build Spring Sort with jOOQ Fields correctly")
45+
void buildSpringSortWithJooqFields() {
46+
var mappings = SortMappingsForJooq.of(
47+
SortMappingsForJooq.map(ESort.Property1, DSL.field("my_col"))
48+
);
49+
var sortParameter = SortParameter.by(ESort.Property1, Sort.Direction.DESC);
50+
51+
var result = sortParameter.buildSort(mappings);
52+
53+
var order = result.getOrderFor("my_col");
54+
assertThat(order).isNotNull();
55+
assertThat(Objects.requireNonNull(order).getDirection()).isEqualTo(Sort.Direction.DESC);
56+
}
57+
}

0 commit comments

Comments
 (0)