Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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,27 +61,23 @@ public String getMessage() {
return message;
}

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

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

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

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

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

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

Expand All @@ -101,21 +96,18 @@ public Path getPropertyPath() {
return propertyPath;
}

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

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

@Nullable
@Override
public <U> U unwrap(Class<U> type) {
public @Nullable <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 {
Comment thread
ThoSap marked this conversation as resolved.
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> {
Comment thread
ThoSap marked this conversation as resolved.
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
Copy Markdown
Member 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 @@ -13,29 +13,22 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
@NullMarked
public class SwaggerMeta {
@Nullable
private String originalTypeFqn = null;
private @Nullable String originalTypeFqn = null;

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

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

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

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

@Nullable
private String mapKeyTypeFqn = null;
private @Nullable String mapKeyTypeFqn = null;

@Nullable
@JsonProperty("isNullable")
private Boolean isNullable = null;
private @Nullable 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() {
Comment thread
ThoSap marked this conversation as resolved.
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);
}
}