From 8cec705482e6c23f3904e7a3a8225c3b635c1e04 Mon Sep 17 00:00:00 2001 From: birddevelper Date: Thu, 26 Dec 2024 21:48:41 +0200 Subject: [PATCH 1/8] feat: add required to Filter --- .../springrestframework/filter/Filter.java | 33 +++++++++++++++++-- .../springrestframework/filter/FilterSet.java | 29 ++++++++++++++++ .../orm/SearchCriteria.java | 10 ++++++ .../web/controllers/ListController.java | 4 +++ 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/nikanique/springrestframework/filter/Filter.java b/src/main/java/io/github/nikanique/springrestframework/filter/Filter.java index 51787cf..d90a6eb 100644 --- a/src/main/java/io/github/nikanique/springrestframework/filter/Filter.java +++ b/src/main/java/io/github/nikanique/springrestframework/filter/Filter.java @@ -12,12 +12,11 @@ @Builder public class Filter implements Comparable { private String name; - private String modelFieldName; private FilterOperation operation; private FieldType fieldType; - private String helpText; + private boolean required = false; public Filter(String name, FilterOperation filterOperation, FieldType fieldType) { this.name = name; @@ -25,6 +24,13 @@ public Filter(String name, FilterOperation filterOperation, FieldType fieldType) this.fieldType = fieldType; } + public Filter(String name, FilterOperation filterOperation, FieldType fieldType, boolean required) { + this.name = name; + this.operation = filterOperation; + this.fieldType = fieldType; + this.required = required; + } + public Filter(String name, FilterOperation filterOperation, FieldType fieldType, String helpText) { this.name = name; this.operation = filterOperation; @@ -32,12 +38,27 @@ public Filter(String name, FilterOperation filterOperation, FieldType fieldType, this.helpText = helpText; } + public Filter(String name, FilterOperation filterOperation, FieldType fieldType, String helpText, boolean required) { + this.name = name; + this.operation = filterOperation; + this.fieldType = fieldType; + this.helpText = helpText; + this.required = required; + } + public Filter(String name, String modelFieldName, FilterOperation filterOperation, FieldType fieldType) { this.name = name; this.operation = filterOperation; this.fieldType = fieldType; this.modelFieldName = modelFieldName; + } + public Filter(String name, String modelFieldName, FilterOperation filterOperation, FieldType fieldType, boolean required) { + this.name = name; + this.operation = filterOperation; + this.fieldType = fieldType; + this.modelFieldName = modelFieldName; + this.required = required; } public Filter(String name, String modelFieldName, FilterOperation filterOperation, FieldType fieldType, String helpText) { @@ -46,7 +67,15 @@ public Filter(String name, String modelFieldName, FilterOperation filterOperatio this.fieldType = fieldType; this.modelFieldName = modelFieldName; this.helpText = helpText; + } + public Filter(String name, String modelFieldName, FilterOperation filterOperation, FieldType fieldType, String helpText, boolean required) { + this.name = name; + this.operation = filterOperation; + this.fieldType = fieldType; + this.modelFieldName = modelFieldName; + this.helpText = helpText; + this.required = required; } diff --git a/src/main/java/io/github/nikanique/springrestframework/filter/FilterSet.java b/src/main/java/io/github/nikanique/springrestframework/filter/FilterSet.java index 60e3ebd..1ddc1ca 100644 --- a/src/main/java/io/github/nikanique/springrestframework/filter/FilterSet.java +++ b/src/main/java/io/github/nikanique/springrestframework/filter/FilterSet.java @@ -45,24 +45,53 @@ public FilterSetBuilder addFilter(String name, FilterOperation operation, FieldT return this; } + public FilterSetBuilder addFilter(String name, FilterOperation operation, FieldType fieldType, boolean required) { + Filter filter = new Filter(name, operation, fieldType, required); + filterSet.add(filter); + return this; + } + public FilterSetBuilder addFilter(String name, FilterOperation operation, FieldType fieldType, String helpText) { Filter filter = new Filter(name, operation, fieldType, helpText); filterSet.add(filter); return this; } + public FilterSetBuilder addFilter(String name, FilterOperation operation, FieldType fieldType, String helpText, boolean required) { + Filter filter = new Filter(name, operation, fieldType, helpText, required); + filterSet.add(filter); + return this; + } + public FilterSetBuilder addFilter(String name, String modelFieldName, FilterOperation operation, FieldType fieldType) { Filter filter = new Filter(name, modelFieldName, operation, fieldType); filterSet.add(filter); return this; } + public FilterSetBuilder addFilter(String name, String modelFieldName, FilterOperation operation, FieldType fieldType, boolean required) { + Filter filter = new Filter(name, modelFieldName, operation, fieldType, required); + filterSet.add(filter); + return this; + } + public FilterSetBuilder addFilter(String name, String modelFieldName, FilterOperation operation, FieldType fieldType, String helpText) { Filter filter = new Filter(name, modelFieldName, operation, fieldType, helpText); filterSet.add(filter); return this; } + public FilterSetBuilder addFilter(String name, String modelFieldName, FilterOperation operation, FieldType fieldType, String helpText, boolean required) { + Filter filter = new Filter(name, modelFieldName, operation, fieldType, helpText, required); + filterSet.add(filter); + return this; + } + + public FilterSetBuilder addFilter(Filter filter) { + filterSet.add(filter); + return this; + } + public FilterSet build() { return filterSet; } diff --git a/src/main/java/io/github/nikanique/springrestframework/orm/SearchCriteria.java b/src/main/java/io/github/nikanique/springrestframework/orm/SearchCriteria.java index 47bf6f9..eae2624 100644 --- a/src/main/java/io/github/nikanique/springrestframework/orm/SearchCriteria.java +++ b/src/main/java/io/github/nikanique/springrestframework/orm/SearchCriteria.java @@ -146,6 +146,13 @@ public static List fromUrlQuery(HttpServletRequest request, Filt String toParameterName = name + "To"; String fromValue = ServletRequestUtils.getStringParameter(request, fromParameterName, null); String toValue = ServletRequestUtils.getStringParameter(request, toParameterName, null); + + if (filter.isRequired() && (fromValue == null || toValue == null)) { + throw new ValidationException( + fromValue == null ? fromParameterName : toParameterName, + "Both " + fromParameterName + " and " + toParameterName + " must be present."); + } + if (fromValue == null ^ toValue == null) { throw new ValidationException( fromValue == null ? fromParameterName : toParameterName, @@ -162,6 +169,9 @@ public static List fromUrlQuery(HttpServletRequest request, Filt } else { value = ServletRequestUtils.getStringParameter(request, name, null); + if (filter.isRequired() && value == null) { + throw new ValidationException(name, name + " is required"); + } if (value != null) { Object parsedValue = extractAndValidateValue(name, filter, value); SearchCriteria searchCriteria = new SearchCriteria(modelFieldName, filter.getOperation(), parsedValue, filter.getFieldType()); diff --git a/src/main/java/io/github/nikanique/springrestframework/web/controllers/ListController.java b/src/main/java/io/github/nikanique/springrestframework/web/controllers/ListController.java index 10b6813..6aa2004 100644 --- a/src/main/java/io/github/nikanique/springrestframework/web/controllers/ListController.java +++ b/src/main/java/io/github/nikanique/springrestframework/web/controllers/ListController.java @@ -72,17 +72,21 @@ default void generateListSchema(Operation operation, Set filterList, Cla String toParameterName = filter.getName() + "To"; operation.addParametersItem(new Parameter().name(fromParameterName).in("query") .schema(new Schema().type(filter.getFieldType().toString().toLowerCase())) + .required(filter.isRequired()) .description(filter.getHelpText() == null ? "Filter operator :" + FilterOperation.GREATER_OR_EQUAL.name() : filter.getHelpText())); operation.addParametersItem(new Parameter().name(toParameterName).in("query") .schema(new Schema().type(filter.getFieldType().toString().toLowerCase())) + .required(filter.isRequired()) .description(filter.getHelpText() == null ? "Filter operator :" + FilterOperation.LESS_OR_EQUAL.name() : filter.getHelpText())); } else if (filter.getOperation().equals(FilterOperation.IN)) { operation.addParametersItem(new Parameter().name(filter.getName()).in("query") .schema(new Schema().type("string")) + .required(filter.isRequired()) .description(filter.getHelpText() == null ? "Filter operator :" + filter.getOperation().name() : filter.getHelpText())); } else { operation.addParametersItem(new Parameter().name(filter.getName()).in("query") .schema(new Schema().type(filter.getFieldType().toString().toLowerCase())) + .required(filter.isRequired()) .description(filter.getHelpText() == null ? "Filter operator :" + filter.getOperation().name() : filter.getHelpText())); } } From 891dc28dd1d0cc115b0827535ab9ca085c35f62f Mon Sep 17 00:00:00 2001 From: birddevelper Date: Sat, 28 Dec 2024 10:38:05 +0200 Subject: [PATCH 2/8] fix: change authorizeRequest method input --- .../web/controllers/BaseGenericController.java | 4 ++-- .../web/controllers/GenericCommandController.java | 8 ++++---- .../web/controllers/GenericCreateController.java | 2 +- .../web/controllers/GenericDeleteController.java | 2 +- .../web/controllers/GenericListController.java | 2 +- .../web/controllers/GenericQueryController.java | 4 ++-- .../web/controllers/GenericRetrieveController.java | 2 +- .../web/controllers/GenericUpdateController.java | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/github/nikanique/springrestframework/web/controllers/BaseGenericController.java b/src/main/java/io/github/nikanique/springrestframework/web/controllers/BaseGenericController.java index c334319..861127d 100644 --- a/src/main/java/io/github/nikanique/springrestframework/web/controllers/BaseGenericController.java +++ b/src/main/java/io/github/nikanique/springrestframework/web/controllers/BaseGenericController.java @@ -110,8 +110,8 @@ protected boolean hasAuthorities(List requiredAuthorities) { ); } - protected void authorizeRequest(String HttpMethod) { - List requiredAuthorities = getRequiredAuthorities(HttpMethod); + protected void authorizeRequest(HttpServletRequest request) { + List requiredAuthorities = getRequiredAuthorities(request.getMethod().toUpperCase()); if (!hasAuthorities(requiredAuthorities)) { throw new UnauthorizedException("You do not have permission to perform this action."); } diff --git a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCommandController.java b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCommandController.java index e20ad7a..7efbda9 100644 --- a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCommandController.java +++ b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCommandController.java @@ -82,27 +82,27 @@ protected Filter configLookupFilter() { @PostMapping("/") public ResponseEntity post(HttpServletRequest request) throws IOException { - this.authorizeRequest("POST"); + this.authorizeRequest(request); return this.create(this, request); } @PutMapping("/{lookup}") public ResponseEntity put(@PathVariable(name = "lookup") Object lookupValue, HttpServletRequest request) throws Throwable { - this.authorizeRequest("PUT"); + this.authorizeRequest(request); return this.update(this, lookupValue, request); } @PatchMapping("/{lookup}") public ResponseEntity patch(@PathVariable(name = "lookup") Object lookupValue, HttpServletRequest request) throws Throwable { - this.authorizeRequest("PATCH"); + this.authorizeRequest(request); return this.partialUpdate(this, lookupValue, request); } @DeleteMapping("/{lookup}") public ResponseEntity delete(HttpServletRequest request, @PathVariable(name = "lookup") Object lookupValue) { - this.authorizeRequest("DELETE"); + this.authorizeRequest(request); return this.deleteObject(this, lookupValue, request); } diff --git a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCreateController.java b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCreateController.java index c30503b..79dfd86 100644 --- a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCreateController.java +++ b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCreateController.java @@ -74,7 +74,7 @@ public Class getCreateResponseBodyDTO() { @PostMapping("/") public ResponseEntity post(HttpServletRequest request) throws IOException { - this.authorizeRequest("POST"); + this.authorizeRequest(request); return this.create(this, request); } diff --git a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericDeleteController.java b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericDeleteController.java index 4afebba..b82e6bc 100644 --- a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericDeleteController.java +++ b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericDeleteController.java @@ -70,7 +70,7 @@ protected Filter configLookupFilter() { @DeleteMapping("/{lookup}") public ResponseEntity delete(HttpServletRequest request, @PathVariable(name = "lookup") Object lookupValue) { - this.authorizeRequest("DELETE"); + this.authorizeRequest(request); return deleteObject(this, lookupValue, request); } diff --git a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericListController.java b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericListController.java index 04785ba..5654da0 100644 --- a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericListController.java +++ b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericListController.java @@ -100,7 +100,7 @@ public ResponseEntity> get( @RequestParam(defaultValue = "10") int size, @RequestParam(defaultValue = "") String sortBy, @RequestParam(defaultValue = "ASC") Sort.Direction direction) throws Throwable { - this.authorizeRequest("GET"); + this.authorizeRequest(request); return this.list(this, request, page, size, sortBy, direction); } diff --git a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericQueryController.java b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericQueryController.java index e050ff5..6186908 100644 --- a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericQueryController.java +++ b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericQueryController.java @@ -120,7 +120,7 @@ public ResponseEntity> get( @RequestParam(defaultValue = "10") int size, @RequestParam(defaultValue = "") String sortBy, @RequestParam(defaultValue = "ASC") Sort.Direction direction) throws Throwable { - this.authorizeRequest("GET"); + this.authorizeRequest(request); return this.list(this, request, page, size, sortBy, direction); } @@ -128,7 +128,7 @@ public ResponseEntity> get( public ResponseEntity getByLookupValue( HttpServletRequest request, @PathVariable(name = "lookup") Object lookupValue) throws Throwable { - this.authorizeRequest("GET"); + this.authorizeRequest(request); return this.retrieve(this, request, lookupValue); } diff --git a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericRetrieveController.java b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericRetrieveController.java index bf26b8d..267faec 100644 --- a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericRetrieveController.java +++ b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericRetrieveController.java @@ -93,7 +93,7 @@ protected Filter configLookupFilter() { public ResponseEntity getByLookupValue( HttpServletRequest request, @PathVariable(name = "lookup") Object lookupValue) throws Throwable { - this.authorizeRequest("GET"); + this.authorizeRequest(request); return this.retrieve(this, request, lookupValue); } diff --git a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericUpdateController.java b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericUpdateController.java index 2bf488b..0bdd90f 100644 --- a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericUpdateController.java +++ b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericUpdateController.java @@ -99,13 +99,13 @@ protected Filter configLookupFilter() { @PutMapping("/{lookup}") public ResponseEntity update(@PathVariable(name = "lookup") Object lookupValue, HttpServletRequest request) throws Throwable { - this.authorizeRequest("PUT"); + this.authorizeRequest(request); return this.update(this, lookupValue, request); } @PatchMapping("/{lookup}") public ResponseEntity patch(@PathVariable(name = "lookup") Object lookupValue, HttpServletRequest request) throws Throwable { - this.authorizeRequest("PATCH"); + this.authorizeRequest(request); return this.partialUpdate(this, lookupValue, request); } From e551c6766d3a7554040f7b3ba80f312d28c2ad2d Mon Sep 17 00:00:00 2001 From: birddevelper Date: Tue, 31 Dec 2024 19:06:44 +0200 Subject: [PATCH 3/8] fix: remove duplicate plugins --- pom.xml | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/pom.xml b/pom.xml index de5a5fa..b398ece 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.github.nikanique spring-rest-framework - 2.1.0 + 2.2.0 jar spring-rest-framework @@ -103,32 +103,6 @@ ${java.home}/bin/javadoc - - org.apache.maven.plugins - maven-source-plugin - 3.2.1 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.3.0 - - - attach-javadocs - - jar - - - - org.apache.maven.plugins maven-source-plugin From 4946e3f5316c4133aba4a01df18bcb7e998502de Mon Sep 17 00:00:00 2001 From: birddevelper Date: Sun, 12 Jan 2025 19:14:49 +0200 Subject: [PATCH 4/8] feat: add postDeserialization --- .../springrestframework/dto/Dto.java | 4 ++ .../springrestframework/dto/DtoManager.java | 12 ++++-- .../dto/FieldMetadata.java | 4 +- .../orm/EntityBuilder.java | 2 +- .../serializer/Serializer.java | 42 +++++++++++++++++-- .../controllers/BaseGenericController.java | 3 +- 6 files changed, 57 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/github/nikanique/springrestframework/dto/Dto.java b/src/main/java/io/github/nikanique/springrestframework/dto/Dto.java index 391290d..beb6a7c 100644 --- a/src/main/java/io/github/nikanique/springrestframework/dto/Dto.java +++ b/src/main/java/io/github/nikanique/springrestframework/dto/Dto.java @@ -3,6 +3,7 @@ import io.github.nikanique.springrestframework.annotation.FieldValidation; import io.github.nikanique.springrestframework.exceptions.ValidationException; +import jakarta.persistence.EntityManager; import java.lang.invoke.MethodHandle; import java.text.ParseException; @@ -104,4 +105,7 @@ private void validateDateField(java.util.Date value, String fieldName, FieldVali } } + public void postDeserialization(EntityManager entityManager) { + // Default implementation + } } diff --git a/src/main/java/io/github/nikanique/springrestframework/dto/DtoManager.java b/src/main/java/io/github/nikanique/springrestframework/dto/DtoManager.java index ea23e20..076e1de 100644 --- a/src/main/java/io/github/nikanique/springrestframework/dto/DtoManager.java +++ b/src/main/java/io/github/nikanique/springrestframework/dto/DtoManager.java @@ -22,10 +22,15 @@ public static Map getDtoByClassName(Class clazz) { return Stream.of(cls.getDeclaredFields()) .map(field -> { MethodHandle getterMethodHandle = null; + MethodHandle setterMethodHandle = null; try { String getterName = "get" + Character.toUpperCase(field.getName().charAt(0)) + field.getName().substring(1); - MethodType methodType = MethodType.methodType(field.getType()); - getterMethodHandle = lookup.findVirtual(cls, getterName, methodType); + String setterName = "set" + Character.toUpperCase(field.getName().charAt(0)) + field.getName().substring(1); + MethodType getterMethodType = MethodType.methodType(field.getType()); + MethodType setterMethodType = MethodType.methodType(void.class, field.getType()); + getterMethodHandle = lookup.findVirtual(cls, getterName, getterMethodType); + setterMethodHandle = lookup.findVirtual(cls, setterName, setterMethodType); + } catch (NoSuchMethodException | IllegalAccessException e) { // Log the exception or handle it as per your requirement } @@ -35,7 +40,8 @@ public static Map getDtoByClassName(Class clazz) { field.getAnnotation(ReadOnly.class), field.getAnnotation(WriteOnly.class), field.getAnnotation(ReferencedModel.class), - getterMethodHandle); + getterMethodHandle, + setterMethodHandle); }) .collect(Collectors.toMap(fieldMetadata -> fieldMetadata.getField().getName(), fieldMetadata -> fieldMetadata)); }); diff --git a/src/main/java/io/github/nikanique/springrestframework/dto/FieldMetadata.java b/src/main/java/io/github/nikanique/springrestframework/dto/FieldMetadata.java index ad192da..c6b2691 100644 --- a/src/main/java/io/github/nikanique/springrestframework/dto/FieldMetadata.java +++ b/src/main/java/io/github/nikanique/springrestframework/dto/FieldMetadata.java @@ -17,8 +17,9 @@ public class FieldMetadata { private final WriteOnly writeOnly; private final ReferencedModel referencedModel; private final MethodHandle getterMethodHandle; + private final MethodHandle setterMethodHandle; - public FieldMetadata(Field field, Class fieldType, FieldValidation validation, Expose expose, ReadOnly readOnly, WriteOnly writeOnly, ReferencedModel referencedModel, MethodHandle getterMethodHandle) { + public FieldMetadata(Field field, Class fieldType, FieldValidation validation, Expose expose, ReadOnly readOnly, WriteOnly writeOnly, ReferencedModel referencedModel, MethodHandle getterMethodHandle, MethodHandle setterMethodHandle) { this.field = field; this.fieldType = fieldType; this.validation = validation; @@ -27,5 +28,6 @@ public FieldMetadata(Field field, Class fieldType, FieldValidation validation this.writeOnly = writeOnly; this.referencedModel = referencedModel; this.getterMethodHandle = getterMethodHandle; + this.setterMethodHandle = setterMethodHandle; } } diff --git a/src/main/java/io/github/nikanique/springrestframework/orm/EntityBuilder.java b/src/main/java/io/github/nikanique/springrestframework/orm/EntityBuilder.java index f08c4f0..ed68bd4 100644 --- a/src/main/java/io/github/nikanique/springrestframework/orm/EntityBuilder.java +++ b/src/main/java/io/github/nikanique/springrestframework/orm/EntityBuilder.java @@ -47,7 +47,7 @@ public Model fromDto(Object dto, Class dtoClass) { for (String fieldName : fieldMetadata.keySet()) { Object fieldValue = fieldMetadata.get(fieldName).getGetterMethodHandle().invoke(dto); ReadOnly readOnlyAnnotation = fieldMetadata.get(fieldName).getReadOnly(); - if (readOnlyAnnotation != null) { + if (readOnlyAnnotation != null && fieldValue == null) { ignoreProperties.add(fieldName); continue; } diff --git a/src/main/java/io/github/nikanique/springrestframework/serializer/Serializer.java b/src/main/java/io/github/nikanique/springrestframework/serializer/Serializer.java index 5171d9a..769bf71 100644 --- a/src/main/java/io/github/nikanique/springrestframework/serializer/Serializer.java +++ b/src/main/java/io/github/nikanique/springrestframework/serializer/Serializer.java @@ -5,10 +5,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import io.github.nikanique.springrestframework.annotation.ReadOnly; import io.github.nikanique.springrestframework.common.FieldType; +import io.github.nikanique.springrestframework.dto.DtoManager; +import io.github.nikanique.springrestframework.dto.FieldMetadata; import io.github.nikanique.springrestframework.utilities.MethodReflectionHelper; import io.github.nikanique.springrestframework.utilities.StringUtils; import io.github.nikanique.springrestframework.utilities.ValueFormatter; +import jakarta.persistence.EntityManager; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -28,10 +32,12 @@ public class Serializer { private final ObjectMapper objectMapper; + private final EntityManager entityManager; @Autowired - public Serializer(ObjectMapper objectMapper) { + public Serializer(ObjectMapper objectMapper, EntityManager entityManager) { this.objectMapper = objectMapper; + this.entityManager = entityManager; } public Object deserialize(String requestBody, Class dtoClass) throws IOException { @@ -52,16 +58,32 @@ public Object deserialize(String requestBody, Class dtoClass, Boolean raiseVa } public Object deserialize(String requestBody, Class dtoClass, Boolean raiseValidationError, Set fields) throws IOException { - Object dto = objectMapper.readValue(requestBody, dtoClass); + Object dto = generateDTO(requestBody, dtoClass); if (!fields.isEmpty()) { invokeValidateIfExists(dto, dtoClass, raiseValidationError, fields); } else { invokeValidateIfExists(dto, dtoClass, raiseValidationError); } - + invokePostDeserialization(dto, dtoClass, entityManager); return dtoClass.cast(dto); } + private Object generateDTO(String requestBody, Class dtoClass) throws JsonProcessingException { + Object dto = objectMapper.readValue(requestBody, dtoClass); + Map fieldMetadata = DtoManager.getDtoByClassName(dtoClass); + for (String fieldName : fieldMetadata.keySet()) { + ReadOnly readOnlyAnnotation = fieldMetadata.get(fieldName).getReadOnly(); + if (readOnlyAnnotation != null) { + try { + fieldMetadata.get(fieldName).getSetterMethodHandle().invoke(dto, null); + } catch (Throwable e) { + log.error("{} setter method invocation failed.", fieldName); + } + } + } + return dto; + } + public Set getPresentFields(String requestBody) throws JsonProcessingException { JsonNode requestBodyNode = objectMapper.readTree(requestBody); Set fieldNames = new HashSet<>(); @@ -98,6 +120,20 @@ private void invokeValidateIfExists(Object dto, Class dtoClass, Boolean raise } } + private void invokePostDeserialization(Object dto, Class dtoClass, EntityManager entityManager) { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodType methodType = MethodType.methodType(void.class, EntityManager.class); + try { + MethodHandle validateMethodHandle = lookup.findVirtual(dtoClass, "postDeserialization", methodType); + validateMethodHandle.invoke(dto, entityManager); + } catch (NoSuchMethodException | IllegalAccessException e) { + log.error("postDeserialization method does not exist or is not accessible"); + } catch (Throwable t) { + throw new RuntimeException("postDeserialization method failed", t); + } + } + + public ObjectNode serialize(Object object, SerializerConfig serializerConfig) { ObjectNode serializedData = serializeObject(object, serializerConfig.getFields(), ""); if (serializerConfig.getToRepresentMethod() != null) { diff --git a/src/main/java/io/github/nikanique/springrestframework/web/controllers/BaseGenericController.java b/src/main/java/io/github/nikanique/springrestframework/web/controllers/BaseGenericController.java index 861127d..6185aa3 100644 --- a/src/main/java/io/github/nikanique/springrestframework/web/controllers/BaseGenericController.java +++ b/src/main/java/io/github/nikanique/springrestframework/web/controllers/BaseGenericController.java @@ -32,11 +32,10 @@ public abstract class BaseGenericController> endpointsRequiredAuthorities; @Getter protected Serializer serializer; protected ApplicationContext context; - private Map> endpointsRequiredAuthorities; @Autowired public BaseGenericController(@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") ModelRepository repository) { From 479f6c65f93ec9cffaa171ff8941504a35d4bde3 Mon Sep 17 00:00:00 2001 From: birddevelper Date: Sun, 12 Jan 2025 19:33:34 +0200 Subject: [PATCH 5/8] fix: use getDeclaredConstructor method --- .../github/nikanique/springrestframework/orm/EntityBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/github/nikanique/springrestframework/orm/EntityBuilder.java b/src/main/java/io/github/nikanique/springrestframework/orm/EntityBuilder.java index ed68bd4..76c4a8f 100644 --- a/src/main/java/io/github/nikanique/springrestframework/orm/EntityBuilder.java +++ b/src/main/java/io/github/nikanique/springrestframework/orm/EntityBuilder.java @@ -40,7 +40,7 @@ public Model fromDto(Object dto, Class dtoClass) { // Create an instance of the main entity class - Model entity = this.entityClass.newInstance(); + Model entity = this.entityClass.getDeclaredConstructor().newInstance(); BeanWrapper entityWrapper = new BeanWrapperImpl(entity); List ignoreProperties = new ArrayList<>(); From 0b2b25165cc99215405a87d691e4ea790bb42df2 Mon Sep 17 00:00:00 2001 From: birddevelper Date: Sun, 12 Jan 2025 19:33:52 +0200 Subject: [PATCH 6/8] fix: remove redundant supprestion --- .../web/controllers/BaseGenericController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/nikanique/springrestframework/web/controllers/BaseGenericController.java b/src/main/java/io/github/nikanique/springrestframework/web/controllers/BaseGenericController.java index 6185aa3..3b23bec 100644 --- a/src/main/java/io/github/nikanique/springrestframework/web/controllers/BaseGenericController.java +++ b/src/main/java/io/github/nikanique/springrestframework/web/controllers/BaseGenericController.java @@ -38,7 +38,7 @@ public abstract class BaseGenericController(); this.configRequiredAuthorities(this.endpointsRequiredAuthorities); @@ -67,7 +67,7 @@ public void setApplicationContext(ApplicationContext applicationContext) throws * } * * - * @return + * @return The class type of the DTO */ protected abstract Class getDTO(); From 1ed991d5fac1c7314c8488653fa8d119abe29b3c Mon Sep 17 00:00:00 2001 From: birddevelper Date: Sun, 12 Jan 2025 21:43:16 +0200 Subject: [PATCH 7/8] feat: add isRequired and defaultValue to Expose annotation --- .../annotation/Expose.java | 4 ++ .../serializer/Serializer.java | 59 ++++++++++++++++--- .../web/controllers/CreateController.java | 4 +- .../controllers/GenericCommandController.java | 4 +- .../controllers/GenericCreateController.java | 4 +- 5 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/main/java/io/github/nikanique/springrestframework/annotation/Expose.java b/src/main/java/io/github/nikanique/springrestframework/annotation/Expose.java index 7c44dd4..d6c86d4 100644 --- a/src/main/java/io/github/nikanique/springrestframework/annotation/Expose.java +++ b/src/main/java/io/github/nikanique/springrestframework/annotation/Expose.java @@ -15,4 +15,8 @@ String methodName() default "not-provided"; String source() default "not-provided"; + + String defaultValue() default "not-provided"; + + boolean isRequired() default false; } \ No newline at end of file diff --git a/src/main/java/io/github/nikanique/springrestframework/serializer/Serializer.java b/src/main/java/io/github/nikanique/springrestframework/serializer/Serializer.java index 769bf71..e1ab247 100644 --- a/src/main/java/io/github/nikanique/springrestframework/serializer/Serializer.java +++ b/src/main/java/io/github/nikanique/springrestframework/serializer/Serializer.java @@ -5,10 +5,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import io.github.nikanique.springrestframework.annotation.Expose; import io.github.nikanique.springrestframework.annotation.ReadOnly; import io.github.nikanique.springrestframework.common.FieldType; import io.github.nikanique.springrestframework.dto.DtoManager; import io.github.nikanique.springrestframework.dto.FieldMetadata; +import io.github.nikanique.springrestframework.exceptions.BadRequestException; import io.github.nikanique.springrestframework.utilities.MethodReflectionHelper; import io.github.nikanique.springrestframework.utilities.StringUtils; import io.github.nikanique.springrestframework.utilities.ValueFormatter; @@ -17,11 +19,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.io.IOException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; +import java.sql.Timestamp; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -40,15 +42,15 @@ public Serializer(ObjectMapper objectMapper, EntityManager entityManager) { this.entityManager = entityManager; } - public Object deserialize(String requestBody, Class dtoClass) throws IOException { + public Object deserialize(String requestBody, Class dtoClass) throws Throwable { return deserialize(requestBody, dtoClass, false, false); } - public Object deserialize(String requestBody, Class dtoClass, Boolean raiseValidationError) throws IOException { + public Object deserialize(String requestBody, Class dtoClass, Boolean raiseValidationError) throws Throwable { return deserialize(requestBody, dtoClass, raiseValidationError, false); } - public Object deserialize(String requestBody, Class dtoClass, Boolean raiseValidationError, Boolean partial) throws IOException { + public Object deserialize(String requestBody, Class dtoClass, Boolean raiseValidationError, Boolean partial) throws Throwable { Set fieldNames = new HashSet<>(); if (partial) { fieldNames = getPresentFields(requestBody); @@ -57,7 +59,7 @@ public Object deserialize(String requestBody, Class dtoClass, Boolean raiseVa } - public Object deserialize(String requestBody, Class dtoClass, Boolean raiseValidationError, Set fields) throws IOException { + public Object deserialize(String requestBody, Class dtoClass, Boolean raiseValidationError, Set fields) throws Throwable { Object dto = generateDTO(requestBody, dtoClass); if (!fields.isEmpty()) { invokeValidateIfExists(dto, dtoClass, raiseValidationError, fields); @@ -68,18 +70,61 @@ public Object deserialize(String requestBody, Class dtoClass, Boolean raiseVa return dtoClass.cast(dto); } - private Object generateDTO(String requestBody, Class dtoClass) throws JsonProcessingException { + private Object generateDTO(String requestBody, Class dtoClass) throws Throwable { Object dto = objectMapper.readValue(requestBody, dtoClass); Map fieldMetadata = DtoManager.getDtoByClassName(dtoClass); for (String fieldName : fieldMetadata.keySet()) { ReadOnly readOnlyAnnotation = fieldMetadata.get(fieldName).getReadOnly(); - if (readOnlyAnnotation != null) { + Expose exposeAnnotation = fieldMetadata.get(fieldName).getExpose(); + Object fieldValue = fieldMetadata.get(fieldName).getGetterMethodHandle().invoke(dto); + + // Read only field set to null + if (readOnlyAnnotation != null && fieldValue != null) { try { fieldMetadata.get(fieldName).getSetterMethodHandle().invoke(dto, null); } catch (Throwable e) { log.error("{} setter method invocation failed.", fieldName); } } + + + // Default value + if (readOnlyAnnotation == null && exposeAnnotation != null && fieldValue == null && + !exposeAnnotation.defaultValue().equals("not-provided")) { + Class fieldType = fieldMetadata.get(fieldName).getFieldType(); + String defaultValue = exposeAnnotation.defaultValue(); + try { + if (fieldType == Integer.class || fieldType == int.class) { + fieldMetadata.get(fieldName).getSetterMethodHandle().invoke(dto, Integer.valueOf(defaultValue)); + } else if (fieldType == Long.class || fieldType == long.class) { + fieldMetadata.get(fieldName).getSetterMethodHandle().invoke(dto, Long.valueOf(defaultValue)); + } else if (fieldType == Double.class || fieldType == double.class) { + fieldMetadata.get(fieldName).getSetterMethodHandle().invoke(dto, Double.valueOf(defaultValue)); + } else if (fieldType == Float.class || fieldType == float.class) { + fieldMetadata.get(fieldName).getSetterMethodHandle().invoke(dto, Float.valueOf(defaultValue)); + } else if (fieldType == LocalDate.class) { + fieldMetadata.get(fieldName).getSetterMethodHandle().invoke(dto, LocalDate.parse(defaultValue)); + } else if (fieldType == Date.class) { + fieldMetadata.get(fieldName).getSetterMethodHandle().invoke(dto, java.sql.Date.valueOf(defaultValue)); + } else if (fieldType == Timestamp.class) { + fieldMetadata.get(fieldName).getSetterMethodHandle().invoke(dto, Timestamp.valueOf(defaultValue)); + } else if (fieldType == String.class) { + fieldMetadata.get(fieldName).getSetterMethodHandle().invoke(dto, defaultValue); + } else if (fieldType == Boolean.class || fieldType == boolean.class) { + fieldMetadata.get(fieldName).getSetterMethodHandle().invoke(dto, Boolean.valueOf(defaultValue)); + } else { + log.error("{} field type is not supported for default value.", fieldName); + } + } catch (Throwable e) { + log.error("{} default value could not be set.", fieldName); + } + } + + // Required check + + if (exposeAnnotation != null && fieldValue == null && exposeAnnotation.isRequired()) { + throw new BadRequestException(fieldName, "Field is required."); + } } return dto; } diff --git a/src/main/java/io/github/nikanique/springrestframework/web/controllers/CreateController.java b/src/main/java/io/github/nikanique/springrestframework/web/controllers/CreateController.java index d3a01c3..acac063 100644 --- a/src/main/java/io/github/nikanique/springrestframework/web/controllers/CreateController.java +++ b/src/main/java/io/github/nikanique/springrestframework/web/controllers/CreateController.java @@ -15,8 +15,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import java.io.IOException; - /** * This interface provides methods for creating entities. * @@ -39,7 +37,7 @@ default SerializerConfig configCreateSerializerFields() { } - default ResponseEntity create(BaseGenericController controller, HttpServletRequest request) throws IOException { + default ResponseEntity create(BaseGenericController controller, HttpServletRequest request) throws Throwable { String requestBody = this.getRequestBody(request); Object dto = controller.getSerializer().deserialize(requestBody, getCreateRequestBodyDTO(), true); Model entity = this.getEntityHelper().fromDto(dto, this.getCreateRequestBodyDTO()); diff --git a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCommandController.java b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCommandController.java index 7efbda9..b68196d 100644 --- a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCommandController.java +++ b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCommandController.java @@ -19,8 +19,6 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.method.HandlerMethod; -import java.io.IOException; - @Getter public abstract class GenericCommandController & JpaSpecificationExecutor> extends BaseGenericController @@ -81,7 +79,7 @@ protected Filter configLookupFilter() { } @PostMapping("/") - public ResponseEntity post(HttpServletRequest request) throws IOException { + public ResponseEntity post(HttpServletRequest request) throws Throwable { this.authorizeRequest(request); return this.create(this, request); } diff --git a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCreateController.java b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCreateController.java index 79dfd86..11423bb 100644 --- a/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCreateController.java +++ b/src/main/java/io/github/nikanique/springrestframework/web/controllers/GenericCreateController.java @@ -14,8 +14,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.method.HandlerMethod; -import java.io.IOException; - /** * The GenericCreateController class is a generic controller designed for use in Spring Boot applications for creating model's records. * It provides a common implementation for creating records. It exposes endpoint with POST method. @@ -73,7 +71,7 @@ public Class getCreateResponseBodyDTO() { } @PostMapping("/") - public ResponseEntity post(HttpServletRequest request) throws IOException { + public ResponseEntity post(HttpServletRequest request) throws Throwable { this.authorizeRequest(request); return this.create(this, request); } From ee5049f0ecab3c4efff120d62589d9ebd28ebceb Mon Sep 17 00:00:00 2001 From: birddevelper Date: Sun, 12 Jan 2025 21:45:00 +0200 Subject: [PATCH 8/8] feat: upgrade version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b398ece..04d5131 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.github.nikanique spring-rest-framework - 2.2.0 + 2.3.0 jar spring-rest-framework