From 5300eb34a897f2f60961c861b7db1410ba95e4bc Mon Sep 17 00:00:00 2001 From: Arnaud Date: Thu, 7 Dec 2023 15:34:12 +0100 Subject: [PATCH] feat(record): add record support --- .../querydsl/model/FieldMetadata.java | 15 ------ .../querydsl/model/SimpleFilter.java | 7 +-- .../service/ext/QueryDslProcessorService.java | 53 +++++++++---------- .../querydsl/service/ext/ReflectUtils.java | 31 +++++++++++ .../scanner/FilterFieldAnnotationScanner.java | 13 ++--- .../FilterFieldValidatorService.java | 4 +- .../querydsl/utils/ReflectUtils.java | 53 ------------------- .../querydsl/FilterFieldServiceTest.java | 4 +- .../FilterFieldValidatorServiceTest.java | 13 +++-- .../querydsl/utils/ReflectUtilsTest.java | 39 -------------- 10 files changed, 78 insertions(+), 154 deletions(-) delete mode 100644 src/main/java/fr/ouestfrance/querydsl/model/FieldMetadata.java create mode 100644 src/main/java/fr/ouestfrance/querydsl/service/ext/ReflectUtils.java delete mode 100644 src/main/java/fr/ouestfrance/querydsl/utils/ReflectUtils.java delete mode 100644 src/test/java/fr/ouestfrance/querydsl/utils/ReflectUtilsTest.java diff --git a/src/main/java/fr/ouestfrance/querydsl/model/FieldMetadata.java b/src/main/java/fr/ouestfrance/querydsl/model/FieldMetadata.java deleted file mode 100644 index 071a0c4..0000000 --- a/src/main/java/fr/ouestfrance/querydsl/model/FieldMetadata.java +++ /dev/null @@ -1,15 +0,0 @@ -package fr.ouestfrance.querydsl.model; - -import java.lang.reflect.Method; -import java.lang.reflect.Type; - -/** - * FieldInformation class represent metadata on declared field in a class - * - * @param name Name of the field - * @param type Return declared type of the field - * @param getter Getter method to access data - */ -public record FieldMetadata(String name, Type type, Method getter) { - -} diff --git a/src/main/java/fr/ouestfrance/querydsl/model/SimpleFilter.java b/src/main/java/fr/ouestfrance/querydsl/model/SimpleFilter.java index 7fa2ada..8e487d8 100644 --- a/src/main/java/fr/ouestfrance/querydsl/model/SimpleFilter.java +++ b/src/main/java/fr/ouestfrance/querydsl/model/SimpleFilter.java @@ -2,13 +2,14 @@ import fr.ouestfrance.querydsl.FilterOperation; +import java.lang.reflect.Field; + /** * FilterFieldInfoModel allow to store object representation of FilterField Annotation * @param key key of the criteria * @param operation operator to apply * @param orNull allow the field to be null - * @param metadata metadata of the field representing type, method accessor and field name + * @param field field of the field representing type, method accessor and field name */ -public record SimpleFilter(String key, FilterOperation operation, boolean orNull, - FieldMetadata metadata) implements Filter { +public record SimpleFilter(String key, FilterOperation operation, boolean orNull, Field field) implements Filter { } diff --git a/src/main/java/fr/ouestfrance/querydsl/service/ext/QueryDslProcessorService.java b/src/main/java/fr/ouestfrance/querydsl/service/ext/QueryDslProcessorService.java index ab81dda..4fb3483 100644 --- a/src/main/java/fr/ouestfrance/querydsl/service/ext/QueryDslProcessorService.java +++ b/src/main/java/fr/ouestfrance/querydsl/service/ext/QueryDslProcessorService.java @@ -1,12 +1,13 @@ package fr.ouestfrance.querydsl.service.ext; import fr.ouestfrance.querydsl.FilterOperation; -import fr.ouestfrance.querydsl.model.SimpleFilter; -import fr.ouestfrance.querydsl.model.GroupFilter; import fr.ouestfrance.querydsl.model.Filter; +import fr.ouestfrance.querydsl.model.GroupFilter; +import fr.ouestfrance.querydsl.model.SimpleFilter; import fr.ouestfrance.querydsl.service.FilterFieldAnnotationProcessorService; -import fr.ouestfrance.querydsl.utils.ReflectUtils; +import lombok.SneakyThrows; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -17,29 +18,29 @@ * @param Type of returned object * Example: YOu can define a queryStringTranslatorService that transform filterField annotations to rest call *
{@code
- *                       class QueryStringTranslatorService implements QueryDslProcessorService{
- *                            List> mappers = List.of(InMapper, EqualsMapper, ...);
+ *                                                                              class QueryStringTranslatorService implements QueryDslProcessorService{
+ *                                                                                   List> mappers = List.of(InMapper, EqualsMapper, ...);
  *
- *                            public Mapper getMapper(FilterOperation operations){
- *                                mappers.stream().filter(x->x.getOperation().equals(operation)).findFirst()
- *                                 .orElse(new DefaultMapper());
- *                            }
+ *                                                                                   public Mapper getMapper(FilterOperation operations){
+ *                                                                                       mappers.stream().filter(x->x.getOperation().equals(operation)).findFirst()
+ *                                                                                        .orElse(new DefaultMapper());
+ *                                                                                   }
  *
- *                            public group(List filters, GroupFilter.Operand operand){
- *                                return String.join(" "+operand+" ", filters); // Will result item1 AND item2 AND ...
- *                            }
- *                       }
- *                       }
+ * public group(List filters, GroupFilter.Operand operand){ + * return String.join(" "+operand+" ", filters); // Will result item1 AND item2 AND ... + * } + * } + * } * With mappers implementations like this *
{@code
- *                       class EqualsMapper implements Mapper{
+ *                                                                              class EqualsMapper implements Mapper{
  *
- *                            String map(FilterFieldInfoModel model, Object data){
- *                                return model.getKey()+".equals("+data+")"
- *                                 // "uuid.equals(25)"
- *                            }
- *                       }
- *                       }
+ * String map(FilterFieldInfoModel model, Object data){ + * return model.getKey()+".equals("+data+")" + * // "uuid.equals(25)" + * } + * } + * } */ public interface QueryDslProcessorService { @@ -106,15 +107,11 @@ private Optional handleGroup(GroupFilter filterGroup, Object object) { * @return concrete filter with values if the object has data */ private Optional handleFilter(SimpleFilter filter, Object object) { - Object data = ReflectUtils.safeGet(object, filter.metadata().getter()); - if (data == null) { - return Optional.empty(); - } - FilterOperation operation = filter.operation(); - Mapper mapper = getMapper(operation); - return Optional.ofNullable(mapper.map(filter, data)); + return ReflectUtils.getObject(filter.field(), object) + .map(x -> getMapper(filter.operation()).map(filter, x)); } + /** * Return concrete mapper for a specific operation * diff --git a/src/main/java/fr/ouestfrance/querydsl/service/ext/ReflectUtils.java b/src/main/java/fr/ouestfrance/querydsl/service/ext/ReflectUtils.java new file mode 100644 index 0000000..70614d3 --- /dev/null +++ b/src/main/java/fr/ouestfrance/querydsl/service/ext/ReflectUtils.java @@ -0,0 +1,31 @@ +package fr.ouestfrance.querydsl.service.ext; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; + +import java.lang.reflect.Field; +import java.util.Optional; + +/** + * Internal reflect class that allow to retrieve object + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +final class ReflectUtils { + + + /** + * Get safe value for a field + * + * @param field to retrieve data + * @param object source object + * @return optional value + */ + @SneakyThrows + @SuppressWarnings("java:S3011") + public static Optional getObject(Field field, Object object) { + // allow to access the field if record or field from clazz + field.setAccessible(true); + return Optional.ofNullable(field.get(object)); + } +} diff --git a/src/main/java/fr/ouestfrance/querydsl/service/scanner/FilterFieldAnnotationScanner.java b/src/main/java/fr/ouestfrance/querydsl/service/scanner/FilterFieldAnnotationScanner.java index 14da9e1..1160dd4 100644 --- a/src/main/java/fr/ouestfrance/querydsl/service/scanner/FilterFieldAnnotationScanner.java +++ b/src/main/java/fr/ouestfrance/querydsl/service/scanner/FilterFieldAnnotationScanner.java @@ -2,14 +2,12 @@ import fr.ouestfrance.querydsl.FilterField; import fr.ouestfrance.querydsl.FilterFields; -import fr.ouestfrance.querydsl.model.FieldMetadata; -import fr.ouestfrance.querydsl.model.SimpleFilter; -import fr.ouestfrance.querydsl.model.GroupFilter; import fr.ouestfrance.querydsl.model.Filter; +import fr.ouestfrance.querydsl.model.GroupFilter; +import fr.ouestfrance.querydsl.model.SimpleFilter; import fr.ouestfrance.querydsl.service.validators.FilterFieldConstraintException; import fr.ouestfrance.querydsl.service.validators.FilterFieldValidatorService; import fr.ouestfrance.querydsl.service.validators.FilterFieldViolation; -import fr.ouestfrance.querydsl.utils.ReflectUtils; import java.lang.reflect.Field; import java.util.*; @@ -49,15 +47,14 @@ public List scan(Class clazz) { .filter(this::hasFilterFieldAnnotation) .forEach( field -> { - FieldMetadata fieldMetadata = new FieldMetadata(field.getName(), field.getType(), ReflectUtils.getGetter(clazz, field)); - FilterFields filterFields = field.getAnnotation(FilterFields.class); + FilterFields filterFields = field.getAnnotation(FilterFields.class); List groupFilters = new ArrayList<>(); if (filterFields!= null && !filterFields.groupName().isEmpty()) { groupFilters.addAll(Arrays.stream(filterFields.value()).toList()); GroupFilter filterAndGroup = new GroupFilter(UUID.randomUUID().toString(), new ArrayList<>(), GroupFilter.Operand.AND); Arrays.stream(filterFields.value()) .forEach(filterField -> { - SimpleFilter filter = new SimpleFilter(firstNotEmpty(filterField.key(), field.getName()), filterField.operation(), filterField.orNull(), fieldMetadata); + SimpleFilter filter = new SimpleFilter(firstNotEmpty(filterField.key(), field.getName()), filterField.operation(), filterField.orNull(), field); validatorService.validate(filter).ifPresent(violations::add); filterAndGroup.filters().add(filter); }); @@ -69,7 +66,7 @@ public List scan(Class clazz) { .filter(x-> !groupFilters.contains(x)) .forEach( filterField -> { - SimpleFilter filter = new SimpleFilter(firstNotEmpty(filterField.key(), field.getName()), filterField.operation(), filterField.orNull(), fieldMetadata); + SimpleFilter filter = new SimpleFilter(firstNotEmpty(filterField.key(), field.getName()), filterField.operation(), filterField.orNull(), field); validatorService.validate(filter).ifPresent(violations::add); appendToGroup(rootGroup, filterField.groupName(), filter); } diff --git a/src/main/java/fr/ouestfrance/querydsl/service/validators/FilterFieldValidatorService.java b/src/main/java/fr/ouestfrance/querydsl/service/validators/FilterFieldValidatorService.java index db2823a..86be046 100644 --- a/src/main/java/fr/ouestfrance/querydsl/service/validators/FilterFieldValidatorService.java +++ b/src/main/java/fr/ouestfrance/querydsl/service/validators/FilterFieldValidatorService.java @@ -50,8 +50,8 @@ public class FilterFieldValidatorService { */ public Optional validate(SimpleFilter filter) { return getValidator(filter.operation()) - .filter(x-> !x.validate((Class) filter.metadata().type())) - .map(x -> new FilterFieldViolation(filter.metadata().name(), "Operation " + filter.operation() + " " + x.message())); + .filter(x-> !x.validate((Class) filter.field().getType())) + .map(x -> new FilterFieldViolation(filter.field().getName(), "Operation " + filter.operation() + " " + x.message())); } /** diff --git a/src/main/java/fr/ouestfrance/querydsl/utils/ReflectUtils.java b/src/main/java/fr/ouestfrance/querydsl/utils/ReflectUtils.java deleted file mode 100644 index d3792c0..0000000 --- a/src/main/java/fr/ouestfrance/querydsl/utils/ReflectUtils.java +++ /dev/null @@ -1,53 +0,0 @@ -package fr.ouestfrance.querydsl.utils; - -import fr.ouestfrance.querydsl.service.validators.FilterFieldConstraintException; -import fr.ouestfrance.querydsl.service.validators.FilterFieldViolation; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.List; - -/** - * Utilities class that allow to simplify use of java reflect - */ -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class ReflectUtils { - - /** - * Retrieve data from getter - * - * @param object search object - * @param getter getter function - * @return object return by using object.getFunction() - */ - public static Object safeGet(Object object, Method getter) { - try { - return getter.invoke(object); - } catch (IllegalAccessException | InvocationTargetException e) { - return null; - } - } - - /** - * Retrieve getter function for a Field - * - * @param clazz clazz containing the field - * @param field declared field - * @return getter for the field - */ - public static Method getGetter(Class clazz, Field field) { - String capitalizedName = field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1); - String getterPrefix = "get"; - if (field.getType().equals(boolean.class)) { - getterPrefix = "is"; - } - try { - return clazz.getDeclaredMethod(getterPrefix + capitalizedName); - } catch (NoSuchMethodException e) { - throw new FilterFieldConstraintException(clazz, List.of(new FilterFieldViolation(field.getName(), "can't find method " + getterPrefix + capitalizedName))); - } - } -} diff --git a/src/test/java/fr/ouestfrance/querydsl/FilterFieldServiceTest.java b/src/test/java/fr/ouestfrance/querydsl/FilterFieldServiceTest.java index ced05ea..1d08855 100644 --- a/src/test/java/fr/ouestfrance/querydsl/FilterFieldServiceTest.java +++ b/src/test/java/fr/ouestfrance/querydsl/FilterFieldServiceTest.java @@ -31,9 +31,7 @@ void shouldLoad() { assertNotNull(x); assertTrue(x instanceof SimpleFilter); SimpleFilter filter = (SimpleFilter)x; - assertNotNull(filter.metadata()); - assertNotNull(filter.metadata().getter()); - assertNotNull(filter.metadata().type()); + assertNotNull(filter.field()); shouldFindOrNull.set(shouldFindOrNull.get() | filter.orNull()); }); assertTrue(shouldFindOrNull.get()); diff --git a/src/test/java/fr/ouestfrance/querydsl/service/validators/FilterFieldValidatorServiceTest.java b/src/test/java/fr/ouestfrance/querydsl/service/validators/FilterFieldValidatorServiceTest.java index 1e5433f..3b266e3 100644 --- a/src/test/java/fr/ouestfrance/querydsl/service/validators/FilterFieldValidatorServiceTest.java +++ b/src/test/java/fr/ouestfrance/querydsl/service/validators/FilterFieldValidatorServiceTest.java @@ -1,8 +1,8 @@ package fr.ouestfrance.querydsl.service.validators; import fr.ouestfrance.querydsl.FilterOperation; -import fr.ouestfrance.querydsl.model.FieldMetadata; import fr.ouestfrance.querydsl.model.SimpleFilter; +import lombok.SneakyThrows; import org.junit.jupiter.api.Test; import java.util.List; @@ -15,18 +15,25 @@ class FilterFieldValidatorServiceTest { private final FilterFieldValidatorService validator = new FilterFieldValidatorService(); + static class SampleClass{ + List lists; + String value; + } + + @SneakyThrows @Test void shouldSentViolations(){ Optional violations = validator.validate( - new SimpleFilter("test", FilterOperation.EQ, false, new FieldMetadata("test", List.class, null))); + new SimpleFilter("test", FilterOperation.EQ, false, SampleClass.class.getDeclaredField("lists"))); assertFalse(violations.isEmpty()); } + @SneakyThrows @Test void shouldValidate(){ Optional violations = validator.validate( - new SimpleFilter("test", FilterOperation.EQ, false, new FieldMetadata("test", String.class, null))); + new SimpleFilter("test", FilterOperation.EQ, false, SampleClass.class.getDeclaredField("value"))); assertTrue(violations.isEmpty()); } diff --git a/src/test/java/fr/ouestfrance/querydsl/utils/ReflectUtilsTest.java b/src/test/java/fr/ouestfrance/querydsl/utils/ReflectUtilsTest.java deleted file mode 100644 index 4262c39..0000000 --- a/src/test/java/fr/ouestfrance/querydsl/utils/ReflectUtilsTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package fr.ouestfrance.querydsl.utils; - -import fr.ouestfrance.querydsl.service.validators.FilterFieldConstraintException; -import fr.ouestfrance.querydsl.utils.ReflectUtils; -import lombok.AllArgsConstructor; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -class ReflectUtilsTest { - - @Test - void shouldRaiseExceptionOnGetter() throws NoSuchFieldException { - Field value = DummyTest.class.getDeclaredField("value"); - assertThrows(FilterFieldConstraintException.class, () -> ReflectUtils.getGetter(DummyTest.class, value)); - } - - @Test - void shouldRaiseExceptionOnGetterInaccessible() throws NoSuchFieldException { - Field value = DummyTest.class.getDeclaredField("data"); - Method getter = ReflectUtils.getGetter(DummyTest.class, value); - Object result = ReflectUtils.safeGet(new DummyTest(1, "Example"), getter); - assertNull(result); - } - - @AllArgsConstructor - static class DummyTest { - private int value; - private String data; - - private String getData() { - return data; - } - } -}