Skip to content

Commit fd061b6

Browse files
committed
add jOOQ as an optional dependency and support jOOQ Field in SortMappings in new class SortMappingsForJooq
1 parent cecc45e commit fd061b6

5 files changed

Lines changed: 151 additions & 18 deletions

File tree

pom.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<groupId>it.aboutbits</groupId>
1313
<artifactId>spring-boot-toolbox</artifactId>
14-
<version>2.2.0</version>
14+
<version>2.2.1-SNAPSHOT</version>
1515
<description>Utility library for Spring Boot projects.</description>
1616
<packaging>jar</packaging>
1717

@@ -59,6 +59,7 @@
5959
<artifactId>jooq</artifactId>
6060
<version>${jooq.version}</version>
6161
<scope>provided</scope>
62+
<optional>true</optional>
6263
</dependency>
6364

6465
<!-- Utilities -->

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

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import org.jspecify.annotations.Nullable;
88
import org.springframework.data.domain.Sort;
99

10+
import java.lang.reflect.Method;
11+
import java.lang.reflect.Modifier;
1012
import java.util.ArrayList;
1113
import java.util.List;
1214
import java.util.Map;
@@ -24,7 +26,7 @@
2426
@NullMarked
2527
public final class SortParameter<T extends Enum<?> & SortParameter.Definition> {
2628
private static final String DEFAULT_SORT_PROPERTY = "id";
27-
private static final Sort.Direction DEFAULT_SORT_DIRETION = Sort.Direction.ASC;
29+
private static final Sort.Direction DEFAULT_SORT_DIRECTION = Sort.Direction.ASC;
2830

2931
@Accessors(fluent = true)
3032
@Getter
@@ -226,11 +228,11 @@ public SortParameter<T> or(SortParameter<T> fallback) {
226228
* @return an instance of {@link Sort} created using the transformed key-value mapping,
227229
* excluding default sorting behavior.
228230
*/
229-
public Sort buildSortWithoutDefault(Map<T, String> mapping) {
231+
public Sort buildSortWithoutDefault(Map<T, ?> mapping) {
230232
var stringMapping = mapping.entrySet().stream()
231233
.collect(Collectors.toMap(
232234
entry -> entry.getKey().name(),
233-
Map.Entry::getValue
235+
entry -> convertToString(entry.getValue())
234236
));
235237

236238
return buildSort(stringMapping, false);
@@ -246,16 +248,68 @@ public Sort buildSortWithoutDefault(Map<T, String> mapping) {
246248
* The enumeration keys must implement the `name()` method to retrieve their string representation.
247249
* @return an instance of {@link Sort} created using the transformed key-value mapping with default sorting behavior.
248250
*/
249-
public Sort buildSort(Map<T, String> mapping) {
251+
public Sort buildSort(Map<T, ?> mapping) {
250252
var stringMapping = mapping.entrySet().stream()
251253
.collect(Collectors.toMap(
252254
entry -> entry.getKey().name(),
253-
Map.Entry::getValue
255+
entry -> convertToString(entry.getValue())
254256
));
255257

256258
return buildSort(stringMapping, true);
257259
}
258260

261+
private static String convertToString(Object value) {
262+
if (value instanceof String string) {
263+
return string;
264+
}
265+
266+
// Use reflection to get the column name from a mapped jOOQ Field<?> using the Field<?>.getName() method
267+
// This allows us to have jOOQ as an optional dependency and not break existing projects that use Hibernate
268+
try {
269+
var getName = findAccessibleGetNameMethod(value.getClass());
270+
if (getName == null) {
271+
getName = value.getClass().getMethod("getName");
272+
getName.setAccessible(true);
273+
}
274+
275+
return (String) getName.invoke(value);
276+
} catch (Exception e) {
277+
throw new IllegalArgumentException(
278+
"Cannot get the jOOQ Field name from the mapped SortMappings$Mapping column value [value.class.name=%s]".formatted(
279+
value.getClass().getName()
280+
),
281+
e
282+
);
283+
}
284+
}
285+
286+
private static @Nullable Method findAccessibleGetNameMethod(Class<?> clazz) {
287+
if (Modifier.isPublic(clazz.getModifiers())) {
288+
try {
289+
var method = clazz.getDeclaredMethod("getName");
290+
if (Modifier.isPublic(method.getModifiers())) {
291+
return method;
292+
}
293+
} catch (NoSuchMethodException _) {
294+
// ignored, let's search in the interfaces or superclass
295+
}
296+
}
297+
298+
for (var interFace : clazz.getInterfaces()) {
299+
var method = findAccessibleGetNameMethod(interFace);
300+
if (method != null) {
301+
return method;
302+
}
303+
}
304+
305+
var superclass = clazz.getSuperclass();
306+
if (superclass != null) {
307+
return findAccessibleGetNameMethod(superclass);
308+
}
309+
310+
return null;
311+
}
312+
259313
private Sort buildSort(Map<String, String> mapping, boolean withDefault) {
260314
if (sortFields.isEmpty()) {
261315
return withDefault ? getMappedDefaultSort(mapping) : Sort.unsorted();
@@ -289,7 +343,7 @@ private Sort buildSort(Map<String, String> mapping, boolean withDefault) {
289343
}
290344

291345
private static Sort getMappedDefaultSort(Map<String, String> mapping) {
292-
return Sort.by(DEFAULT_SORT_DIRETION, mapping.getOrDefault(DEFAULT_SORT_PROPERTY, DEFAULT_SORT_PROPERTY));
346+
return Sort.by(DEFAULT_SORT_DIRECTION, mapping.getOrDefault(DEFAULT_SORT_PROPERTY, DEFAULT_SORT_PROPERTY));
293347
}
294348

295349
public record SortField(
Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package it.aboutbits.springboot.toolbox.persistence;
22

33
import it.aboutbits.springboot.toolbox.parameter.SortParameter;
4-
import org.jooq.Field;
54
import org.jspecify.annotations.NullMarked;
65

76
import java.util.HashMap;
87

98
@NullMarked
10-
public final class SortMappings<T extends Enum<?> & SortParameter.Definition> extends HashMap<T, String> {
11-
private SortMappings() {
9+
public class SortMappings<T extends Enum<?> & SortParameter.Definition> extends HashMap<T, Object> {
10+
SortMappings() {
1211
super();
1312
}
1413

@@ -19,13 +18,6 @@ public static <T extends Enum<?> & SortParameter.Definition> Mapping<T> map(
1918
return new Mapping<>(property, column);
2019
}
2120

22-
public static <T extends Enum<?> & SortParameter.Definition> Mapping<T> map(
23-
T property,
24-
Field<?> column
25-
) {
26-
return new Mapping<>(property, column.getName());
27-
}
28-
2921
@SafeVarargs
3022
public static <T extends Enum<?> & SortParameter.Definition> SortMappings<T> of(
3123
Mapping<T>... mappings
@@ -39,6 +31,6 @@ public static <T extends Enum<?> & SortParameter.Definition> SortMappings<T> of(
3931
return sortMappings;
4032
}
4133

42-
public record Mapping<T extends Enum<?> & SortParameter.Definition>(T property, String column) {
34+
public record Mapping<T extends Enum<?> & SortParameter.Definition>(T property, Object column) {
4335
}
4436
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package it.aboutbits.springboot.toolbox.persistence;
2+
3+
import it.aboutbits.springboot.toolbox.parameter.SortParameter;
4+
import it.aboutbits.springboot.toolbox.persistence.SortMappings.Mapping;
5+
import org.jooq.Field;
6+
import org.jspecify.annotations.NullMarked;
7+
8+
@NullMarked
9+
public final class SortMappingsForJooq<T extends Enum<?> & SortParameter.Definition> extends SortMappings<T> {
10+
private SortMappingsForJooq() {
11+
super();
12+
}
13+
14+
public static <T extends Enum<?> & SortParameter.Definition> Mapping<T> map(
15+
T property,
16+
Field<?> column
17+
) {
18+
return new Mapping<>(property, column);
19+
}
20+
21+
@SafeVarargs
22+
public static <T extends Enum<?> & SortParameter.Definition> SortMappingsForJooq<T> of(
23+
Mapping<T>... mappings
24+
) {
25+
var sortMappings = new SortMappingsForJooq<T>();
26+
27+
for (var mapping : mappings) {
28+
sortMappings.put(mapping.property(), mapping.column());
29+
}
30+
31+
return sortMappings;
32+
}
33+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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.Test;
7+
import org.springframework.data.domain.Sort;
8+
9+
import java.util.Objects;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
13+
@NullMarked
14+
class SortMappingsForJooqTest {
15+
private enum ESort implements SortParameter.Definition {
16+
Property1,
17+
Property2
18+
}
19+
20+
@Test
21+
void map_shouldReturnMappingWithFieldObject() {
22+
var field = DSL.field("my_column");
23+
var mapping = SortMappingsForJooq.map(ESort.Property1, field);
24+
25+
assertThat(mapping.property()).isEqualTo(ESort.Property1);
26+
assertThat(mapping.column()).isEqualTo(field);
27+
}
28+
29+
@Test
30+
void of_shouldCreateSortMappingsForJooq() {
31+
var field1 = DSL.field("col1");
32+
var mappings = SortMappingsForJooq.of(
33+
SortMappingsForJooq.map(ESort.Property1, field1)
34+
);
35+
36+
assertThat(mappings).isInstanceOf(SortMappingsForJooq.class);
37+
assertThat(mappings.get(ESort.Property1)).isEqualTo(field1);
38+
}
39+
40+
@Test
41+
void buildSpringSortWithJooqFields() {
42+
var mappings = SortMappingsForJooq.of(
43+
SortMappingsForJooq.map(ESort.Property1, DSL.field("my_col"))
44+
);
45+
var sortParameter = SortParameter.by(ESort.Property1, Sort.Direction.DESC);
46+
47+
var result = sortParameter.buildSort(mappings);
48+
49+
var order = result.getOrderFor("my_col");
50+
assertThat(order).isNotNull();
51+
assertThat(Objects.requireNonNull(order).getDirection()).isEqualTo(Sort.Direction.DESC);
52+
}
53+
}

0 commit comments

Comments
 (0)