diff --git a/boot/platform/src/main/java/com/plate/boot/commons/converters/CollectionConverters.java b/boot/platform/src/main/java/com/plate/boot/commons/converters/CollectionConverters.java index 0e1cf8ad..bc5b0c20 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/converters/CollectionConverters.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/converters/CollectionConverters.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import com.plate.boot.commons.exception.JsonException; import com.plate.boot.commons.utils.ContextUtils; import io.r2dbc.postgresql.codec.Json; @@ -17,21 +18,78 @@ import java.util.Collection; /** - * @author Alex bob + * CollectionConverters is a Spring Configuration class responsible for initializing converters + * that facilitate the transformation between JSON and Java Collection types. It provides + * ReadingConverter and WritingConverter components to support these conversions seamlessly + * within a Spring application context, particularly useful in scenarios involving data binding + * with JSON data sources like databases or web requests. + * + *

This class implements InitializingBean, ensuring that the converters are properly initialized + * upon the application's startup sequence. + * + *

Components:

+ * + *

Both converters utilize {@link ContextUtils#OBJECT_MAPPER}, an ObjectMapper instance configured + * elsewhere in the application context, to perform the actual JSON serialization and deserialization. + * + * @see InitializingBean Spring's InitializingBean interface for callback on bean initialization. + * @see ReadingConverter Marker interface for converters reading from a MongoDB representation. + * @see WritingConverter Marker interface for converters writing to a MongoDB representation. + * @since Typically used in applications requiring data binding with JSON. */ @Log4j2 @Configuration(proxyBeanMethods = false) public class CollectionConverters implements InitializingBean { + /** + * Initializes the converters managed by this class. This method is part of the + * Spring Framework's {@link InitializingBean} contract, ensuring that the converters + * are properly set up when the Spring container initializes this bean. + *

+ * Logs a message indicating the start of the initialization process for the + * [CollectionConverters], which is crucial for preparing the application to handle + * JSON to Collection and vice versa transformations seamlessly. + * + * @see InitializingBean for more details on the callback contract for bean initialization. + */ @Override public void afterPropertiesSet() { log.info("Initializing converter [CollectionConverters]..."); } + /** + * CollectionReadConverter is a Spring component and a ReadingConverter that is designed to + * deserialize JSON data into a Collection of objects. It is part of the CollectionConverters + * configuration, ensuring seamless integration within a Spring context where conversion between + * JSON and Java Collections is required, such as when interacting with MongoDB or processing web requests. + *

This converter leverages the OBJECT_MAPPER from ContextUtils to perform JSON deserialization. + * It defines the conversion logic for transforming a Json representation into a Collection of generic type. + *

In the event of a JsonProcessingException during deserialization, this converter throws a + * JsonException, encapsulating the error details for further handling. + * + * @see ContextUtils For the setup of the OBJECT_MAPPER utilized in deserialization. + * @see JsonException For the exception type thrown upon JSON processing errors. + * @see CollectionConverters The parent configuration class managing this converter. + */ @Component @ReadingConverter public static class CollectionReadConverter implements Converter> { + /** + * Converts the given Json source into a Collection of objects. + *

+ * This method utilizes the OBJECT_MAPPER from ContextUtils to deserialize the JSON string + * represented by the Json source into a Collection of a generic type determined at runtime. + * In case of a JsonProcessingException during deserialization, a JsonException is thrown + * with a descriptive error message including the original exception's message. + * + * @param source The Json source to be converted into a Collection. Must not be null. + * @return A Collection of objects resulting from the JSON deserialization. + * @throws JsonException If an error occurs during the JSON processing or deserialization. + */ @Override public Collection convert(@NonNull Json source) { try { @@ -44,10 +102,34 @@ public Collection convert(@NonNull Json source) { } } + /** + * A converter class designed to serialize collections into their corresponding JSON representations. + * This class is annotated as a Spring Component and a WritingConverter, indicating its role within + * the Spring ConversionService for writing data to JSON format. + * + *

The primary method, {@link #convert(Collection)}, accepts a collection of any type and + * utilizes Jackson's `ObjectMapper` to serialize the collection into a byte array, which is then + * encapsulated within a {@link Json} object for further processing or transmission.

+ * + *

Note: The method throws a {@link JsonException} if the serialization process encounters an + * issue, providing a descriptive error message that includes the original exception's details.

+ */ @Component @WritingConverter public static class CollectionWriteConverter implements Converter, Json> { + /** + * Converts a given collection into its JSON representation. + * + * This method takes a collection of any type and attempts to serialize it into a JSON object + * using the Jackson {@link ObjectMapper}. If the serialization process encounters a + * {@link JsonProcessingException}, it is caught and rethrown as a {@link JsonException} with a + * descriptive error message, preserving the original exception's details. + * + * @param source The collection to be converted into JSON. Must not be null. + * @return A {@link Json} object representing the serialized form of the input collection. + * @throws JsonException If an error occurs during the JSON serialization process. + */ @Override public Json convert(@NonNull Collection source) { try { diff --git a/boot/platform/src/main/java/com/plate/boot/commons/converters/JsonNodeConverters.java b/boot/platform/src/main/java/com/plate/boot/commons/converters/JsonNodeConverters.java index b9e80fd8..d3fc9bfa 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/converters/JsonNodeConverters.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/converters/JsonNodeConverters.java @@ -16,29 +16,73 @@ import java.io.IOException; /** - * @author Alex bob + * Configuration class for registering custom converters between JSON and Jackson's JsonNode. + * This class ensures the converters are properly initialized within a Spring context. + * It includes two inner classes: {@link JsonToNodeWriteConverter} for writing JSON to JsonNode, + * and {@link JsonToNodeReadConverter} for reading JsonNode back to JSON. */ @Log4j2 @Configuration(proxyBeanMethods = false) public class JsonNodeConverters implements InitializingBean { + /** + * Invoked by the containing BeanFactory after it has set all bean properties + * and satisfied all dependencies for this bean. This method allows the bean instance + * to perform initialization only possible when all bean properties have been set + * and to throw an exception in the event of misconfiguration. + *

+ * In this implementation, it logs the initialization status of the [JsonNodeConverters], + * indicating that the converter setup process is being initiated. + */ @Override public void afterPropertiesSet() { log.info("Initializing converter [JsonNodeConverters]..."); } + /** + * Converts a Jackson {@link JsonNode} to a custom {@link Json} object for write operations. + * This converter is designed to be used within a Spring context and is annotated as a component + * and a writing converter. + *

+ * The conversion process involves converting the JsonNode to its string representation + * and then wrapping it into a Json object using the {@link Json#of(String)} method. + */ @Component @WritingConverter public static class JsonToNodeWriteConverter implements Converter { + /** + * Converts a Jackson {@link JsonNode} to a custom {@link Json} object. + * + * @param source The JsonNode to be converted. Must not be null. + * @return A {@link Json} object representing the stringified input JsonNode. + * @throws NullPointerException if the source argument is null. + */ @Override public Json convert(@NonNull JsonNode source) { return Json.of(source.toString()); } } + /** + * Converts a custom {@link Json} object to a Jackson {@link JsonNode} for read operations. + * This converter is designed to be used within a Spring context and is annotated as a component + * and a reading converter. + *

+ * The conversion process involves parsing the Json object into a JSON array and then + * using Jackson's ObjectMapper to read this array into a JsonNode structure. + * If an {@link IOException} occurs during the conversion, it is rethrown as a {@link JsonException} + * to propagate the error appropriately. + */ @Component @ReadingConverter public static class JsonToNodeReadConverter implements Converter { + /** + * Converts a custom {@link Json} object to a Jackson {@link JsonNode} for read operations. + * + * @param source The {@link Json} object to be converted. Must not be null. + * @return The converted {@link JsonNode} instance ready for read operations. + * @throws JsonException If an {@link IOException} occurs during the conversion process. + */ @Override public JsonNode convert(@NonNull Json source) { try { diff --git a/boot/platform/src/main/java/com/plate/boot/commons/converters/UserAuditorConverters.java b/boot/platform/src/main/java/com/plate/boot/commons/converters/UserAuditorConverters.java index b0d41958..88f2d1b8 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/converters/UserAuditorConverters.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/converters/UserAuditorConverters.java @@ -47,6 +47,13 @@ public void afterPropertiesSet() { @Component @WritingConverter public static class UserAuditorWriteConverter implements Converter { + /** + * Converts a {@link UserAuditor} object to its code represented as a String. + * + * @param source The {@link UserAuditor} instance to be converted. Must not be null. + * @return The code of the {@link UserAuditor} as a String. + * @throws NullPointerException if the source {@link UserAuditor} is null. + */ @Override public String convert(@NonNull UserAuditor source) { return source.code(); @@ -72,6 +79,20 @@ public String convert(@NonNull UserAuditor source) { @Component @ReadingConverter public static class UserAuditorReadConverter implements Converter { + /** + * Converts a given non-null String source representing a user auditor code + * into a {@link UserAuditor} instance. + *

+ * This method is part of the read conversion process, used to transform auditor codes + * stored as strings (for example, in a database) back into rich {@link UserAuditor} objects. + * It delegates the conversion to the {@link UserAuditor#withCode(String)} factory method, + * which reconstructs the auditor with the provided code while setting default null values for + * the username and name fields. + * + * @param source The non-null String containing the user auditor code to convert. + * @return A {@link UserAuditor} instance created using the provided code. + * @throws NullPointerException if the source is null. + */ @Override public UserAuditor convert(@NonNull String source) { return UserAuditor.withCode(source); diff --git a/boot/platform/src/main/java/com/plate/boot/commons/utils/BeanUtils.java b/boot/platform/src/main/java/com/plate/boot/commons/utils/BeanUtils.java index 7a923f71..f322fd16 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/utils/BeanUtils.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/utils/BeanUtils.java @@ -271,6 +271,13 @@ public void setMaxInMemorySize(DataSize dataSize) { MAX_IN_MEMORY_SIZE = dataSize; } + /** + * Initializes the utils bean by performing necessary setup steps. + * This method is called after all properties of this bean have been set. + * It is part of the Spring Framework's InitializingBean interface contract. + *

+ * Logs a message indicating the start of the initialization process for BeanUtils. + */ @Override public void afterPropertiesSet() { log.info("Initializing utils [BeanUtils]..."); diff --git a/boot/platform/src/main/java/com/plate/boot/commons/utils/ContextUtils.java b/boot/platform/src/main/java/com/plate/boot/commons/utils/ContextUtils.java index fbd5658a..3e241898 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/utils/ContextUtils.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/utils/ContextUtils.java @@ -23,20 +23,49 @@ import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; import java.util.Base64; import java.util.List; import java.util.Objects; /** - * @author Alex bob(Alex Bob) + * Utility class providing context-related operations and services for the application. + * This class is designed as a Spring Component and implements InitializingBean to ensure + * proper setup during application initialization. + *

+ * It offers functionalities such as encoding strings to MD5, retrieving client IP addresses, + * handling security details within reactive context, serializing UserAuditor objects, + * generating unique IDs, and initializing utility setup with logging. */ @Log4j2 @Component public final class ContextUtils implements InitializingBean { + /** + * Constant defining the role identifier for administrators within the system. + * This role grants access to administrative functionalities and permissions. + */ public final static String RULE_ADMINISTRATORS = "ROLE_ADMINISTRATORS"; + /** + * Constants for the context key used to store CSRF token information. + * This string represents the identifier used to retrieve or store CSRF tokens in a context such as a thread local, + * request attribute, or any other data structure where keys are strings. + */ public final static String CSRF_TOKEN_CONTEXT = "CSRF_TOKEN_CONTEXT"; + /** + * A constant holding a pre-initialized instance of {@link MessageDigest}. + * This digest can be used for cryptographic hashing operations. + */ + public final static MessageDigest MD; + /** + * An array of strings representing the possible header names that could contain + * the client's IP address in HTTP requests. This is typically used when working + * behind proxies or load balancers where the original client IP is not directly + * available in the standard {@code REMOTE_ADDR} header. + *

+ * The list includes common headers like {@code X-Forwarded-For}, {@code X-Real-IP}, + * and others that are often set by proxies to facilitate correct identification + * of the originating IP address of the client. + */ private final static String[] IP_HEADER_CANDIDATES = { "X-Forwarded-For", "X-Real-IP", @@ -52,8 +81,6 @@ public final class ContextUtils implements InitializingBean { "REMOTE_ADDR" }; - public final static MessageDigest MD; - static { try { MD = MessageDigest.getInstance("MD5"); @@ -61,20 +88,61 @@ public final class ContextUtils implements InitializingBean { throw RestServerException.withMsg("MD5 algorithm not found", e); } } - + /** + * A shared static instance of Jackson's {@link ObjectMapper}, which is utilized for + * serializing and deserializing Java objects to and from JSON format. This singleton pattern + * helps in ensuring consistent JSON processing across the application and reduces the + * overhead of creating multiple ObjectMapper instances. + * + *

Usage of this mapper should be considered for general-purpose JSON operations within the application, + * unless specific configurations are required for certain use cases, in which case a separate + * ObjectMapper instance with tailored configurations should be created. + * + *

It is important to note that since this is a static shared resource, any configuration changes + * made to this instance will affect all parts of the application using it. Therefore, caution must + * be exercised when modifying its settings. + */ public static ObjectMapper OBJECT_MAPPER; + /** + * Static reference to the UsersService instance. + * This service provides functionalities related to user management such as + * user retrieval, creation, update, and deletion. + */ public static UsersService USERS_SERVICE; + /** + * Initializes the ContextUtils class with necessary dependencies. + * + * @param objectMapper The ObjectMapper instance used for JSON serialization and deserialization. + * @param usersService The UsersService instance to provide access to user-related operations. + */ ContextUtils(ObjectMapper objectMapper, UsersService usersService) { ContextUtils.OBJECT_MAPPER = objectMapper; ContextUtils.USERS_SERVICE = usersService; } + /** + * Encodes the given input string into its MD5 hash representation, which is then + * base64 encoded for a more compact and URL-friendly format. + * + * @param input The string to be encoded into MD5 hash. + * @return A base64 encoded string representing the MD5 hash of the input string. + */ public static String encodeToMD5(String input) { byte[] bytes = MD.digest(input.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(bytes); } + /** + * Retrieves the client's IP address from the given server HTTP request. + * This method iterates over a set of common headers used to carry the client's IP address + * and returns the first non-empty value found. If none of these headers yield an IP, + * it falls back to extracting the IP from the remote address of the request. + * + * @param httpRequest The server HTTP request from which to extract the client's IP address. + * @return The client's IP address as a string, or the remote address's host address if no + * explicit IP is found in the headers. + */ public static String getClientIpAddress(ServerHttpRequest httpRequest) { HttpHeaders headers = httpRequest.getHeaders(); for (String header : IP_HEADER_CANDIDATES) { @@ -86,22 +154,56 @@ public static String getClientIpAddress(ServerHttpRequest httpRequest) { return Objects.requireNonNull(httpRequest.getRemoteAddress()).getAddress().getHostAddress(); } + /** + * Retrieves the security details of the currently authenticated user. + *

+ * This method accesses the security context asynchronously and extracts the principal, + * which is then cast to a {@link SecurityDetails} object. It is designed to be used + * within a reactive environment where security information is required for further processing. + *

+ * + * @return A {@link Mono} emitting the {@link SecurityDetails} associated with the + * current authentication context, or an empty Mono if no authentication is present. + */ public static Mono securityDetails() { return ReactiveSecurityContextHolder.getContext() .map(securityContext -> securityContext.getAuthentication().getPrincipal()) .cast(SecurityDetails.class); } + /** + * Serializes the {@link UserAuditor} objects contained within the properties of the given input object. + * This method inspects the object's properties to identify those of type {@link UserAuditor} and applies + * a serialization process to them. It is designed to work with objects that may contain auditor fields + * which need to be processed before further operations, such as saving to a database or sending over a network. + * + * @param The generic type of the object whose properties are to be serialized. + * @param object The object whose properties are to be inspected and potentially serialized. + * @return A {@link Mono} wrapping the original object after all {@link UserAuditor} properties have been processed. + * The Mono completes when all processing is finished. + */ public static Mono serializeUserAuditor(T object) { PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(object.getClass()); - var setPropertyStream = Arrays.stream(propertyDescriptors) - .filter(propertyDescriptor -> propertyDescriptor.getPropertyType() == UserAuditor.class); - var propertyFlux = Flux.fromStream(setPropertyStream) - .flatMap(propertyDescriptor -> handlePropertyDescriptor(object, propertyDescriptor)); + var propertyFlux = Flux.fromArray(propertyDescriptors) + .filter(propertyDescriptor -> propertyDescriptor.getPropertyType() == UserAuditor.class) + .flatMap(propertyDescriptor -> serializeUserAuditorProperty(object, propertyDescriptor)); return propertyFlux.then(Mono.just(object)); } - private static Mono handlePropertyDescriptor(T object, PropertyDescriptor propertyDescriptor) { + /** + * Handles the property descriptor for an object, particularly focusing on serializing + * a {@link UserAuditor} within the given object by fetching additional user details + * and updating the object accordingly. + * + * @param The type of the object containing the property to be handled. + * @param object The object whose property described by {@code propertyDescriptor} is to be processed. + * @param propertyDescriptor The descriptor of the property within {@code object} that refers to a {@link UserAuditor}. + * @return A {@link Mono} emitting a message indicating success or failure: + * - If the user auditor is empty, emits a warning message and completes. + * - If successful in serializing the user auditor, emits a success message and completes. + * - Emits an error signal if there are issues invoking methods on the property descriptor. + */ + private static Mono serializeUserAuditorProperty(T object, PropertyDescriptor propertyDescriptor) { try { UserAuditor userAuditor = (UserAuditor) propertyDescriptor.getReadMethod().invoke(object); if (ObjectUtils.isEmpty(userAuditor)) { @@ -126,11 +228,29 @@ private static Mono handlePropertyDescriptor(T object, PropertyDescr } } + /** + * Generates the next unique identifier using ULID (Universally Unique Lexicographically Sortable Identifier). + *

+ * This method utilizes the {@link UlidCreator#getMonotonicUlid()} to produce ULIDs which are: + * - Globally unique across space and time. + * - Lexicographically sortable. + * - Monotonic in time. + *

+ * + * @return A string representation of the generated ULID, which can be used as a unique identifier. + */ public static String nextId() { Ulid ulid = UlidCreator.getMonotonicUlid(); return ulid.toString(); } + /** + * Overrides the {@link InitializingBean#afterPropertiesSet()} method. + * This method is called by the containing BeanFactory after it has set all bean properties. + * It is typically used to perform initialization that requires all properties to be set. + *

+ * Logs a message indicating the initialization of the [ContextUtils] component. + */ @Override public void afterPropertiesSet() { log.info("Initializing utils [ContextUtils]..."); diff --git a/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryFragment.java b/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryFragment.java index 5612045d..4e03e953 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryFragment.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryFragment.java @@ -50,10 +50,27 @@ public Map params() { return this; } + /** + * Retrieves the {@link StringJoiner} instance containing the dynamically + * constructed SQL fragments of this {@link QueryFragment} object. + *

+ * This method is useful for accessing the SQL fragment that represents + * a part of a query, such as a WHERE clause, which can be further + * incorporated into a complete SQL statement. + * + * @return The {@link StringJoiner} object holding the concatenated SQL segments. + */ public StringJoiner sql() { return this.sql; } + /** + * Generates the WHERE clause part of a SQL query based on the stored conditions. + * If conditions have been accumulated, it prefixes the conditions with the 'WHERE' keyword; + * otherwise, it returns an empty string to indicate no conditions. + * + * @return A String forming the WHERE clause of the SQL query, or an empty string if no conditions are present. + */ public String whereSql() { if (this.sql.length() > 0) { return " where " + this.sql;