Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<properties>
<java.version>25</java.version>
<errorprone.version>2.46.0</errorprone.version>
<jooq.version>3.20.11</jooq.version>
<nullaway.version>0.12.15</nullaway.version>
</properties>

Expand Down Expand Up @@ -52,6 +53,15 @@
<artifactId>spring-security-core</artifactId>
</dependency>

<!-- jOOQ -->
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq</artifactId>
<version>${jooq.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>

<!-- Utilities -->
<dependency>
<groupId>org.projectlombok</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,26 +61,26 @@ public String getMessage() {
return message;
}

@Nullable
@Override
@Nullable
public String getMessageTemplate() {
return null;
}

@Nullable
@Override
@Nullable
public Object getRootBean() {
return null;
}

@Nullable
@Override
@Nullable
public Class<Object> getRootBeanClass() {
return null;
}

@Nullable
@Override
@Nullable
public Object getLeafBean() {
return null;
}
Expand All @@ -90,8 +90,8 @@ public Object[] getExecutableParameters() {
return new Object[0];
}

@Nullable
@Override
@Nullable
public Object getExecutableReturnValue() {
return null;
}
Expand All @@ -101,20 +101,20 @@ public Path getPropertyPath() {
return propertyPath;
}

@Nullable
@Override
@Nullable
public Object getInvalidValue() {
return null;
}

@Nullable
@Override
@Nullable
public ConstraintDescriptor<?> getConstraintDescriptor() {
return null;
}

@Nullable
@Override
@Nullable
public <U> U unwrap(Class<U> type) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.jspecify.annotations.Nullable;
import org.springframework.data.domain.Sort;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand All @@ -24,7 +26,7 @@
@NullMarked
public final class SortParameter<T extends Enum<?> & SortParameter.Definition> {
private static final String DEFAULT_SORT_PROPERTY = "id";
private static final Sort.Direction DEFAULT_SORT_DIRETION = Sort.Direction.ASC;
private static final Sort.Direction DEFAULT_SORT_DIRECTION = Sort.Direction.ASC;

@Accessors(fluent = true)
@Getter
Expand Down Expand Up @@ -226,11 +228,11 @@ public SortParameter<T> or(SortParameter<T> fallback) {
* @return an instance of {@link Sort} created using the transformed key-value mapping,
* excluding default sorting behavior.
*/
public Sort buildSortWithoutDefault(Map<T, String> mapping) {
public Sort buildSortWithoutDefault(Map<T, ?> mapping) {
var stringMapping = mapping.entrySet().stream()
.collect(Collectors.toMap(
entry -> entry.getKey().name(),
Map.Entry::getValue
entry -> convertToString(entry.getValue())
));

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

return buildSort(stringMapping, true);
}

private static String convertToString(Object value) {
if (value instanceof String string) {
return string;
}

// Use reflection to get the column name from a mapped jOOQ Field<?> using the Field<?>.getName() method
// This allows us to have jOOQ as an optional dependency and not break existing projects that use Hibernate
try {
var getName = findAccessibleGetNameMethod(value.getClass());
if (getName == null) {
getName = value.getClass().getMethod("getName");
getName.setAccessible(true);
}

return (String) getName.invoke(value);
} catch (Exception e) {
throw new IllegalArgumentException(
"Cannot get the jOOQ Field name from the mapped SortMappings$Mapping column value [value.class.name=%s]".formatted(
value.getClass().getName()
),
e
);
}
}

private static @Nullable Method findAccessibleGetNameMethod(Class<?> clazz) {
if (Modifier.isPublic(clazz.getModifiers())) {
try {
var method = clazz.getDeclaredMethod("getName");
if (Modifier.isPublic(method.getModifiers())) {
return method;
}
} catch (NoSuchMethodException _) {
// ignored, let's search in the interfaces or superclass
}
}

for (var interFace : clazz.getInterfaces()) {
var method = findAccessibleGetNameMethod(interFace);
if (method != null) {
return method;
}
}

var superclass = clazz.getSuperclass();
if (superclass != null) {
return findAccessibleGetNameMethod(superclass);
}

return null;
}

private Sort buildSort(Map<String, String> mapping, boolean withDefault) {
if (sortFields.isEmpty()) {
return withDefault ? getMappedDefaultSort(mapping) : Sort.unsorted();
Expand Down Expand Up @@ -289,7 +343,7 @@ private Sort buildSort(Map<String, String> mapping, boolean withDefault) {
}

private static Sort getMappedDefaultSort(Map<String, String> mapping) {
return Sort.by(DEFAULT_SORT_DIRETION, mapping.getOrDefault(DEFAULT_SORT_PROPERTY, DEFAULT_SORT_PROPERTY));
return Sort.by(DEFAULT_SORT_DIRECTION, mapping.getOrDefault(DEFAULT_SORT_PROPERTY, DEFAULT_SORT_PROPERTY));
}

public record SortField(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import java.util.HashMap;

@NullMarked
public final class SortMappings<T extends Enum<?> & SortParameter.Definition> extends HashMap<T, String> {
private SortMappings() {
public class SortMappings<T extends Enum<?> & SortParameter.Definition> extends HashMap<T, Object> {
SortMappings() {
super();
}

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

public record Mapping<T extends Enum<?> & SortParameter.Definition>(T property, String column) {
public record Mapping<T extends Enum<?> & SortParameter.Definition>(T property, Object column) {
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this class is not used in a non jOOQ BE project, the project will just compile fine.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package it.aboutbits.springboot.toolbox.persistence;

import it.aboutbits.springboot.toolbox.parameter.SortParameter;
import org.jooq.Field;
import org.jspecify.annotations.NullMarked;

@NullMarked
public final class SortMappingsForJooq<T extends Enum<?> & SortParameter.Definition> extends SortMappings<T> {
private SortMappingsForJooq() {
super();
}

public static <T extends Enum<?> & SortParameter.Definition> Mapping<T> map(
T property,
Field<?> column
) {
return new Mapping<>(property, column);
}

@SafeVarargs
public static <T extends Enum<?> & SortParameter.Definition> SortMappingsForJooq<T> of(
Mapping<T>... mappings
) {
var sortMappings = new SortMappingsForJooq<T>();

for (var mapping : mappings) {
sortMappings.put(mapping.property(), mapping.column());
}

return sortMappings;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,26 @@ public class SwaggerMeta {
@Nullable
private String originalTypeFqn = null;

@Nullable
@JsonProperty("isIdentity")
@Nullable
private Boolean isIdentity = null;

@Nullable
@JsonProperty("isCustomType")
@Nullable
private Boolean isCustomType = null;

@Nullable
@JsonProperty("isNestedStructure")
@Nullable
private Boolean isNestedStructure = null;

@Nullable
@JsonProperty("isMap")
@Nullable
private Boolean isMap = null;

@Nullable
private String mapKeyTypeFqn = null;

@Nullable
@JsonProperty("isNullable")
@Nullable
private Boolean isNullable = null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package it.aboutbits.springboot.toolbox.persistence;

import it.aboutbits.springboot.toolbox.parameter.SortParameter;
import org.jooq.impl.DSL;
import org.jspecify.annotations.NullMarked;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Sort;

import java.util.Objects;

import static org.assertj.core.api.Assertions.assertThat;

@NullMarked
class SortMappingsForJooqTest {
private enum ESort implements SortParameter.Definition {
Property1,
Property2
}

@Test
void map_shouldReturnMappingWithFieldObject() {
var field = DSL.field("my_column");
var mapping = SortMappingsForJooq.map(ESort.Property1, field);

assertThat(mapping.property()).isEqualTo(ESort.Property1);
assertThat(mapping.column()).isEqualTo(field);
}

@Test
void of_shouldCreateSortMappingsForJooq() {
var field1 = DSL.field("col1");
var mappings = SortMappingsForJooq.of(
SortMappingsForJooq.map(ESort.Property1, field1)
);

assertThat(mappings).isInstanceOf(SortMappingsForJooq.class);
assertThat(mappings.get(ESort.Property1)).isEqualTo(field1);
}

@Test
void buildSpringSortWithJooqFields() {
var mappings = SortMappingsForJooq.of(
SortMappingsForJooq.map(ESort.Property1, DSL.field("my_col"))
);
var sortParameter = SortParameter.by(ESort.Property1, Sort.Direction.DESC);

var result = sortParameter.buildSort(mappings);

var order = result.getOrderFor("my_col");
assertThat(order).isNotNull();
assertThat(Objects.requireNonNull(order).getDirection()).isEqualTo(Sort.Direction.DESC);
}
}