diff --git a/src/main/java/com/onixbyte/helix/HelixApplication.java b/src/main/java/com/onixbyte/helix/HelixApplication.java index b4d15d1..bfd4063 100644 --- a/src/main/java/com/onixbyte/helix/HelixApplication.java +++ b/src/main/java/com/onixbyte/helix/HelixApplication.java @@ -14,14 +14,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class HelixApplication { - /** - * Main method that serves as the entry point for the Helix application. - * - * @param args command-line arguments passed to the application, which can be used to override - * default configuration properties or specify runtime options - */ public static void main(String[] args) { SpringApplication.run(HelixApplication.class, args); } - } diff --git a/src/main/java/com/onixbyte/helix/shared/CacheName.java b/src/main/java/com/onixbyte/helix/shared/CacheName.java index 8babf0f..5a61ff9 100644 --- a/src/main/java/com/onixbyte/helix/shared/CacheName.java +++ b/src/main/java/com/onixbyte/helix/shared/CacheName.java @@ -4,7 +4,7 @@ public final class CacheName { public static final String USER = "user"; - public static final String AUTHORITIES_OF_USER = "user-authorities"; + public static final String AUTHORITIES_OF_USER = "user-authority"; public static final String ASSET = "asset"; diff --git a/src/main/java/com/onixbyte/helix/validation/annotation/CustomValidation.java b/src/main/java/com/onixbyte/helix/validation/annotation/CustomValidation.java new file mode 100644 index 0000000..637271a --- /dev/null +++ b/src/main/java/com/onixbyte/helix/validation/annotation/CustomValidation.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024-2026 OnixByte + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.onixbyte.helix.validation.annotation; + +import com.onixbyte.helix.validation.executor.CustomValidationExecutor; +import com.onixbyte.helix.validation.validator.DynamicValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * CustomValidation is a custom validation annotation that allows for dynamic validation rules to be + * applied to fields or classes. It accepts an array of DynamicValidator classes that will be + * executed to validate the annotated element. + * + * @author siujamo + */ +@Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = CustomValidationExecutor.class) +public @interface CustomValidation { + /** + * An array of DynamicValidator classes that will be executed to validate the annotated element. + * + * @return an array of DynamicValidator classes + */ + Class>[] rules(); + + /** + * The default validation message that will be used if any of the specified validators fail. + * + * @return the default validation message + */ + String message() default "Validation failed for the annotated element."; + + /** + * Groups can be used to specify different validation scenarios. This allows for more flexible + * validation logic based on the context of the operation (e.g., creation vs. update). + * + * @return an array of validation groups + */ + Class[] groups() default {}; + + /** + * Payload can be used by clients of the Bean Validation API to assign custom payload objects + * to a constraint. This is not commonly used but can be helpful for advanced validation + * scenarios. + * + * @return an array of payload classes + */ + Class[] payload() default {}; +} diff --git a/src/main/java/com/onixbyte/helix/validation/executor/CustomValidationExecutor.java b/src/main/java/com/onixbyte/helix/validation/executor/CustomValidationExecutor.java new file mode 100644 index 0000000..717d7e5 --- /dev/null +++ b/src/main/java/com/onixbyte/helix/validation/executor/CustomValidationExecutor.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2024-2026 OnixByte + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.onixbyte.helix.validation.executor; + +import com.onixbyte.helix.validation.annotation.CustomValidation; +import com.onixbyte.helix.validation.validator.DynamicValidator; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +/** + * CustomValidationExecutor is a dynamic validation executor that processes multiple validation + * rules specified by the @CustomValidation annotation. + * + * @author siujamo + */ +@Component +public class CustomValidationExecutor + implements ConstraintValidator, ApplicationContextAware { + + private ApplicationContext applicationContext; + private Class>[] validatorClasses; + + @Override + public void initialize(CustomValidation constraintAnnotation) { + this.validatorClasses = constraintAnnotation.rules(); + } + + /** + * Validates the given value against all specified validators. If any validator fails, + * the entire validation fails. + * + * @param value the value to validate + * @param context the constraint validator context for adding violation messages if needed + * @return true if the value is valid according to all validators, false otherwise + */ + @Override + public boolean isValid(Object value, ConstraintValidatorContext context) { + if (Objects.isNull(value)) return true; + + // Iterate through each specified validator class and perform validation + for (Class> clazz : validatorClasses) { + DynamicValidator validator = getValidatorInstance(clazz); + + // Ensure the validator supports the type of the value being validated + if (!validator.getSupportedType().isInstance(value)) { + return false; + } + + // Perform validation using the current validator + if (!invokeValidator(validator, value, context)) { + return false; + } + } + + return true; // All validators passed + } + + /** + * Invokes the validation logic of the given validator on the provided value. + * + * @param validator the dynamic validator to invoke + * @param value the value to validate + * @param context the constraint validator context for adding violation messages if needed + * @return true if the value is valid according to the validator, false otherwise + */ + private boolean invokeValidator(DynamicValidator validator, Object value, ConstraintValidatorContext context) { + var supportedType = validator.getSupportedType(); + if (!supportedType.isInstance(value)) { + return false; + } + return validator.isValid(supportedType.cast(value), context); + } + + /** + * Retrieves an instance of the specified DynamicValidator class from the Spring application context. + * + * @param clazz the class of the DynamicValidator to retrieve + * @return an instance of the specified DynamicValidator + * @throws IllegalStateException if the validator cannot be instantiated + */ + private DynamicValidator getValidatorInstance(Class> clazz) { + try { + return applicationContext.getBean(clazz); + } catch (NoSuchBeanDefinitionException e) { + throw new IllegalStateException("Failed to instantiate the validator: " + clazz.getName(), e); + } + } + + /** + * Sets the ApplicationContext that this object runs in. This method is called by the + * Spring framework during bean initialisation to provide the application context to + * this validator. + * + * @param applicationContext the ApplicationContext object to be used by this validator + */ + @Override + public void setApplicationContext(@NonNull ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } +} diff --git a/src/main/java/com/onixbyte/helix/validation/group/OnCreate.java b/src/main/java/com/onixbyte/helix/validation/group/OnCreate.java index d9799f8..6c16f2c 100644 --- a/src/main/java/com/onixbyte/helix/validation/group/OnCreate.java +++ b/src/main/java/com/onixbyte/helix/validation/group/OnCreate.java @@ -1,64 +1,12 @@ package com.onixbyte.helix.validation.group; /** - * Validation group interface for create operations. - *

- * This marker interface is used with Bean Validation (JSR-303/JSR-380) to define a validation group - * that is specifically applied during entity creation operations. By using validation groups, - * different validation rules can be applied depending on the context of the operation (create vs. - * update vs. other scenarios). - *

- * Usage Pattern: This interface is typically used in conjunction with validation - * annotations such as {@code @NotNull}, {@code @NotBlank}, {@code @Valid}, etc., to specify that - * certain validation constraints should only be applied when creating new entities. - *

- * Example Usage: - *

{@code
- * public class User {
- *     @NotNull(groups = OnCreate.class, message = "Username is required for new users")
- *     @Size(min = 3, max = 50, groups = OnCreate.class)
- *     private String username;
- *     
- *     @NotNull(groups = OnCreate.class, message = "Password is required for new users")
- *     @Size(min = 8, groups = OnCreate.class)
- *     private String password;
- *     
- *     // Other fields...
- * }
- * 
- * // In a REST controller:
- * @PostMapping("/users")
- * public ResponseEntity createUser(
- *     @Validated(OnCreate.class) @RequestBody User user) {
- *     // Create user logic
- * }
- * }
- *

- * Benefits of Validation Groups: - *

    - *
  • - * Context-Specific Validation: Apply different validation rules for different - * operations (e.g., password required on create but not on update) - *
  • - *
  • - * Flexible Constraint Application: Enable or disable specific validations based - * on the operation context - *
  • - *
  • - * Cleaner Code: Avoid complex conditional validation logic by using declarative - * group-based validation - *
  • - *
- *

- * This interface contains no methods and serves purely as a marker for the Bean Validation - * framework to identify which constraints should be applied during create operations. + * OnCreate is a validation group used to specify that certain validation rules should only be + * applied when creating a new entity. This allows for different validation logic to be executed + * during creation and update operations, ensuring that the appropriate constraints are enforced + * based on the context of the operation. * - * @author zihluwang - * @since 1.0.0 - * @see OnUpdate - * @see jakarta.validation.groups.Default - * @see org.springframework.validation.annotation.Validated - * @see Bean Validation Specification - Validation Groups + * @author siujamo */ public interface OnCreate { } diff --git a/src/main/java/com/onixbyte/helix/validation/group/OnUpdate.java b/src/main/java/com/onixbyte/helix/validation/group/OnUpdate.java index dcb17c0..a7737fa 100644 --- a/src/main/java/com/onixbyte/helix/validation/group/OnUpdate.java +++ b/src/main/java/com/onixbyte/helix/validation/group/OnUpdate.java @@ -1,84 +1,12 @@ package com.onixbyte.helix.validation.group; /** - * Validation group interface for update operations. - *

- * This marker interface is used with Bean Validation (JSR-303/JSR-380) to define a validation group - * that is specifically applied during entity update operations. By using validation groups, - * different validation rules can be applied depending on the context of the operation (update vs. - * create vs. other scenarios). - *

- * Usage Pattern: This interface is typically used in conjunction with validation - * annotations such as {@code @NotNull}, {@code @NotBlank}, {@code @Valid}, etc., to specify that - * certain validation constraints should only be applied when updating existing entities. - *

- * Example Usage: - *

{@code
- * public class User {
- *     @NotNull(groups = OnUpdate.class, message = "ID is required for updates")
- *     @Positive(groups = OnUpdate.class)
- *     private Long id;
- *     
- *     @Size(min = 3, max = 50, groups = OnUpdate.class)
- *     private String username;
- *     
- *     // Password might be optional on update
- *     @Size(min = 8, groups = OnUpdate.class)
- *     private String password;
- *     
- *     // Other fields...
- * }
- * 
- * // In a REST controller:
- * @PutMapping("/users/{id}")
- * public ResponseEntity updateUser(
- *     @PathVariable Long id,
- *     @Validated(OnUpdate.class) @RequestBody User user) {
- *     // Update user logic
- * }
- * }
- *

- * Common Update Validation Scenarios: - *

    - *
  • - * ID Validation: Ensure entity ID is present and valid for updates - *
  • - *
  • - * Optional Fields: Allow certain fields to be optional during updates that were - * required during creation - *
  • - *
  • - * Partial Updates: Support PATCH operations where only modified fields - * need validation - *
  • - *
  • - * Version Control: Validate version fields for optimistic locking - *
  • - *
- *

- * Benefits of Update-Specific Validation: - *

    - *
  • - * Flexible Field Requirements: Different fields may be required or optional - * compared to create operations - *
  • - *
  • - * Identity Validation: Ensure proper entity identification for update operations - *
  • - *
  • - * Conditional Logic: Apply validation rules specific to modification scenarios - *
  • - *
- *

- * This interface contains no methods and serves purely as a marker for the Bean Validation - * framework to identify which constraints should be applied during update operations. + * OnUpdate is a validation group used to specify that certain validation rules should only be + * applied when updating an existing entity. This allows for different validation logic to be + * executed during creation and update operations, ensuring that the appropriate constraints are + * enforced based on the context of the operation. * - * @author zihluwang - * @since 1.0.0 - * @see OnCreate - * @see jakarta.validation.groups.Default - * @see org.springframework.validation.annotation.Validated - * @see Bean Validation Specification - Validation Groups + * @author siujamo */ public interface OnUpdate { } diff --git a/src/main/java/com/onixbyte/helix/validation/validator/DynamicValidator.java b/src/main/java/com/onixbyte/helix/validation/validator/DynamicValidator.java new file mode 100644 index 0000000..c9ff8f0 --- /dev/null +++ b/src/main/java/com/onixbyte/helix/validation/validator/DynamicValidator.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024-2026 OnixByte + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.onixbyte.helix.validation.validator; + +import jakarta.validation.ConstraintValidatorContext; + +/** + * DynamicValidator is an interface for implementing dynamic validation logic that can be applied + * to various types of data. + * + * @param + * @author siujamo + */ +public interface DynamicValidator { + + /** + * Get the supported type of this validator. + * + * @return the supported type class + */ + Class getSupportedType(); + + /** + * Validate the given value and return whether it is valid according to the implemented logic. + * + * @param value the value to validate + * @param context the constraint validator context for adding violation messages if needed + * @return true if the value is valid, false otherwise + */ + boolean isValid(T value, ConstraintValidatorContext context); + + /** + * A helper method to add a violation message to the context. + * + * @param context the constraint validator context + * @param message the violation message to add + * @return the updated constraint validator context with the violation message added + */ + default ConstraintValidatorContext addViolationMessage(ConstraintValidatorContext context, String message) { + context.disableDefaultConstraintViolation(); + return context.buildConstraintViolationWithTemplate(message) + .addConstraintViolation(); + } +}