diff --git a/README.md b/README.md index fab1055..fe82760 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This README provides quickstart instructions on running [`fw-commons`]() on bare [![Maven Central](https://img.shields.io/badge/maven--central-1.1.0-orange.svg?style=plastic&logo=apachemaven)](https://central.sonatype.com/artifact/io.github.hoangtien2k3/fw-commons/1.1.0) [![Gradle](https://img.shields.io/badge/gradle-1.1.0-orange.svg?style=plastic&logo=apachemaven)](https://central.sonatype.com/artifact/io.github.hoangtien2k3/fw-commons/1.1.0) -### ⬇️ [Download From Gradle and Maven Central](https://central.sonatype.com/artifact/cn.ponfee/commons-core/1.4) +#### ⬇️ [Download From Gradle and Maven Central](https://central.sonatype.com/artifact/io.github.hoangtien2k3/fw-commons/1.1.0) #### Gradle diff --git a/pom.xml b/pom.xml index be2b157..23190c4 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ Hoang Anh Tien hoangtien2k3qx1@gmail.com - io.hoangtien2k3 + io.github.hoangtien2k3 https://github.com/hoangtien2k3 @@ -433,6 +433,7 @@ ${maven-javadoc-plugin.version} ${java.home}/bin/javadoc + false @@ -507,7 +508,8 @@ - + + + ]]> + diff --git a/src/main/java/io/hoangtien2k3/commons/annotations/LogPerformance.java b/src/main/java/io/hoangtien2k3/commons/annotations/LogPerformance.java index f47f741..f7fc552 100644 --- a/src/main/java/io/hoangtien2k3/commons/annotations/LogPerformance.java +++ b/src/main/java/io/hoangtien2k3/commons/annotations/LogPerformance.java @@ -19,16 +19,108 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * The {@code LogPerformance} annotation is used to mark methods or classes for + * performance logging. It allows developers to specify what type of information + * should be logged when the annotated method or class is executed, including + * input arguments, output results, and other custom information. + * + *

+ * This annotation is typically applied to methods in a service layer where + * performance monitoring is essential. The annotation can be configured to log + * specific types of data, such as input arguments, output results, and the + * duration of method execution. The logged information can then be used for + * performance analysis, debugging, and auditing purposes. + *

+ * + *

Example Usage:

+ * + *
{@code
+ * @LogPerformance(logType = "API_CALL", actionType = "FETCH_DATA", logInput = true, logOutput = true, title = "Fetching Data")
+ * public Mono fetchData(String id) {
+ * 	// Method implementation
+ * }
+ * }
+ * + *

+ * In this example, the `fetchData` method is annotated with + * {@code @LogPerformance}, which will log the input arguments, output results, + * and execution duration when the method is called. The log will include the + * specified `logType`, `actionType`, and `title`. + *

+ * + *

Retention and Target:

+ *

+ * The annotation is retained at runtime ({@link RetentionPolicy#RUNTIME}) and + * can be applied to both methods and types (classes or interfaces) + * ({@link ElementType#METHOD}, {@link ElementType#TYPE}). + *

+ */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface LogPerformance { + + /** + * Specifies the type of log entry, such as "API_CALL", "DB_QUERY", etc. + *

+ * This value helps categorize logs based on the type of operation being logged. + * For instance, you might use different log types for API calls versus database + * queries to differentiate them in your log analysis. + *

+ * + * @return the type of log entry + */ String logType() default ""; + /** + * Specifies the type of action being performed, such as "CREATE", "UPDATE", + * "DELETE", etc. + *

+ * This value can be used to identify what kind of action is being logged, + * providing more context for the log entry. For example, you might have actions + * like "FETCH_DATA", "SAVE_ENTITY", etc. + *

+ * + * @return the type of action being logged + */ String actionType() default ""; + /** + * Indicates whether the output (result) of the method should be logged. + *

+ * When set to {@code true}, the output of the method will be logged, provided + * it can be serialized. This is useful for tracing the results of method + * executions, especially when debugging or monitoring system behavior. + *

+ * + * @return {@code true} if the output should be logged, {@code false} otherwise + */ boolean logOutput() default true; + /** + * Indicates whether the input arguments of the method should be logged. + *

+ * When set to {@code true}, the input arguments to the method will be logged. + * This is useful for capturing the state of the inputs when the method was + * called, which can be important for understanding the context of the log + * entry. + *

+ * + * @return {@code true} if the input arguments should be logged, {@code false} + * otherwise + */ boolean logInput() default true; + /** + * Specifies a custom title for the log entry. + *

+ * This title can be used to provide a brief description of the logged + * operation, making it easier to identify and search for specific log entries + * in your logging system. For example, the title might be something like "User + * Login Attempt" or "Order Processing". + *

+ * + * @return the custom title for the log entry + */ String title() default ""; } diff --git a/src/main/java/io/hoangtien2k3/commons/annotations/cache/CustomizeRemovalListener.java b/src/main/java/io/hoangtien2k3/commons/annotations/cache/CustomizeRemovalListener.java index b7bdb4a..ec53201 100644 --- a/src/main/java/io/hoangtien2k3/commons/annotations/cache/CustomizeRemovalListener.java +++ b/src/main/java/io/hoangtien2k3/commons/annotations/cache/CustomizeRemovalListener.java @@ -22,16 +22,62 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +/** + * This class implements the {@link RemovalListener} interface and provides + * custom handling for cache entry removals. It logs information when a cache + * entry is evicted and invokes a specified method. + * + *

+ * The class uses Lombok annotations {@code @Slf4j} for logging and + * {@code @AllArgsConstructor} to generate a constructor with one parameter for + * each field in the class. + * + *

+ * This listener is typically used in caching mechanisms where specific actions + * need to be taken when an entry is removed due to eviction. + * + *

+ * The listener relies on a {@link Method} instance, which is passed through the + * constructor. This method is invoked whenever a cache entry associated with it + * is evicted. + * + *

+ * Example usage: + * + *

+ * {@code
+ * Cache cache = Caffeine.newBuilder().removalListener(new CustomizeRemovalListener(someMethod))
+ * 		.build();
+ * }
+ * 
+ * + * @author Your Name + */ @Slf4j @AllArgsConstructor public class CustomizeRemovalListener implements RemovalListener { - private Method method; + private final Method method; + /** + * Handles the removal of a cache entry. If the removal cause is eviction, this + * method logs the event and invokes the method provided during the creation of + * this listener. + * + * @param key + * the key of the removed cache entry, can be null + * @param value + * the value of the removed cache entry, can be null + * @param removalCause + * the cause of the removal, never null + */ @Override public void onRemoval(@Nullable Object key, @Nullable Object value, @NonNull RemovalCause removalCause) { if (removalCause.wasEvicted()) { - log.info("Cache " + method.getDeclaringClass().getSimpleName() + "." + method.getName() - + " was evicted because " + removalCause); + log.info( + "Cache {}.{} was evicted because {}", + method.getDeclaringClass().getSimpleName(), + method.getName(), + removalCause); CacheUtils.invokeMethod(method); } } diff --git a/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerAspect.java b/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerAspect.java index a8f0400..c7bd70e 100644 --- a/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerAspect.java +++ b/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerAspect.java @@ -21,12 +21,59 @@ import org.aspectj.lang.annotation.Pointcut; import org.springframework.context.annotation.Configuration; +/** + * This aspect class is responsible for logging the performance of methods in + * the specified packages. It uses AOP (Aspect-Oriented Programming) to + * intercept method calls and log relevant information before and after the + * method execution. + * + *

+ * The class uses Spring AOP and relies on two pointcuts to identify which + * methods should be logged: + *

    + *
  • {@code performancePointCut}: Matches methods in the specified packages + * (controller, service, repository, client) except for those in the Spring Boot + * Actuator package.
  • + *
  • {@code logPerfMethods}: Matches methods annotated with + * {@link io.hoangtien2k3.commons.annotations.LogPerformance}.
  • + *
+ * + *

+ * The {@code @Around} advice is applied to the methods matched by these + * pointcuts, and it delegates the logging logic to {@link LoggerAspectUtils}. + * + *

+ * Example of usage: + * + *

+ * {@code
+ * @LogPerformance
+ * public void someMethod() {
+ * 	// method implementation
+ * }
+ * }
+ * 
+ * + *

+ * The class is annotated with {@code @Aspect} to define it as an aspect, and + * {@code @Configuration} to ensure it is registered as a Spring bean. It also + * uses {@code @RequiredArgsConstructor} from Lombok to generate a constructor + * that injects the required dependencies. + * + * @author hoangtien2k3 + */ @Aspect @Configuration @RequiredArgsConstructor public class LoggerAspect { + private final LoggerAspectUtils loggerAspectUtils; + /** + * Pointcut that matches the execution of any method within the specified + * packages (controller, service, repository, client) except for those under the + * Spring Boot Actuator package. + */ @Pointcut("(execution(* io.hoangtien2k3.commons.*.controller..*(..)) || " + "execution(* io.hoangtien2k3.commons.*.service..*(..)) || " + "execution(* io.hoangtien2k3.commons.*.repository..*(..)) || " @@ -34,9 +81,24 @@ public class LoggerAspect { + "!execution(* org.springframework.boot.actuate..*(..))") public void performancePointCut() {} + /** + * Pointcut that matches methods annotated with + * {@link io.hoangtien2k3.commons.annotations.LogPerformance}. + */ @Pointcut("@annotation(io.hoangtien2k3.commons.annotations.LogPerformance)") private void logPerfMethods() {} + /** + * Around advice that wraps the matched method executions, providing logging + * functionality around the method invocation. The actual logging logic is + * delegated to {@link LoggerAspectUtils#logAround}. + * + * @param joinPoint + * the join point representing the method execution + * @return the result of the method execution + * @throws Throwable + * if the method execution throws an exception + */ @Around("performancePointCut() || logPerfMethods()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { return loggerAspectUtils.logAround(joinPoint); diff --git a/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerAspectUtils.java b/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerAspectUtils.java index cbf6efd..a7c6f8e 100644 --- a/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerAspectUtils.java +++ b/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerAspectUtils.java @@ -33,6 +33,41 @@ import reactor.core.publisher.Mono; import reactor.util.context.Context; +/** + * Utility class for logging method execution performance using Aspect-Oriented + * Programming (AOP) in Spring. This class handles logging for methods returning + * reactive types (`Mono` and `Flux`) and integrates with distributed tracing + * using Spring Cloud Sleuth. + * + *

+ * The class provides functionality to: + *

    + *
  • Log method execution time for methods matched by the logging aspect.
  • + *
  • Handle logging of input and output data conditionally based on the + * {@link LogPerformance} annotation.
  • + *
  • Capture and log exceptions, with optional detailed logging based on + * configuration.
  • + *
+ * + *

+ * This class is annotated with `@Component` to be managed by the Spring + * container and `@RequiredArgsConstructor` to inject required dependencies + * automatically. + * + *

+ * Example of usage: + * + *

+ * {@code
+ * @LogPerformance(logOutput = true, logInput = true, logType = "MyLogType", actionType = "MyActionType")
+ * public Mono myMethod() {
+ * 	// method implementation
+ * }
+ * }
+ * 
+ * + * @author Your Name + */ @Component @RequiredArgsConstructor public class LoggerAspectUtils { @@ -45,9 +80,28 @@ public class LoggerAspectUtils { @Value("${debug.detailException:true}") private boolean detailException; + /** + * Initializes the utility class after the dependency injection is done to + * perform any necessary setup. + */ @PostConstruct - private void init() {} + private void init() { + // Initialization logic if necessary + } + /** + * Logs method execution performance around the join point. It handles logging + * for methods that return reactive types (`Mono` or `Flux`). The logging + * includes input arguments, output results, execution time, and any exceptions + * that occur during method execution. + * + * @param joinPoint + * the join point representing the method execution + * @return the result of the method execution, possibly wrapped in a `Mono` or + * `Flux` + * @throws Throwable + * if the method execution throws an exception + */ public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); @@ -79,20 +133,50 @@ public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { var result = joinPoint.proceed(); if (result instanceof Mono) { return logMonoResult( - joinPoint, start, (Mono) result, newSpan, name, logType, actionType, logOutput, logInput, title); + joinPoint, start, (Mono) result, newSpan, name, logType, actionType, logOutput, logInput, title); } if (result instanceof Flux) { return logFluxResult( - joinPoint, start, (Flux) result, newSpan, name, logType, actionType, logOutput, logInput, title); + joinPoint, start, (Flux) result, newSpan, name, logType, actionType, logOutput, logInput, title); } else { return result; } } - private Mono logMonoResult( + /** + * Handles logging for methods that return a `Mono`. It logs the input + * arguments, output results, execution time, and any exceptions that occur + * during the execution of the `Mono`. + * + * @param joinPoint + * the join point representing the method execution + * @param start + * the start time of the method execution + * @param result + * the `Mono` returned by the method + * @param newSpan + * the tracing span associated with the method execution + * @param name + * the name of the method being logged + * @param logType + * the type of log (can be customized via `LogPerformance` + * annotation) + * @param actionType + * the action type (can be customized via `LogPerformance` + * annotation) + * @param logOutput + * whether to log the output of the method + * @param logInput + * whether to log the input arguments of the method + * @param title + * the title for the log entry (can be customized via + * `LogPerformance` annotation) + * @return the `Mono` with added logging + */ + private Mono logMonoResult( ProceedingJoinPoint joinPoint, long start, - Mono result, + Mono result, Span newSpan, String name, String logType, @@ -115,10 +199,6 @@ private Mono logMonoResult( .contextWrite(context -> { var currContext = (Context) context; contextRef.set(currContext); - // the error happens in a different thread, so get the trace from context, set - // in MDC - // and downstream - // to doOnError return context; }) .doOnError(o -> { @@ -133,10 +213,40 @@ private Mono logMonoResult( }); } - private Flux logFluxResult( + /** + * Handles logging for methods that return a `Flux`. It logs the input + * arguments, output results, execution time, and any exceptions that occur + * during the execution of the `Flux`. + * + * @param joinPoint + * the join point representing the method execution + * @param start + * the start time of the method execution + * @param result + * the `Flux` returned by the method + * @param newSpan + * the tracing span associated with the method execution + * @param name + * the name of the method being logged + * @param logType + * the type of log (can be customized via `LogPerformance` + * annotation) + * @param actionType + * the action type (can be customized via `LogPerformance` + * annotation) + * @param logOutput + * whether to log the output of the method + * @param logInput + * whether to log the input arguments of the method + * @param title + * the title for the log entry (can be customized via + * `LogPerformance` annotation) + * @return the `Flux` with added logging + */ + private Flux logFluxResult( ProceedingJoinPoint joinPoint, long start, - Flux result, + Flux result, Span newSpan, String name, String logType, @@ -151,10 +261,6 @@ private Flux logFluxResult( .contextWrite(context -> { var currContext = (Context) context; contextRef.set(currContext); - // the error happens in a different thread, so get the trace from context, set - // in MDC - // and downstream - // to doOnError return context; }) .doOnError(o -> { @@ -162,6 +268,23 @@ private Flux logFluxResult( }); } + /** + * Logs performance details such as method name, duration, result, and any + * exceptions encountered during execution. + * + * @param contextRef + * reference to the current context + * @param newSpan + * the tracing span associated with the method execution + * @param name + * the name of the method being logged + * @param start + * the start time of the method execution + * @param result + * the result code ("0" for success, "1" for failure) + * @param o + * the output object or exception + */ private void logPerf( AtomicReference contextRef, Span newSpan, String name, Long start, String result, Object o) { newSpan.finish(); @@ -170,6 +293,21 @@ private void logPerf( logPerf.info("{} {} {} M2 {}", name, duration, result, o == null ? "-" : o.toString()); } + /** + * Logs performance details with additional parameters for more granular + * control. + * + * @param contextRef + * reference to the current context + * @param newSpan + * the tracing span associated with the method execution + * @param name + * the name of the method being logged + * @param startTime + * the start time of the method execution + * @param result + * the result code + */ private void logPerf( AtomicReference contextRef, Span newSpan, diff --git a/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerQueue.java b/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerQueue.java index c58e0a7..a4648b3 100644 --- a/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerQueue.java +++ b/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerQueue.java @@ -24,18 +24,38 @@ import lombok.extern.slf4j.Slf4j; import reactor.util.context.Context; +/** + * LoggerQueue is a singleton class that manages a bounded blocking queue for + * logging operations. It is designed to handle high-throughput logging tasks by + * storing logs in a queue and providing various methods to interact with the + * queue. + */ @Slf4j public class LoggerQueue { + + /** Singleton instance of LoggerQueue */ private static LoggerQueue mMe = null; + + /** Queue to store LoggerDTO objects, with a capacity of 100,000 */ private ArrayBlockingQueue myQueue = null; + + /** Lock object used for synchronization (currently not in use) */ private static final Object myLock = new Object(); + /** Counter for failed attempts to add logs to the queue */ @Getter private int countFalse = 0; + /** Counter for successful attempts to add logs to the queue */ @Getter private int countSuccess = 0; + /** + * Returns the singleton instance of LoggerQueue. If the instance is null, a new + * one is created. + * + * @return the singleton instance of LoggerQueue + */ public static LoggerQueue getInstance() { if (mMe == null) { mMe = new LoggerQueue(); @@ -43,18 +63,39 @@ public static LoggerQueue getInstance() { return mMe; } + /** + * Private constructor to initialize the queue with a fixed capacity of 100,000. + * This ensures the class follows the singleton pattern. + */ private LoggerQueue() { myQueue = new ArrayBlockingQueue<>(100000) {}; } + /** + * Clears all elements from the queue. + */ public void clearQueue() { myQueue.clear(); } + /** + * Retrieves and removes the head of the queue, or returns null if the queue is + * empty. + * + * @return the head of the queue, or null if the queue is empty + */ public LoggerDTO getQueue() { return myQueue.poll(); } + /** + * Adds a LoggerDTO task to the queue. Increments countSuccess if successful, + * otherwise increments countFalse. + * + * @param task + * the LoggerDTO task to add to the queue + * @return true if the task was successfully added, false otherwise + */ public boolean addQueue(LoggerDTO task) { if (myQueue.add(task)) { countSuccess++; @@ -64,6 +105,34 @@ public boolean addQueue(LoggerDTO task) { return false; } + /** + * Adds a LoggerDTO task to the queue with detailed parameters. This method + * wraps the parameters into a LoggerDTO object and adds it to the queue. + * + * @param contextRef + * the Reactor context reference + * @param newSpan + * the span from Sleuth tracing + * @param service + * the service name + * @param startTime + * the start time of the operation + * @param endTime + * the end time of the operation + * @param result + * the result of the operation ("0" for success, "1" for failure) + * @param obj + * additional information about the result + * @param logType + * the type of log + * @param actionType + * the type of action being logged + * @param args + * the method arguments + * @param title + * a custom title for the log entry + * @return true if the task was successfully added, false otherwise + */ public boolean addQueue( AtomicReference contextRef, Span newSpan, @@ -83,11 +152,18 @@ public boolean addQueue( return true; } } catch (Exception ex) { + log.error("Failed to add log to the queue", ex); } countFalse++; return false; } + /** + * Retrieves a list of up to 100,000 LoggerDTO objects from the queue. The queue + * is drained to the provided list. + * + * @return a list of LoggerDTO objects + */ public List getRecords() { List records = new ArrayList<>(); if (myQueue != null) { @@ -96,10 +172,19 @@ public List getRecords() { return records; } + /** + * Returns the current size of the queue. + * + * @return the number of elements in the queue + */ public int getQueueSize() { return myQueue.size(); } + /** + * Resets the counters for successful and failed attempts to add logs to the + * queue. + */ public void resetCount() { countSuccess = 0; countFalse = 0; diff --git a/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerSchedule.java b/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerSchedule.java index 615b5a1..fe62495 100644 --- a/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerSchedule.java +++ b/src/main/java/io/hoangtien2k3/commons/annotations/logging/LoggerSchedule.java @@ -35,34 +35,111 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +/** + * The {@code LoggerSchedule} class is a Spring-managed configuration class + * responsible for processing and logging records from the {@link LoggerQueue}. + * This class schedules a task that runs periodically to extract log data, + * process it, and log it using a performance-specific logger. The log data + * includes information such as trace IDs, IP addresses, request IDs, input + * arguments, responses, and more. + * + *

+ * This class is designed to handle log records in a non-blocking, reactive web + * application environment, typically using Spring WebFlux. It extracts useful + * information from the application context and logs it for performance + * monitoring and debugging purposes. + *

+ * + *

Example Usage:

+ * + *
+ * {
+ * 	@code
+ * 	@Configuration
+ * 	@RequiredArgsConstructor
+ * 	@Slf4j
+ * 	public class LoggerSchedule {
+ *
+ * 		// Scheduled task to process log records every 3 seconds
+ * 		@Scheduled(fixedDelay = 3000)
+ * 		public void scheduleSaveLogClick() {
+ * 			// implementation
+ * 		}
+ *
+ * 		// Additional methods for processing records
+ * 	}
+ * }
+ * 
+ * + *

+ * This class is typically used in conjunction with a log queue manager like + * {@link LoggerQueue} that accumulates log data asynchronously from various + * parts of the application. The scheduled task then retrieves and processes + * these logs at regular intervals. + *

+ * + *

Thread Safety:

+ *

+ * This class is thread-safe, as the scheduled method + * {@link #scheduleSaveLogClick()} is executed periodically in a controlled + * manner by the Spring framework's task scheduler. Internally, it relies on the + * {@link LoggerQueue}, which is backed by a thread-safe queue implementation. + *

+ */ @Configuration @RequiredArgsConstructor @Slf4j public class LoggerSchedule { + + /** + * Logger instance specifically used for logging performance-related + * information. This logger is configured separately from the main application + * logger to focus on performance metrics. + */ private static final Logger logPerf = LoggerFactory.getLogger("perfLogger"); + /** + * Scheduled task that runs every 3 seconds to process the log records in the + * {@link LoggerQueue}. + *

+ * This method retrieves log records from the queue, processes each record, and + * logs the details. If an error occurs while processing a record, it is caught + * and logged separately. After processing, the method resets the counters for + * successful and failed log additions to the queue. + *

+ */ @Scheduled(fixedDelay = 3000) public void scheduleSaveLogClick() { long analyId = System.currentTimeMillis(); int numSuccess = 0; int numFalse = 0; List records = LoggerQueue.getInstance().getRecords(); + for (LoggerDTO record : records) { try { process(record); numSuccess++; } catch (Exception e) { numFalse++; - log.error("Error while handle record queue: ", e.getMessage()); + log.error("Error while handling record in the queue: ", e.getMessage()); } } - // log.info("AsyncLog analyId {}: QueueSize: {}, addSuccess: {}, addFalse: {}, - // writeSuccess:{}, writeFalse:{}", - // analyId, records.size(), LoggerQueue.getInstance().getCountSuccess(), - // LoggerQueue.getInstance().getCountFalse(), numSuccess, numFalse); + LoggerQueue.getInstance().resetCount(); } + /** + * Processes a single {@link LoggerDTO} record by extracting relevant + * information and logging it. + *

+ * This method handles trace IDs, IP addresses, request IDs, input arguments, + * responses, and more. It also applies necessary truncations to the input and + * output data to ensure they don't exceed the maximum allowed size. + *

+ * + * @param record + * The {@link LoggerDTO} record to be processed and logged. + */ private void process(LoggerDTO record) { if (record != null) { String traceId = @@ -71,6 +148,7 @@ private void process(LoggerDTO record) { : ""; String ipAddress = null; String requestId = null; + if (record.getContextRef().get() != null) { if (record.getContextRef().get().hasKey(ServerWebExchange.class)) { ServerWebExchange serverWebExchange = @@ -78,11 +156,8 @@ private void process(LoggerDTO record) { ServerHttpRequest request = serverWebExchange.getRequest(); ipAddress = RequestUtils.getIpAddress(request); - if (serverWebExchange.getRequest() != null - && serverWebExchange.getRequest().getHeaders() != null - && !DataUtil.isNullOrEmpty( - serverWebExchange.getRequest().getHeaders().getFirst("Request-Id"))) { - requestId = serverWebExchange.getRequest().getHeaders().getFirst("Request-Id"); + if (request != null && request.getHeaders() != null) { + requestId = request.getHeaders().getFirst("Request-Id"); } } } @@ -90,33 +165,33 @@ private void process(LoggerDTO record) { String inputs = null; try { if (record.getArgs() != null) { - inputs = ObjectMapperFactory.getInstance().writeValueAsString(getAgrs(record.getArgs())); + inputs = ObjectMapperFactory.getInstance().writeValueAsString(getArgs(record.getArgs())); } } catch (Exception ex) { - log.error("Error while handle record queue: ", ex.getMessage()); + log.error("Error while processing input arguments: ", ex.getMessage()); } String resStr = null; try { if (record.getResponse() instanceof Optional) { - Optional output = (Optional) record.getResponse(); + Optional output = (Optional) record.getResponse(); if (output.isPresent()) { resStr = ObjectMapperFactory.getInstance().writeValueAsString(output.get()); } - } else { - if (record.getResponse() != null) { - resStr = ObjectMapperFactory.getInstance().writeValueAsString(record.getResponse()); - } + } else if (record.getResponse() != null) { + resStr = ObjectMapperFactory.getInstance().writeValueAsString(record.getResponse()); } } catch (Exception ex) { - log.error("Error while handle record queue: ", ex.getMessage()); + log.error("Error while processing response: ", ex.getMessage()); } + try { inputs = TruncateUtils.truncate(inputs, MAX_BYTE); resStr = TruncateUtils.truncate(resStr, MAX_BYTE); } catch (Exception ex) { - log.error("Truncate input/output error ", ex); + log.error("Error while truncating input/output data: ", ex); } + logInfo(new LogField( traceId, requestId, @@ -134,26 +209,45 @@ private void process(LoggerDTO record) { } } + /** + * Logs the given {@link LogField} object as a JSON string. + * + * @param logField + * The {@link LogField} object containing the log data. + */ private void logInfo(LogField logField) { try { logPerf.info(ObjectMapperFactory.getInstance().writeValueAsString(logField)); } catch (Exception ex) { - log.error("Error while handle record queue: ", ex.getMessage()); + log.error("Error while logging performance information: ", ex.getMessage()); } } - private List getAgrs(Object[] args) { + /** + * Extracts and returns a list of non-reactive arguments from the given array of + * arguments. + *

+ * This method filters out arguments that are instances of {@link Mono} or + * {@link ServerWebExchange} to avoid unnecessary logging of reactive streams or + * web exchange objects. + *

+ * + * @param args + * The array of arguments to be processed. + * @return A list of processed arguments excluding {@link Mono} and + * {@link ServerWebExchange}. + */ + private List getArgs(Object[] args) { List listArg = new ArrayList<>(); - for (int i = 0; i < args.length; i++) { - if (args[i] instanceof Mono) { - // listArg.add(((Mono) args[i]).block()); - // skip - } else if (args[i] instanceof ServerWebExchange) { - // skip - } else { - listArg.add(args[i]); + + for (Object arg : args) { + if (arg instanceof Mono || arg instanceof ServerWebExchange) { + // Skip reactive and web exchange arguments + continue; } + listArg.add(arg); } + return listArg; } } diff --git a/src/main/java/io/hoangtien2k3/commons/client/properties/WebClientProperties.java b/src/main/java/io/hoangtien2k3/commons/client/properties/WebClientProperties.java index 4b91c3b..7af862d 100644 --- a/src/main/java/io/hoangtien2k3/commons/client/properties/WebClientProperties.java +++ b/src/main/java/io/hoangtien2k3/commons/client/properties/WebClientProperties.java @@ -21,21 +21,138 @@ import lombok.NoArgsConstructor; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +/** + * Configuration properties for setting up a WebClient instance. This class + * encapsulates various settings required to configure a WebClient, including + * connection details, authentication, timeout settings, retry policies, + * logging, monitoring, and proxy configurations. + * + *

+ * This class is typically used in a Spring Boot application to configure + * WebClient beans via properties files or programmatically. The properties can + * be populated using Spring's configuration mechanism, such as with application + * properties or YAML files. + *

+ * + *

Example Usage:

+ * + *
{@code
+ * WebClientProperties webClientProperties = new WebClientProperties();
+ * webClientProperties.setName("exampleClient");
+ * webClientProperties.setAddress("https://api.example.com");
+ * webClientProperties.setUsername("user");
+ * webClientProperties.setPassword("pass");
+ * // Set other properties as needed
+ * }
+ * + *

+ * In this example, the `WebClientProperties` class is used to configure a + * WebClient instance, setting the connection address, credentials, and other + * relevant properties. + *

+ */ @Data @AllArgsConstructor @NoArgsConstructor public class WebClientProperties { + + /** + * The name of the WebClient configuration. This can be used to identify + * different WebClient configurations within the application. + * + *

+ * Example: {@code "exampleClient"} + *

+ */ private String name; + + /** + * The base URL address that the WebClient will connect to. This is typically + * the root URL of the service the WebClient will communicate with. + * + *

+ * Example: {@code "https://api.example.com"} + *

+ */ private String address; + + /** + * The username used for basic authentication. This is typically used in + * conjunction with the `password` field. + * + *

+ * Example: {@code "user"} + *

+ */ private String username; + + /** + * The password used for basic authentication. This is used alongside the + * `username` for securing the WebClient requests. + * + *

+ * Example: {@code "pass"} + *

+ */ private String password; + + /** + * The authorization token or credentials that the WebClient will use for + * securing API requests. This can be a bearer token, API key, etc. + * + *

+ * Example: {@code "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."} + *

+ */ private String authorization; + + /** + * Configuration properties for the WebClient's connection pool. This includes + * settings such as maximum connections, idle timeout, etc. + */ private PoolProperties pool = new PoolProperties(); + + /** + * Configuration properties for timeouts in WebClient operations. This includes + * settings such as connection timeout, read timeout, etc. + */ private TimeoutProperties timeout = new TimeoutProperties(); + + /** + * Configuration properties for retry policies. This includes settings for + * retrying failed requests, backoff strategies, etc. + */ private RetryProperties retry = new RetryProperties(); + + /** + * Configuration properties for logging requests and responses. This includes + * settings for enabling/disabling logging, log levels, etc. + */ private ClientLogProperties log = new ClientLogProperties(); + + /** + * Configuration properties for monitoring WebClient metrics. This includes + * settings for capturing and reporting WebClient performance metrics. + */ private MonitoringProperties monitoring = new MonitoringProperties(); + + /** + * Configuration properties for setting up a proxy. This includes settings for + * proxy host, port, and credentials. + */ private ProxyProperties proxy = new ProxyProperties(); + + /** + * Custom filter functions to be applied to the WebClient's request and response + * exchanges. These filters can modify requests and responses, implement custom + * logging, add headers, etc. + */ private List customFilters; + + /** + * Indicates whether the WebClient should use internal OAuth for authentication. + * When set to {@code true}, the WebClient will use an internal mechanism for + * obtaining OAuth tokens, rather than relying on external credentials. + */ private boolean internalOauth = false; } diff --git a/src/main/java/io/hoangtien2k3/commons/config/DatabaseConversion.java b/src/main/java/io/hoangtien2k3/commons/config/DatabaseConversion.java index 077b912..8d8f692 100644 --- a/src/main/java/io/hoangtien2k3/commons/config/DatabaseConversion.java +++ b/src/main/java/io/hoangtien2k3/commons/config/DatabaseConversion.java @@ -32,15 +32,63 @@ import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; +/** + * The {@code DatabaseConversion} class provides utility methods to configure + * and create a {@link MappingR2dbcConverter} with custom conversions for + * handling various data types between Java objects and database records. + *

+ * This class sets up custom converters for types such as {@link Instant}, + * {@link UUID}, {@link BitSet}, and others, ensuring proper conversion between + * database and Java types. + *

+ *

+ * The class also includes a set of nested enums that implement + * {@link Converter} interfaces to handle specific conversion logic for + * different data types. + *

+ *

+ * The custom converters handle conversions for types such as: + *

    + *
  • {@link Instant} to {@link LocalDateTime} and vice versa
  • + *
  • {@link UUID} to binary representation and vice versa
  • + *
  • {@link Blob} to {@link String}
  • + *
  • {@link Duration} to {@link Long} and vice versa
  • + *
  • {@link LocalDateTime} to {@link Date}
  • + *
  • {@link BitSet} to {@link Boolean}
  • + *
  • {@link ZonedDateTime} to {@link LocalDateTime} and vice versa
  • + *
+ *

+ * Usage example: + * + *

+ * DatabaseConversion conversion = new DatabaseConversion();
+ * MappingR2dbcConverter converter = conversion.getR2dbcConverter();
+ * 
+ */ @Slf4j @Component public class DatabaseConversion { + + /** + * Creates and returns a MappingR2dbcConverter configured with custom + * conversions and a mapping context. + * + * @return MappingR2dbcConverter instance for converting between Java objects + * and database records. + */ public MappingR2dbcConverter getR2dbcConverter() { R2dbcMappingContext mappingContext = getR2dbcMappingContext(); R2dbcCustomConversions r2dbcCustomConversions = getR2dbcCustomConversions(); return new MappingR2dbcConverter(mappingContext, r2dbcCustomConversions); } + /** + * Creates and configures a R2dbcMappingContext with a naming strategy and + * custom conversions. + * + * @return R2dbcMappingContext instance used to map entities to database tables + * and columns. + */ private R2dbcMappingContext getR2dbcMappingContext() { NamingStrategy namingStrategy = NamingStrategy.INSTANCE; R2dbcCustomConversions r2dbcCustomConversions = getR2dbcCustomConversions(); @@ -49,10 +97,22 @@ private R2dbcMappingContext getR2dbcMappingContext() { return context; } + /** + * Creates and returns an R2dbcCustomConversions instance configured with a list + * of custom converters. + * + * @return R2dbcCustomConversions instance for custom conversion logic. + */ private R2dbcCustomConversions getR2dbcCustomConversions() { return R2dbcCustomConversions.of(MySqlDialect.INSTANCE, getListConverters()); } + /** + * Provides a list of custom converters used for data conversion between Java + * types and database types. + * + * @return List of custom converters. + */ public List getListConverters() { List converters = new ArrayList<>(); converters.add(InstantWriteConverter.INSTANCE); @@ -69,28 +129,33 @@ public List getListConverters() { return converters; } + /** + * Converter to write Instant values to LocalDateTime in UTC. + */ @WritingConverter public enum InstantWriteConverter implements Converter { INSTANCE; - public LocalDateTime convert(Instant source) { + @Override + public LocalDateTime convert(@NotNull Instant source) { return LocalDateTime.ofInstant(source, ZoneOffset.UTC); } } + /** + * Converter to read Blob values as Strings. + */ @ReadingConverter public enum BlobToStringConverter implements Converter { INSTANCE; @Override - public String convert(Blob source) { + public String convert(@NotNull Blob source) { try { - return source == null - ? null - : Mono.from(source.stream()) - .map(bb -> StandardCharsets.UTF_8.decode(bb).toString()) - .toFuture() - .get(); + return Mono.from(source.stream()) + .map(bb -> StandardCharsets.UTF_8.decode(bb).toString()) + .toFuture() + .get(); } catch (Exception e) { log.error("Exception when read blob value", e); return null; @@ -98,6 +163,9 @@ public String convert(Blob source) { } } + /** + * Converter to write UUID values to byte arrays. + */ @WritingConverter public enum UUIDToBinaryConverter implements Converter { INSTANCE; @@ -111,6 +179,9 @@ public byte[] convert(UUID uuid) { } } + /** + * Converter to read byte arrays as UUID values. + */ @ReadingConverter public enum BinaryToUUIDConverter implements Converter { INSTANCE; @@ -124,6 +195,9 @@ public UUID convert(@NotNull byte[] source) { } } + /** + * Converter to read LocalDateTime values as Instant values in UTC. + */ @ReadingConverter public enum InstantReadConverter implements Converter { INSTANCE; @@ -134,6 +208,9 @@ public Instant convert(LocalDateTime localDateTime) { } } + /** + * Converter to read BitSet as Boolean values. + */ @ReadingConverter public enum BitSetReadConverter implements Converter { INSTANCE; @@ -144,17 +221,22 @@ public Boolean convert(BitSet bitSet) { } } + /** + * Converter to read LocalDateTime values as ZonedDateTime in UTC. + */ @ReadingConverter public enum ZonedDateTimeReadConverter implements Converter { INSTANCE; @Override public ZonedDateTime convert(@NotNull LocalDateTime localDateTime) { - // Be aware - we are using the UTC timezone return ZonedDateTime.of(localDateTime, ZoneOffset.UTC); } } + /** + * Converter to write ZonedDateTime values as LocalDateTime. + */ @WritingConverter public enum ZonedDateTimeWriteConverter implements Converter { INSTANCE; @@ -165,6 +247,9 @@ public LocalDateTime convert(ZonedDateTime zonedDateTime) { } } + /** + * Converter to write Duration values as Long values representing milliseconds. + */ @WritingConverter public enum DurationWriteConverter implements Converter { INSTANCE; @@ -175,18 +260,24 @@ public Long convert(Duration source) { } } + /** + * Converter to read LocalDateTime values as Date values in the system's default + * timezone. + */ @ReadingConverter public enum LocalDateTimeToDateReadConverter implements Converter { INSTANCE; @Override public Date convert(LocalDateTime localDateTime) { - // Be aware - we are using the UTC timezone Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant(); return Date.from(instant); } } + /** + * Converter to read Long values as Duration values representing milliseconds. + */ @ReadingConverter public enum DurationReadConverter implements Converter { INSTANCE; diff --git a/src/main/java/io/hoangtien2k3/commons/config/LocaleConfiguration.java b/src/main/java/io/hoangtien2k3/commons/config/LocaleConfiguration.java index a8ba65e..a772f4b 100644 --- a/src/main/java/io/hoangtien2k3/commons/config/LocaleConfiguration.java +++ b/src/main/java/io/hoangtien2k3/commons/config/LocaleConfiguration.java @@ -21,21 +21,62 @@ import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver; import org.springframework.web.server.i18n.LocaleContextResolver; +/** + * Configuration class for locale and message source settings in the + * application. + *

+ * This configuration class sets up the locale resolver and message source to + * handle internationalization (i18n) and localization in a Spring Boot + * application. + *

+ */ @Configuration public class LocaleConfiguration { + + /** + * Configures the {@link LocaleContextResolver} bean. + *

+ * This bean is responsible for resolving the locale based on the + * Accept-Language header in the HTTP request. + *

+ *

+ * In this configuration, the default locale is set to Vietnamese (vi). + *

+ * + * @return an instance of {@link LocaleContextResolver} configured with the + * default locale + */ @Bean("localeContextResolver2") public LocaleContextResolver localeContextResolver() { var resolver = new AcceptHeaderLocaleContextResolver(); - resolver.setDefaultLocale(Locale.forLanguageTag("vi")); + resolver.setDefaultLocale(Locale.forLanguageTag("vi")); // Set default locale to Vietnamese return resolver; } + /** + * Configures the {@link ReloadableResourceBundleMessageSource} bean. + *

+ * This bean is used to load and reload message bundles for internationalization + * purposes. + *

+ *

+ * The message source is configured to use UTF-8 encoding and loads messages + * from the classpath location "/i18n/messages". + *

+ *

+ * Messages can be retrieved using their codes, and the code itself is used as + * the default message if no translation is found. + *

+ * + * @return an instance of {@link ReloadableResourceBundleMessageSource} + * configured for loading messages + */ @Bean public ReloadableResourceBundleMessageSource messageSource() { ReloadableResourceBundleMessageSource rs = new ReloadableResourceBundleMessageSource(); - rs.setDefaultEncoding("UTF-8"); - rs.setBasenames("classpath:/i18n/messages"); - rs.setUseCodeAsDefaultMessage(true); + rs.setDefaultEncoding("UTF-8"); // Set encoding to UTF-8 + rs.setBasenames("classpath:/i18n/messages"); // Location of message bundles + rs.setUseCodeAsDefaultMessage(true); // Use message code as default message if no translation is found return rs; } } diff --git a/src/main/java/io/hoangtien2k3/commons/config/MinioConfiguration.java b/src/main/java/io/hoangtien2k3/commons/config/MinioConfiguration.java index c5383ca..1f61b8a 100644 --- a/src/main/java/io/hoangtien2k3/commons/config/MinioConfiguration.java +++ b/src/main/java/io/hoangtien2k3/commons/config/MinioConfiguration.java @@ -21,6 +21,18 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +/** + * Configuration class for setting up the Minio client. + *

+ * This class configures a {@link MinioClient} bean that is used to interact + * with the Minio object storage service. + *

+ *

+ * The Minio client is configured based on properties defined in + * {@link MinioProperties} and only created if the 'minio.enabled' property is + * set to 'true'. + *

+ */ @Slf4j @Configuration @RequiredArgsConstructor @@ -28,10 +40,26 @@ public class MinioConfiguration { private final MinioProperties minioProperties; + /** + * Configures the {@link MinioClient} bean. + *

+ * This bean is created only if the 'minio.enabled' property is set to 'true'. + * The Minio client is initialized with the endpoint URL, access key, and secret + * key from the {@link MinioProperties} class. + *

+ *

+ * The Minio client allows interaction with Minio object storage, including + * operations such as uploading, downloading, and managing objects in the + * storage service. + *

+ * + * @return an instance of {@link MinioClient} configured with properties from + * {@link MinioProperties} + */ @Bean @ConditionalOnProperty(value = "minio.enabled", havingValue = "true", matchIfMissing = false) public MinioClient minioClient() { - log.info("Configuring minio client: {}", minioProperties.getBaseUrl()); + log.info("Configuring Minio client: {}", minioProperties.getBaseUrl()); return MinioClient.builder() .endpoint(minioProperties.getBaseUrl()) .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey()) diff --git a/src/main/java/io/hoangtien2k3/commons/config/MinioProperties.java b/src/main/java/io/hoangtien2k3/commons/config/MinioProperties.java index 18adfdb..ce76a14 100644 --- a/src/main/java/io/hoangtien2k3/commons/config/MinioProperties.java +++ b/src/main/java/io/hoangtien2k3/commons/config/MinioProperties.java @@ -18,13 +18,71 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; +/** + * Configuration properties for Minio. + *

+ * This class binds properties from the application's configuration file (e.g., + * application.yml or application.properties) with the prefix 'minio'. It + * provides configuration values necessary for interacting with the Minio object + * storage service. + *

+ *

+ * Each property corresponds to a specific configuration parameter required for + * setting up the Minio client. + *

+ */ @Data @Component @ConfigurationProperties(prefix = "minio") public class MinioProperties { + + /** + * The base URL of the Minio service. + *

+ * This URL is used to connect to the Minio server for performing storage + * operations. + *

+ * Example: "http://localhost:9000" + */ private String baseUrl; + + /** + * The public URL for accessing objects stored in Minio. + *

+ * This URL can be used to access files directly from the Minio server if public + * access is enabled. + *

+ * Example: "http://localhost:9000/public" + */ private String publicUrl; + + /** + * The access key used for authentication with the Minio service. + *

+ * This key is used along with the secret key to authorize requests to the Minio + * server. + *

+ * Example: "minio-access-key" + */ private String accessKey; + + /** + * The secret key used for authentication with the Minio service. + *

+ * This key is used along with the access key to authorize requests to the Minio + * server. + *

+ * Example: "minio-secret-key" + */ private String secretKey; + + /** + * The default bucket name for storing objects in Minio. + *

+ * This bucket is used to organize and store objects. All objects will be stored + * in this bucket by default. + *

+ * Example: "my-bucket" + */ private String bucket; } diff --git a/src/main/java/io/hoangtien2k3/commons/config/TracingConfiguration.java b/src/main/java/io/hoangtien2k3/commons/config/TracingConfiguration.java index 22e7952..1b70307 100644 --- a/src/main/java/io/hoangtien2k3/commons/config/TracingConfiguration.java +++ b/src/main/java/io/hoangtien2k3/commons/config/TracingConfiguration.java @@ -20,13 +20,58 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +/** + * Configuration class for setting up tracing in the application. + *

+ * This configuration class provides beans for tracing using the Brave library. + * It is designed to set up tracing capabilities for distributed tracing and + * monitoring. + *

+ *

+ * The configuration defines two beans: + *

+ *
    + *
  • {@link Tracer}: The primary interface used for tracing operations.
  • + *
  • {@link Tracing}: The builder for creating a new tracing instance.
  • + *
+ */ @Configuration public class TracingConfiguration { + /** + * Creates a {@link Tracer} bean that is used for tracing operations. + *

+ * In this configuration, it returns a NOOP (no-operation) tracer. This is a + * placeholder tracer that does nothing. This can be useful when tracing is + * disabled or not properly configured. + *

+ *

+ * In a production setup, you would typically replace this with an actual + * implementation of a tracer. + *

+ * + * @param tracing + * the {@link Tracing} instance to be used for creating the tracer. + * @return a {@link Tracer} instance configured as a NOOP tracer. + */ @Bean public Tracer tracer(Tracing tracing) { return BraveTracer.NOOP; } + /** + * Creates a {@link Tracing} bean used for tracing operations. + *

+ * This method creates and configures a new {@link Tracing} instance using the + * builder pattern. + *

+ *

+ * The configuration here does not include any specific tracing settings or + * configurations, and defaults are used. You might need to customize this setup + * based on your tracing and monitoring needs. + *

+ * + * @return a configured {@link Tracing} instance. + */ @Bean public Tracing tracing() { return Tracing.newBuilder().build(); diff --git a/src/main/java/io/hoangtien2k3/commons/config/WhiteListProperties.java b/src/main/java/io/hoangtien2k3/commons/config/WhiteListProperties.java index f6baff4..179fa5e 100644 --- a/src/main/java/io/hoangtien2k3/commons/config/WhiteListProperties.java +++ b/src/main/java/io/hoangtien2k3/commons/config/WhiteListProperties.java @@ -20,9 +20,40 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; +/** + * Configuration properties class for defining and managing a whitelist of URIs + * and HTTP methods. + *

+ * This class maps properties from the application's configuration file (e.g., + * application.yml or application.properties) to Java objects for easier access + * and management. + *

+ *

+ * The properties are prefixed with "application", as defined in the + * {@link ConfigurationProperties} annotation. + *

+ *

+ * The main purpose of this class is to provide a way to configure and access a + * list of allowed URIs and HTTP methods that are considered safe and do not + * require authentication. + *

+ */ @Data @Component @ConfigurationProperties(prefix = "application") public class WhiteListProperties { + + /** + * List of {@link WhiteList} objects representing the whitelisted URIs and HTTP + * methods. + *

+ * This property contains a list of whitelist configurations. Each entry in the + * list specifies a URI and the HTTP methods that are allowed for that URI. This + * can be used to configure which endpoints in the application should be + * accessible without authentication or other security measures. + *

+ * + * @see WhiteList + */ private List whiteList; } diff --git a/src/main/java/io/hoangtien2k3/commons/config/exception/ExceptionResponseConfig.java b/src/main/java/io/hoangtien2k3/commons/config/exception/ExceptionResponseConfig.java index 6bf3956..bc316aa 100644 --- a/src/main/java/io/hoangtien2k3/commons/config/exception/ExceptionResponseConfig.java +++ b/src/main/java/io/hoangtien2k3/commons/config/exception/ExceptionResponseConfig.java @@ -38,12 +38,60 @@ import org.springframework.web.server.ServerWebInputException; import reactor.core.publisher.Mono; +/** + * Centralized exception handling configuration for the application. This class + * provides global exception handling for various types of exceptions and + * formats them into a consistent error response format. + * + *

+ * The `ExceptionResponseConfig` class utilizes Spring's `@RestControllerAdvice` + * to handle exceptions thrown by controllers and provides appropriate HTTP + * responses with trace information for debugging purposes. Each exception + * handler method logs the error with a trace ID and returns a + * `TraceErrorResponse` object with relevant error information. + *

+ * + *

Exception Handlers:

+ *
    + *
  • RuntimeException - Handles general runtime exceptions. Returns a + * 500 Internal Server Error status with a trace ID for debugging.
  • + *
  • R2dbcException - Handles exceptions specific to R2DBC (Reactive + * Relational Database Connectivity). Returns a 500 Internal Server Error status + * with a trace ID.
  • + *
  • AccessDeniedException - Handles access denial exceptions, + * indicating insufficient permissions. Returns a 403 Forbidden status with a + * trace ID.
  • + *
  • DataBufferLimitException - Handles exceptions when the data buffer + * limit is exceeded. Returns a 400 Bad Request status with a trace ID and a + * localized error message.
  • + *
  • ServerWebInputException - Handles exceptions related to invalid + * request input formats. Returns a 400 Bad Request status with a trace ID and + * the reason for the error.
  • + *
  • WebExchangeBindException - Handles exceptions related to binding + * errors in request inputs. Returns a 400 Bad Request status with a trace ID + * and a list of error messages. Special handling is provided for property + * conversion errors.
  • + *
  • BusinessException - Handles business logic exceptions. Returns + * appropriate HTTP status based on the error code and a trace ID. Supports + * custom error codes like NOT_FOUND and NO_PERMISSION.
  • + *
+ */ @Slf4j @RequiredArgsConstructor @RestControllerAdvice public class ExceptionResponseConfig { private final Tracer tracer; + /** + * Handles runtime exceptions. Logs the exception with a trace ID and returns a + * 500 Internal Server Error status with a trace error response. + * + * @param ex + * the runtime exception + * @param serverWebExchange + * the current server web exchange + * @return a Mono containing the ResponseEntity with TraceErrorResponse + */ @ExceptionHandler(RuntimeException.class) public Mono>> runtimeException( RuntimeException ex, ServerWebExchange serverWebExchange) { @@ -54,6 +102,16 @@ public Mono>> runtimeException( HttpStatus.INTERNAL_SERVER_ERROR)); } + /** + * Handles R2DBC exceptions. Logs the exception with a trace ID and returns a + * 500 Internal Server Error status with a trace error response. + * + * @param ex + * the R2DBC exception + * @param serverWebExchange + * the current server web exchange + * @return a Mono containing the ResponseEntity with TraceErrorResponse + */ @ExceptionHandler(R2dbcException.class) public Mono>> r2dbcException( R2dbcException ex, ServerWebExchange serverWebExchange) { @@ -64,6 +122,16 @@ public Mono>> r2dbcException( HttpStatus.INTERNAL_SERVER_ERROR)); } + /** + * Handles access denied exceptions. Logs the exception with a trace ID and + * returns a 403 Forbidden status with a trace error response. + * + * @param ex + * the access denied exception + * @param serverWebExchange + * the current server web exchange + * @return a Mono containing the ResponseEntity with TraceErrorResponse + */ @ExceptionHandler(AccessDeniedException.class) public Mono>> accessDeniedException( AccessDeniedException ex, ServerWebExchange serverWebExchange) { @@ -74,6 +142,14 @@ public Mono>> accessDeniedException( HttpStatus.FORBIDDEN)); } + /** + * Handles data buffer limit exceptions. Logs the exception with a trace ID and + * returns a 400 Bad Request status with a trace error response. + * + * @param ex + * the data buffer limit exception + * @return a Mono containing the ResponseEntity with TraceErrorResponse + */ @ExceptionHandler(DataBufferLimitException.class) public Mono>> dataBufferLimitException(DataBufferLimitException ex) { String traceId = Objects.requireNonNull(tracer.currentSpan()).context().traceId(); @@ -84,6 +160,17 @@ public Mono>> dataBufferLimitException HttpStatus.BAD_REQUEST)); } + /** + * Handles server input exceptions, such as invalid request format. Logs the + * exception with a trace ID and returns a 400 Bad Request status with a trace + * error response. + * + * @param ex + * the server input exception + * @param serverWebExchange + * the current server web exchange + * @return a Mono containing the ResponseEntity with TraceErrorResponse + */ @ExceptionHandler(ServerWebInputException.class) public Mono>> serverInputException( ServerWebInputException ex, ServerWebExchange serverWebExchange) { @@ -94,6 +181,17 @@ public Mono>> serverInputException( HttpStatus.BAD_REQUEST)); } + /** + * Handles binding errors in request inputs. Logs the exception with a trace ID + * and returns a 400 Bad Request status with a trace error response containing + * error messages. + * + * @param ex + * the WebExchangeBindException + * @param serverWebExchange + * the current server web exchange + * @return a Mono containing the ResponseEntity with TraceErrorResponse + */ @ExceptionHandler(WebExchangeBindException.class) public Mono>> serverInputException( WebExchangeBindException ex, ServerWebExchange serverWebExchange) { @@ -118,6 +216,18 @@ public Mono>> serverInputException( HttpStatus.BAD_REQUEST)); } + /** + * Handles business logic exceptions. Logs the exception with a trace ID and + * returns an appropriate HTTP status based on the error code. Defaults to 400 + * Bad Request but can return other statuses like 404 Not Found or 403 Forbidden + * based on specific error codes. + * + * @param ex + * the business exception + * @param serverWebExchange + * the current server web exchange + * @return a Mono containing the ResponseEntity with TraceErrorResponse + */ @ExceptionHandler(BusinessException.class) public Mono>> businessException( BusinessException ex, ServerWebExchange serverWebExchange) { diff --git a/src/main/java/io/hoangtien2k3/commons/config/security/WebSecurityCorsFilter.java b/src/main/java/io/hoangtien2k3/commons/config/security/WebSecurityCorsFilter.java index 3944ae8..e601fe4 100644 --- a/src/main/java/io/hoangtien2k3/commons/config/security/WebSecurityCorsFilter.java +++ b/src/main/java/io/hoangtien2k3/commons/config/security/WebSecurityCorsFilter.java @@ -19,17 +19,36 @@ import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.config.WebFluxConfigurer; +/** + * Configuration class to set up Cross-Origin Resource Sharing (CORS) for the + * WebFlux application. + *

+ * This class implements {@link WebFluxConfigurer} to customize CORS mappings + * globally for the application. + *

+ */ @Configuration @EnableWebFlux public class WebSecurityCorsFilter implements WebFluxConfigurer { + /** + * Configures CORS settings for the application. + *

+ * This method sets global CORS configuration to allow requests from any origin, + * supports all HTTP methods, and allows all headers. Additionally, it sets the + * maximum age of the preflight response cache to 3600 seconds (1 hour). + *

+ * + * @param corsRegistry + * the {@link CorsRegistry} to customize CORS mappings + */ @Override public void addCorsMappings(CorsRegistry corsRegistry) { corsRegistry - .addMapping("/**") - .allowedOrigins("*") - .allowedMethods("*") - .allowedHeaders("*") - .maxAge(3600); + .addMapping("/**") // Allow CORS requests to all endpoints + .allowedOrigins("*") // Allow requests from any origin + .allowedMethods("*") // Allow all HTTP methods (GET, POST, PUT, DELETE, etc.) + .allowedHeaders("*") // Allow all headers in requests + .maxAge(3600); // Cache preflight responses for 3600 seconds (1 hour) } } diff --git a/src/main/java/io/hoangtien2k3/commons/config/security/http/WebConfig.java b/src/main/java/io/hoangtien2k3/commons/config/security/http/WebConfig.java index 97ddb6c..f52fb52 100644 --- a/src/main/java/io/hoangtien2k3/commons/config/security/http/WebConfig.java +++ b/src/main/java/io/hoangtien2k3/commons/config/security/http/WebConfig.java @@ -36,6 +36,13 @@ import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; import reactor.core.publisher.Mono; +/** + * Configuration class for setting up security and CORS (Cross-Origin Resource + * Sharing) policies in a Spring WebFlux application. This class configures how + * security is handled across the application, including whitelisting certain + * URIs and methods, setting up CORS configurations, and configuring OAuth2 + * resource server support. + */ @Slf4j @RequiredArgsConstructor @Configuration @@ -45,6 +52,25 @@ public class WebConfig { private final WhiteListProperties whiteListProperties; + /** + * Configures the security filter chain for the application. + * + *

+ * Sets up various security settings including CORS, authorization rules, and + * OAuth2 resource server configurations. The method reads whitelist + * configurations and applies them to permit access to certain paths and methods + * without authentication. + *

+ * + * @param http + * the {@link ServerHttpSecurity} instance used to configure the + * security settings + * @param jwtAuthenticationConverter + * the {@link Converter} to convert JWT tokens to authentication + * tokens + * @return a {@link SecurityWebFilterChain} configured with the defined security + * settings + */ @Bean public SecurityWebFilterChain springSecurityFilterChain( ServerHttpSecurity http, Converter> jwtAuthenticationConverter) { @@ -92,6 +118,17 @@ public SecurityWebFilterChain springSecurityFilterChain( // return configurationSource; // } + /** + * Provides CORS configuration for the application. + * + *

+ * Sets up CORS to allow requests from any origin with any HTTP method, and + * permits all headers. CORS settings apply to all paths in the application. + *

+ * + * @return a {@link CorsConfigurationSource} instance with the defined CORS + * settings + */ private CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOriginPatterns(List.of("*")); diff --git a/src/main/java/io/hoangtien2k3/commons/config/security/keycloak/KeycloakConfiguration.java b/src/main/java/io/hoangtien2k3/commons/config/security/keycloak/KeycloakConfiguration.java index f881a0d..e0cc3c6 100644 --- a/src/main/java/io/hoangtien2k3/commons/config/security/keycloak/KeycloakConfiguration.java +++ b/src/main/java/io/hoangtien2k3/commons/config/security/keycloak/KeycloakConfiguration.java @@ -24,15 +24,55 @@ import org.springframework.security.oauth2.jwt.Jwt; import reactor.core.publisher.Mono; +/** + * Configuration class for setting up Keycloak-related beans in the Spring + * Security configuration. + *

+ * This configuration class defines beans to convert JWT tokens from Keycloak + * into Spring Security authentication tokens and authorities. + *

+ */ @Configuration public class KeycloakConfiguration { + /** + * Provides a {@link Converter} to convert a {@link Jwt} to a {@link Collection} + * of {@link GrantedAuthority}. + *

+ * This bean is responsible for extracting and converting roles or authorities + * from the JWT token issued by Keycloak. The + * {@link KeycloakGrantedAuthoritiesConverter} implementation will use the + * client ID to correctly parse and map the JWT claims to Spring Security + * authorities. + *

+ * + * @param clientId + * the client ID used to configure the authority converter + * @return a {@link Converter} that transforms a {@link Jwt} into a + * {@link Collection} of {@link GrantedAuthority} + */ @Bean Converter> keycloakGrantedAuthoritiesConverter( @Value("${spring.security.oauth2.keycloak.client-id}") String clientId) { return new KeycloakGrantedAuthoritiesConverter(clientId); } + /** + * Provides a {@link Converter} to convert a {@link Jwt} into a {@link Mono} of + * {@link AbstractAuthenticationToken}. + *

+ * This bean sets up a converter to transform JWT tokens into reactive + * authentication tokens, integrating with Keycloak for OAuth2 authentication. + * The {@link ReactiveKeycloakJwtAuthenticationConverter} uses the provided + * {@link Converter} for authorities to complete the authentication process. + *

+ * + * @param converter + * a {@link Converter} that transforms a {@link Jwt} into a + * {@link Collection} of {@link GrantedAuthority} + * @return a {@link Converter} that converts a {@link Jwt} into a {@link Mono} + * of {@link AbstractAuthenticationToken} + */ @Bean Converter> keycloakJwtAuthenticationConverter( Converter> converter) { diff --git a/src/main/java/io/hoangtien2k3/commons/config/security/keycloak/KeycloakGrantedAuthoritiesConverter.java b/src/main/java/io/hoangtien2k3/commons/config/security/keycloak/KeycloakGrantedAuthoritiesConverter.java index 553bac5..15063ec 100644 --- a/src/main/java/io/hoangtien2k3/commons/config/security/keycloak/KeycloakGrantedAuthoritiesConverter.java +++ b/src/main/java/io/hoangtien2k3/commons/config/security/keycloak/KeycloakGrantedAuthoritiesConverter.java @@ -31,6 +31,15 @@ import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.util.ObjectUtils; +/** + * A {@link Converter} implementation that converts a {@link Jwt} token from + * Keycloak into a {@link Collection} of {@link GrantedAuthority}. + *

+ * This converter handles the extraction and transformation of roles from the + * JWT token issued by Keycloak. It combines realm roles and client-specific + * roles into a single set of granted authorities. + *

+ */ @RequiredArgsConstructor public class KeycloakGrantedAuthoritiesConverter implements Converter> { private static final String ROLES = "roles"; @@ -42,6 +51,20 @@ public class KeycloakGrantedAuthoritiesConverter implements Converter + * This method combines realm roles and client-specific roles extracted from the + * JWT token. It also includes any default granted authorities provided by the + * {@link JwtGrantedAuthoritiesConverter}. + *

+ * + * @param jwt + * the {@link Jwt} token to convert + * @return a {@link Collection} of {@link GrantedAuthority} derived from the JWT + * token + */ @Override public Collection convert(Jwt jwt) { var realmRoles = realmRoles(jwt); @@ -55,10 +78,32 @@ public Collection convert(Jwt jwt) { return authorities; } + /** + * Extracts default granted authorities from the provided {@link Jwt} token + * using the {@link JwtGrantedAuthoritiesConverter}. + *

+ * If the default converter does not provide any authorities, an empty set is + * returned. + *

+ * + * @param jwt + * the {@link Jwt} token to extract default authorities from + * @return a {@link Collection} of default {@link GrantedAuthority} + */ private Collection defaultGrantedAuthorities(Jwt jwt) { return Optional.ofNullable(defaultAuthoritiesConverter.convert(jwt)).orElse(emptySet()); } + /** + * Extracts realm roles from the provided {@link Jwt} token. + *

+ * Realm roles are found in the "realm_access" claim of the JWT token. + *

+ * + * @param jwt + * the {@link Jwt} token to extract realm roles from + * @return a {@link List} of realm role names + */ @SuppressWarnings("unchecked") protected List realmRoles(Jwt jwt) { return Optional.ofNullable(jwt.getClaimAsMap(CLAIM_REALM_ACCESS)) @@ -66,6 +111,20 @@ protected List realmRoles(Jwt jwt) { .orElse(emptyList()); } + /** + * Extracts client-specific roles from the provided {@link Jwt} token for a + * specific client. + *

+ * Client-specific roles are found in the "resource_access" claim of the JWT + * token under the specified client ID. + *

+ * + * @param jwt + * the {@link Jwt} token to extract client roles from + * @param clientId + * the client ID to find roles for + * @return a {@link List} of client-specific role names + */ @SuppressWarnings("unchecked") protected List clientRoles(Jwt jwt, String clientId) { if (ObjectUtils.isEmpty(clientId)) { diff --git a/src/main/java/io/hoangtien2k3/commons/constants/CommonErrorCode.java b/src/main/java/io/hoangtien2k3/commons/constants/CommonErrorCode.java index 8c19e65..df0c4c5 100644 --- a/src/main/java/io/hoangtien2k3/commons/constants/CommonErrorCode.java +++ b/src/main/java/io/hoangtien2k3/commons/constants/CommonErrorCode.java @@ -14,25 +14,90 @@ */ package io.hoangtien2k3.commons.constants; +/** + * The `CommonErrorCode` class contains common error codes used throughout the + * application. These error codes are defined as constant strings for easy + * management and usage across the application. + */ public class CommonErrorCode { + + /** + * Error code for invalid requests. + */ public static final String BAD_REQUEST = "bad_request"; + + /** + * Error code when a resource is not found. + */ public static final String NOT_FOUND = "not_found"; + + /** + * Error code for invalid parameters. + */ public static final String INVALID_PARAMS = "invalid_params"; + + /** + * Error code when data already exists. + */ public static final String EXIST_DATA = "exist_data"; + + /** + * Error code when data does not exist. + */ public static final String NOT_EXIST_DATA = "not_exist_data"; + + /** + * Error code for internal server errors. + */ public static final String INTERNAL_SERVER_ERROR = "internal_error"; + + /** + * Error code when the user is not authorized. + */ public static final String UN_AUTHORIZATION = "un_auth"; + + /** + * Error code when the user does not have permission. + */ public static final String NO_PERMISSION = "no_permission"; + + /** + * Error code when access is denied. + */ public static final String ACCESS_DENIED = "access_denied"; + + /** + * Error code when there is an error parsing the token. + */ public static final String PARSE_TOKEN_ERROR = "parse_token_failed"; + + /** + * Error code for SQL-related errors. + */ public static final String SQL_ERROR = "sql"; - public static final String TRUST_MST_01 = "trust_mst_01"; - public static final String TRUST_MST_02 = "trust_mst_02"; - public static final String COMPANY_NOT_FOUND_TRUST_INDENTITY = "company_not_found_trust_indentity"; + + /** + * Error code when group information is not found. + */ public static final String GROUP_INFO_NOT_FOUND = "group_info_not_found"; - public static final String GROUP_INFO_NOT_FOUND_2 = "group_info_not_found_2"; + + /** + * Error code indicating success. + */ public static final String SUCCESS = "success"; + + /** + * Error code when deserialization fails. + */ public static final String UN_DESERIALIZE = "un_deserialize"; + + /** + * Error code when there is a fault in password hashing. + */ public static final String HASHING_PASSWORD_FAULT = "hashing_password_fault"; + + /** + * Error code when an exception occurs during a SOAP call. + */ public static final String CALL_SOAP_ERROR = "Exception when call soap: "; } diff --git a/src/main/java/io/hoangtien2k3/commons/constants/Role.java b/src/main/java/io/hoangtien2k3/commons/constants/Role.java index c071dec..dc0c831 100644 --- a/src/main/java/io/hoangtien2k3/commons/constants/Role.java +++ b/src/main/java/io/hoangtien2k3/commons/constants/Role.java @@ -14,7 +14,33 @@ */ package io.hoangtien2k3.commons.constants; +/** + * Enumeration representing different user roles in the application. + *

+ * This enum is used to define and manage various user roles and their + * associated permissions. + *

+ *

+ * Each constant in this enum corresponds to a specific role that a user can + * have within the system. + *

+ */ public enum Role { + /** + * Represents an administrative role with the highest level of permissions. + *

+ * Users with this role typically have full access to all system resources and + * management capabilities. + *

+ */ ROLE_admin, + + /** + * Represents a standard user role with limited permissions. + *

+ * Users with this role typically have access to basic functionalities and + * resources, but not administrative controls. + *

+ */ ROLE_user } diff --git a/src/main/java/io/hoangtien2k3/commons/exception/UnRetryableException.java b/src/main/java/io/hoangtien2k3/commons/exception/UnRetryableException.java index 06261c4..82f9768 100644 --- a/src/main/java/io/hoangtien2k3/commons/exception/UnRetryableException.java +++ b/src/main/java/io/hoangtien2k3/commons/exception/UnRetryableException.java @@ -18,11 +18,32 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +/** + * Exception class representing an error that cannot be retried. + *

+ * This exception extends {@link BusinessException} and is used to signal errors + * that are not eligible for retrying, indicating that the error condition is + * permanent and requires manual intervention or resolution. + *

+ *

+ * The exception carries an error code and a message to provide details about + * the error. + *

+ */ @EqualsAndHashCode(callSuper = true) @Data @NoArgsConstructor public class UnRetryableException extends BusinessException { + /** + * Constructs a new {@code UnRetryableException} with the specified error code + * and message. + * + * @param errorCode + * the error code associated with the exception. + * @param message + * the detailed message explaining the cause of the exception. + */ public UnRetryableException(String errorCode, String message) { super(errorCode, message); } diff --git a/src/main/java/io/hoangtien2k3/commons/factory/ObjectMapperFactory.java b/src/main/java/io/hoangtien2k3/commons/factory/ObjectMapperFactory.java index af02881..a349437 100644 --- a/src/main/java/io/hoangtien2k3/commons/factory/ObjectMapperFactory.java +++ b/src/main/java/io/hoangtien2k3/commons/factory/ObjectMapperFactory.java @@ -24,11 +24,151 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.io.IOException; +/** + * Factory class for creating and configuring {@link ObjectMapper} instances. + *

+ * The {@code ObjectMapperFactory} class provides methods to obtain different + * configurations of Jackson's {@link ObjectMapper}. It includes configurations + * for handling JSON deserialization, serialization, and custom deserializers. + *

+ * + *

Class Overview:

+ *

+ * This class provides static methods to retrieve {@link ObjectMapper} instances + * with different configurations: - getInstance: Provides a + * singleton instance with custom configurations. - getInstance2: + * Provides a second instance with a different configuration. - + * defaultGetInstance: Provides a default instance with standard + * configurations. + *

+ * + *

Methods:

+ *
    + *
  • getInstance: Provides a singleton {@link ObjectMapper} + * instance with custom configurations. + *
      + *
    • Returns: + *

      + * A singleton instance of {@link ObjectMapper} configured with custom settings + * such as ignoring unknown properties, accepting single values as arrays, + * unwrapping single value arrays, and registering custom deserializers. + *

      + *
    • + *
    • Configuration: + *
        + *
      • DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES: + * false
      • + *
      • DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY: + * true
      • + *
      • DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS: true
      • + *
      • Custom deserializer for boolean and Boolean + * types.
      • + *
      • Registers JavaTimeModule and custom + * SimpleModule.
      • + *
      + *
    • + *
    + *
  • + *
  • getInstance2: Provides a second {@link ObjectMapper} + * instance with alternative configurations. + *
      + *
    • Returns: + *

      + * A new instance of {@link ObjectMapper} configured to ignore unknown + * properties and accept single values as arrays. + *

      + *
    • + *
    • Configuration: + *
        + *
      • DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES: + * false
      • + *
      • DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY: + * true
      • + *
      + *
    • + *
    + *
  • + *
  • defaultGetInstance: Provides a default + * {@link ObjectMapper} instance with standard configurations. + *
      + *
    • Returns: + *

      + * A new instance of {@link ObjectMapper} configured to ignore unknown + * properties, accept single values as arrays, include non-null values only, and + * register available modules. + *

      + *
    • + *
    • Configuration: + *
        + *
      • DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES: + * false
      • + *
      • DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY: + * true
      • + *
      • SerializationInclusion: NON_NULL
      • + *
      • Finds and registers all available modules.
      • + *
      + *
    • + *
    + *
  • + *
+ * + *

Private Inner Classes:

+ *
    + *
  • NumericBooleanDeserializer: Custom deserializer for + * handling numeric and boolean values. + *
      + *
    • deserialize: Converts numeric or textual representations + * of boolean values ("1", "true", "0", "false") to Boolean. + *
        + *
      • Parameters: + *
          + *
        • p (JsonParser): The parser to read JSON + * content.
        • + *
        • ctxt (DeserializationContext): The context for + * deserialization.
        • + *
        + *
      • + *
      • Returns: + *

        + * A Boolean value based on the input JSON text. + *

        + *
      • + *
      + *
    • + *
    + *
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * ObjectMapper mapper = ObjectMapperFactory.getInstance();
+ * MyClass obj = mapper.readValue(jsonString, MyClass.class);
+ * }
+ * + *

Notes:

+ *

+ * This factory class is designed to provide different configurations for + * Jackson's {@link ObjectMapper} based on the needs of different use cases. + * Custom deserializers and module registrations are handled within the factory + * to ensure consistent configuration across various parts of the application. + *

+ */ public class ObjectMapperFactory { private static ObjectMapper objectMapper; private static ObjectMapper objectMapperV2 = new ObjectMapper(); private static ObjectMapper defaultGetInstance = new ObjectMapper(); + /** + * Provides a singleton {@link ObjectMapper} instance with custom + * configurations. + *

+ * Configures the instance to ignore unknown properties, accept single values as + * arrays, unwrap single value arrays, and register custom deserializers. + *

+ * + * @return A singleton instance of {@link ObjectMapper} with custom settings. + */ public static ObjectMapper getInstance() { if (objectMapper == null) { objectMapper = new ObjectMapper(); @@ -44,12 +184,32 @@ public static ObjectMapper getInstance() { return objectMapper; } + /** + * Provides a second {@link ObjectMapper} instance with alternative + * configurations. + *

+ * Configures the instance to ignore unknown properties and accept single values + * as arrays. + *

+ * + * @return A new instance of {@link ObjectMapper} with alternative settings. + */ public static ObjectMapper getInstance2() { objectMapperV2.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapperV2.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); return objectMapperV2; } + /** + * Provides a default {@link ObjectMapper} instance with standard + * configurations. + *

+ * Configures the instance to ignore unknown properties, accept single values as + * arrays, include non-null values only, and register all available modules. + *

+ * + * @return A new instance of {@link ObjectMapper} with standard settings. + */ public static ObjectMapper defaultGetInstance() { defaultGetInstance.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); defaultGetInstance.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); @@ -58,6 +218,9 @@ public static ObjectMapper defaultGetInstance() { return defaultGetInstance; } + /** + * Custom deserializer for handling numeric and boolean values. + */ private static class NumericBooleanDeserializer extends JsonDeserializer { @Override public Boolean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { diff --git a/src/main/java/io/hoangtien2k3/commons/factory/UnmarshallerFactory.java b/src/main/java/io/hoangtien2k3/commons/factory/UnmarshallerFactory.java index 939917e..eb764b6 100644 --- a/src/main/java/io/hoangtien2k3/commons/factory/UnmarshallerFactory.java +++ b/src/main/java/io/hoangtien2k3/commons/factory/UnmarshallerFactory.java @@ -21,11 +21,89 @@ import javax.xml.bind.Unmarshaller; import lombok.extern.slf4j.Slf4j; +/** + * Factory class for creating and caching {@link Unmarshaller} instances. + *

+ * The {@code UnmarshallerFactory} class provides a method to obtain a singleton + * {@link Unmarshaller} instance for a given class. It utilizes a cache to + * ensure that each {@link Unmarshaller} is created only once per class, + * improving performance and reducing the overhead of creating multiple + * instances. + *

+ * + *

Class Overview:

+ *

+ * This class provides a static method to retrieve {@link Unmarshaller} + * instances, which are cached for efficiency. + *

+ * + *

Methods:

+ *
    + *
  • getInstance: Retrieves or creates an + * {@link Unmarshaller} instance for the specified class. + *
      + *
    • Parameters: + *
        + *
      • clz (Class): The class for which the + * {@link Unmarshaller} is to be created or retrieved.
      • + *
      + *
    • + *
    • Returns: + *

      + * An {@link Unmarshaller} instance for the specified class, or + * null if an error occurs during creation. + *

      + *
    • + *
    • Exceptions: + *
        + *
      • JAXBException: Thrown if an error occurs during the creation + * of the {@link Unmarshaller}.
      • + *
      + *
    • + *
    + *
  • + *
+ * + *

Private Fields:

+ *
    + *
  • instance: A Map that + * caches {@link Unmarshaller} instances.
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * Unmarshaller unmarshaller = UnmarshallerFactory.getInstance(MyClass.class);
+ * MyClass myObject = (MyClass) unmarshaller.unmarshal(new StringReader(xmlData));
+ * }
+ * + *

Notes:

+ *

+ * This factory class uses a cache to ensure that {@link Unmarshaller} instances + * are only created once per class, thus optimizing the performance of XML + * unmarshalling operations. + *

+ */ @Slf4j public class UnmarshallerFactory { private static Map instance = new HashMap<>(); + /** + * Retrieves or creates an {@link Unmarshaller} instance for the specified + * class. + *

+ * If an {@link Unmarshaller} for the given class is already cached, it is + * returned. Otherwise, a new {@link Unmarshaller} is created and cached for + * future use. + *

+ * + * @param clz + * The class for which the {@link Unmarshaller} is to be created or + * retrieved. + * @return An {@link Unmarshaller} instance for the specified class, or + * null if an error occurs. + */ public static Unmarshaller getInstance(Class clz) { Unmarshaller obj = instance.get(clz); if (obj != null) return obj; diff --git a/src/main/java/io/hoangtien2k3/commons/filter/http/GatewayContextFilter.java b/src/main/java/io/hoangtien2k3/commons/filter/http/GatewayContextFilter.java index 5a2a139..0a02b72 100644 --- a/src/main/java/io/hoangtien2k3/commons/filter/http/GatewayContextFilter.java +++ b/src/main/java/io/hoangtien2k3/commons/filter/http/GatewayContextFilter.java @@ -46,19 +46,80 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +/** + * A filter that captures and processes HTTP request and response data for + * logging purposes in a Spring WebFlux gateway application. + *

+ * This filter intercepts incoming requests and outgoing responses to record + * their headers and content, if logging is enabled for them. The context is + * stored in the {@link GatewayContext}, which can be accessed later in the + * request handling pipeline. + *

+ * + *

+ * Note: This filter is only active in non-production profiles + * (i.e., when the profile is not "prod"). + *

+ * + *

+ * Logging and context management: + *

+ *
    + *
  • Logs request and response data based on configurations in + * {@link HttpLogProperties}.
  • + *
  • Stores the captured data in a {@link GatewayContext} object, which is + * saved in the {@link ServerWebExchange} attributes.
  • + *
  • Supports logging of JSON and form URL-encoded content types.
  • + *
+ * + *

+ * The filter is executed with the highest precedence in the filter chain, + * ensuring that it captures all relevant data before other filters process the + * request. + *

+ * + * @author hoangtien2k3 + * @see WebFilter + * @see Ordered + * @see GatewayContext + */ @Component @Log4j2 @AllArgsConstructor @Profile("!prod") public class GatewayContextFilter implements WebFilter, Ordered { - private HttpLogProperties httpLogProperties; - private CodecConfigurer codecConfigurer; + /** + * Properties for configuring HTTP request and response logging. + */ + private final HttpLogProperties httpLogProperties; + + /** + * Configurer for customizing HTTP message readers and writers. + */ + private final CodecConfigurer codecConfigurer; + /** + * Specifies the order in which this filter is applied. This filter has the + * highest precedence. + * + * @return the highest precedence value, indicating that this filter is applied + * first. + */ @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } + /** + * Filters the incoming HTTP request and outgoing response, capturing data for + * logging. + * + * @param exchange + * the current server exchange + * @param chain + * the web filter chain + * @return {@link Mono} signaling when request processing is complete + */ @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); @@ -98,11 +159,23 @@ public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { } /** - * ReadFormData + * Reads form data from the incoming HTTP request, processes it, and prepares + * the data for further filtering in the chain. + *

+ * This method handles form data (typically from an HTML form) in the request + * body. It captures the form data, encodes it properly, and rewrites the + * request body with the processed form data before passing the request along + * the filter chain. + *

* * @param exchange + * the current server exchange containing the request and response * @param chain - * @return + * the web filter chain + * @param gatewayContext + * the context object that stores request and response data for + * logging + * @return a {@link Mono} signaling when request processing is complete */ private Mono readFormData(ServerWebExchange exchange, WebFilterChain chain, GatewayContext gatewayContext) { HttpHeaders headers = exchange.getRequest().getHeaders(); @@ -200,11 +273,23 @@ public Flux getBody() { } /** - * ReadJsonBody + * Reads JSON data from the incoming HTTP request body and prepares it for + * further filtering in the chain. + *

+ * This method handles JSON data in the request body by capturing and storing it + * in the {@link GatewayContext}. It then decorates the request to allow the + * request body to be read multiple times before passing the request along the + * filter chain. + *

* * @param exchange + * the current server exchange containing the request and response * @param chain - * @return + * the web filter chain + * @param gatewayContext + * the context object that stores request and response data for + * logging + * @return a {@link Mono} signaling when request processing is complete */ private Mono readBody(ServerWebExchange exchange, WebFilterChain chain, GatewayContext gatewayContext) { return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> { diff --git a/src/main/java/io/hoangtien2k3/commons/filter/http/PerformanceLogFilter.java b/src/main/java/io/hoangtien2k3/commons/filter/http/PerformanceLogFilter.java index 8f4da64..a3b5ec3 100644 --- a/src/main/java/io/hoangtien2k3/commons/filter/http/PerformanceLogFilter.java +++ b/src/main/java/io/hoangtien2k3/commons/filter/http/PerformanceLogFilter.java @@ -21,6 +21,7 @@ import io.hoangtien2k3.commons.model.GatewayContext; import io.hoangtien2k3.commons.utils.DataUtil; import io.hoangtien2k3.commons.utils.RequestUtils; +import io.hoangtien2k3.commons.utils.TruncateUtils; import java.net.URI; import java.util.ArrayList; import java.util.List; @@ -44,6 +45,59 @@ import reactor.core.publisher.Mono; import reactor.util.context.Context; +/** + * A WebFlux filter that logs performance metrics and request/response data for + * HTTP requests. This filter is applied to all incoming requests, except those + * targeting actuator endpoints. It measures the time taken to process requests, + * captures tracing information using Sleuth, and conditionally logs request and + * response data based on the application's active profile. + *

+ * The performance data is logged using a dedicated logger (`perfLogger`), and + * request/response data is logged using a separate logger (`reqResLogger`). + * + *

Key Features:

+ *
    + *
  • Logs the duration of HTTP request processing.
  • + *
  • Integrates with Spring Cloud Sleuth for distributed tracing.
  • + *
  • Logs request and response bodies in non-production environments.
  • + *
  • Excludes actuator endpoints from logging.
  • + *
+ * + *

Usage Example:

+ * + *
+ * {
+ * 	@code
+ * 	// Application configuration
+ * 	@SpringBootApplication
+ * 	public class MyApplication {
+ * 		public static void main(String[] args) {
+ * 			SpringApplication.run(MyApplication.class, args);
+ * 		}
+ * 	}
+ *
+ * 	// Custom filter registration
+ * 	@Configuration
+ * 	public class WebFilterConfig {
+ * 		@Bean
+ * 		public PerformanceLogFilter(Tracer trace, Environment) {
+ * 			return new PerformanceLogFilter(tracer, environment);
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *

Logging Example:

+ * + *
{@code
+ * // Performance log (perfLogger)
+ * [perfLogger] [SpanId:12345] [TraceId:abcd1234] GET /api/example took 120ms - Success
+ *
+ * // Request/Response log (reqResLogger)
+ * [reqResLogger] [RequestId:abcdef] Request Body: {"name":"hoangtien2k3"}
+ * [reqResLogger] [RequestId:abcdef] Response Body: {"status":"OK"}
+ * }
+ */ @Component @RequiredArgsConstructor public class PerformanceLogFilter implements WebFilter, Ordered { @@ -53,6 +107,30 @@ public class PerformanceLogFilter implements WebFilter, Ordered { private static final int MAX_BYTE = 800; // Max byte allow to print private final Environment environment; + /** + * Filters the incoming HTTP request to log performance metrics and optionally + * logs request/response data if the environment is not production. + * + *

Processing Flow:

+ *
    + *
  1. Records the start time of the request.
  2. + *
  3. Creates a new tracing span using Sleuth's Tracer.
  4. + *
  5. If the request targets an actuator endpoint, it bypasses logging and + * continues the chain.
  6. + *
  7. Filters the request through the chain, logging performance data upon + * success or failure.
  8. + *
  9. If the environment is not production, logs the request and response + * bodies.
  10. + *
+ * + * @param exchange + * The current server web exchange containing the request and + * response. + * @param chain + * The WebFilterChain to pass the request to the next filter. + * @return A {@code Mono} that completes when the filter chain has + * completed. + */ @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { long startMillis = System.currentTimeMillis(); @@ -72,8 +150,7 @@ public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { logPerf(exchange, newSpan, name, startMillis, "F", null); }) .contextWrite(context -> { - var currContext = (Context) context; - contextRef.set(currContext); + contextRef.set((Context) context); // the error happens in a different thread, so get the trace from context, set // in MDC // and downstream @@ -218,7 +295,7 @@ private String truncateBody(MultiValueMap formData) { StringBuilder messageResponse = new StringBuilder(); Set keys = formData.keySet(); for (String key : keys) { - messageResponse.append(key + ":" + truncateBody(formData.get(key))); + messageResponse.append(key).append(":").append(truncateBody(formData.get(key))); } return messageResponse.toString(); } @@ -232,65 +309,11 @@ private String truncateBody(List messageList) { } private String truncateBody(String s, int maxByte) { - int b = 0; - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - - // ranges from http://en.wikipedia.org/wiki/UTF-8 - int skip = 0; - int more; - if (c <= 0x007f) { - more = 1; - } else if (c <= 0x07FF) { - more = 2; - } else if (c <= 0xd7ff) { - more = 3; - } else if (c <= 0xDFFF) { - // surrogate area, consume next char as well - more = 4; - skip = 1; - } else { - more = 3; - } - - if (b + more > maxByte) { - return s.substring(0, i); - } - b += more; - i += skip; - } - return s; + return TruncateUtils.truncateBody(s, maxByte); } private String truncateBody(String s) { - int b = 0; - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - - // ranges from http://en.wikipedia.org/wiki/UTF-8 - int skip = 0; - int more; - if (c <= 0x007f) { - more = 1; - } else if (c <= 0x07FF) { - more = 2; - } else if (c <= 0xd7ff) { - more = 3; - } else if (c <= 0xDFFF) { - // surrogate area, consume next char as well - more = 4; - skip = 1; - } else { - more = 3; - } - - if (b + more > MAX_BYTE) { - return s.substring(0, i); - } - b += more; - i += skip; - } - return s; + return TruncateUtils.truncateBody(s, MAX_BYTE); } /** diff --git a/src/main/java/io/hoangtien2k3/commons/filter/http/ResponseLogFilter.java b/src/main/java/io/hoangtien2k3/commons/filter/http/ResponseLogFilter.java index 19a7301..a1c7f1d 100644 --- a/src/main/java/io/hoangtien2k3/commons/filter/http/ResponseLogFilter.java +++ b/src/main/java/io/hoangtien2k3/commons/filter/http/ResponseLogFilter.java @@ -43,15 +43,65 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +/** + * A WebFlux filter for logging HTTP response bodies in a Spring Gateway + * application. This filter decorates the response object to capture and log the + * response body content. It is typically used in non-production environments to + * help with debugging and monitoring. + *

+ * The filter can handle large response bodies by configuring the maximum + * in-memory size, and it supports both Mono and Flux types of responses. + * + *

Key Features:

+ *
    + *
  • Logs HTTP response bodies for requests that match certain criteria.
  • + *
  • Decorates the response object to intercept and modify the response + * body.
  • + *
  • Supports both single (Mono) and multiple (Flux) data buffers.
  • + *
  • Configurable in-memory buffer size for handling large responses.
  • + *
+ * + *

Usage Example:

+ * + *
+ * {
+ * 	@code
+ * 	@Configuration
+ * 	public class WebFilterConfig {
+ *
+ * 		@Bean
+ * 		@Profile("!prod")
+ * 		public ResponseLogFilter responseLogFilter() {
+ * 			return new ResponseLogFilter();
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *

Logging Example:

+ * + *
{@code
+ * // Example log output
+ * [ResponseLogFilter] Response Body: {"status":"OK", "data": {...}}
+ * }
+ */ @Log4j2 @AllArgsConstructor @Component // @Profile("!prod") public class ResponseLogFilter implements WebFilter, Ordered { + private final ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder() .codecs(cl -> cl.defaultCodecs().maxInMemorySize(50 * 1024 * 1024)) .build(); + /** + * Converts an InputStream to a byte array. + * + * @param inStream + * the InputStream to convert + * @return the byte array representation of the input stream + */ private static byte[] toByteArray(InputStream inStream) { ByteArrayOutputStream swapStream = new ByteArrayOutputStream(); byte[] buff = new byte[100]; @@ -68,6 +118,27 @@ private static byte[] toByteArray(InputStream inStream) { return in_b; } + /** + * Filters the incoming HTTP request and decorates the response to log the + * response body. + * + *

Processing Flow:

+ *
    + *
  1. Checks if the request should log the response body based on the + * {@link GatewayContext} settings.
  2. + *
  3. If logging is enabled, wraps the response in a + * {@link ServerHttpResponseDecorator}.
  4. + *
  5. The decorator intercepts the response body, logs it, and returns a + * modified data buffer.
  6. + *
+ * + * @param exchange + * The current server web exchange containing the request and + * response. + * @param chain + * The WebFilterChain to pass the request to the next filter. + * @return A `Mono` that completes when the filter chain has completed. + */ @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT); @@ -101,6 +172,16 @@ public Mono writeAndFlushWith(Publisher flux; private final HttpHeaders headers; + /** + * Constructs a ResponseAdapter with the given body and headers. + * + * @param body + * The Publisher that provides the response body. + * @param headers + * The headers associated with the response. + */ public ResponseAdapter(Publisher body, HttpHeaders headers) { this.headers = headers; if (body instanceof Flux) { diff --git a/src/main/java/io/hoangtien2k3/commons/filter/properties/KeyCloakProperties.java b/src/main/java/io/hoangtien2k3/commons/filter/properties/KeyCloakProperties.java index 3fe8701..1fa9150 100644 --- a/src/main/java/io/hoangtien2k3/commons/filter/properties/KeyCloakProperties.java +++ b/src/main/java/io/hoangtien2k3/commons/filter/properties/KeyCloakProperties.java @@ -18,6 +18,74 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * The `KeyCloakProperties` class is used to store configuration properties + * related to Keycloak. It is a simple POJO (Plain Old Java Object) with Lombok + * annotations to reduce boilerplate code such as getters, setters, and + * constructors. + * + *

Attributes:

+ *
    + *
  • clientId: A string representing the client ID used in + * Keycloak. This is typically used to identify the client application.
  • + *
  • clientSecret: A string representing the client secret + * used in Keycloak. This secret is used to authenticate the client application + * with Keycloak.
  • + *
+ * + *

Lombok Annotations:

+ *
    + *
  • @Data: Generates boilerplate code such as getters, + * setters, `equals()`, `hashCode()`, and `toString()` methods + * automatically.
  • + *
  • @AllArgsConstructor: Generates a constructor with all + * fields as parameters. This allows you to create an instance of the class with + * all attributes set at once.
  • + *
  • @NoArgsConstructor: Generates a no-arguments + * constructor. This is useful for creating instances of the class without + * setting any attributes initially.
  • + *
+ * + *

Usage Example:

+ * + *
+ * {
+ * 	@code
+ * 	@Configuration
+ * 	@ConfigurationProperties(prefix = "keycloak")
+ * 	public class KeyCloakConfig {
+ *
+ * 		private final KeyCloakProperties keyCloakProperties;
+ *
+ * 		@Autowired
+ * 		public KeyCloakConfig(KeyCloakProperties keyCloakProperties) {
+ * 			this.keyCloakProperties = keyCloakProperties;
+ * 		}
+ *
+ * 		@PostConstruct
+ * 		public void init() {
+ * 			// Example of accessing properties
+ * 			System.out.println("Client ID: " + keyCloakProperties.getClientId());
+ * 			System.out.println("Client Secret: " + keyCloakProperties.getClientSecret());
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *

Configuration Example:

+ * + *
{@code
+ * # application.yml
+ * keycloak:
+ *   clientId: my-client-id
+ *   clientSecret: my-client-secret
+ * }
+ * + * In this example, the `KeyCloakProperties` class is used to bind the + * configuration properties from `application.yml` file. The `KeyCloakConfig` + * class demonstrates how to use these properties within a Spring Boot + * application. + */ @Data @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/io/hoangtien2k3/commons/filter/properties/MonitoringProperties.java b/src/main/java/io/hoangtien2k3/commons/filter/properties/MonitoringProperties.java index 07d5ae0..34f3e87 100644 --- a/src/main/java/io/hoangtien2k3/commons/filter/properties/MonitoringProperties.java +++ b/src/main/java/io/hoangtien2k3/commons/filter/properties/MonitoringProperties.java @@ -20,6 +20,87 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * The `MonitoringProperties` class holds configuration properties related to + * monitoring within an application. It uses Lombok annotations to automatically + * generate boilerplate code such as getters, setters, and constructors. + * + *

Attributes:

+ *
    + *
  • isEnable: A boolean flag indicating whether monitoring + * is enabled or not. Defaults to `true` if not explicitly set.
  • + *
  • meterRegistry: An instance of `MeterRegistry` used for + * registering and managing metrics. Defaults to a `LoggingMeterRegistry` if not + * explicitly set.
  • + *
+ * + *

Lombok Annotations:

+ *
    + *
  • @Data: Generates getters, setters, `equals()`, + * `hashCode()`, and `toString()` methods automatically.
  • + *
  • @AllArgsConstructor: Creates a constructor with all + * fields as parameters, allowing for easy instantiation of the class with all + * properties set.
  • + *
  • @NoArgsConstructor: Provides a no-argument constructor + * for creating instances of the class without initializing the fields.
  • + *
+ * + *

Usage Example:

+ * + *
+ * {
+ * 	@code
+ * 	@Configuration
+ * 	@ConfigurationProperties(prefix = "monitoring")
+ * 	public class MonitoringConfig {
+ *
+ * 		private final MonitoringProperties monitoringProperties;
+ *
+ * 		@Autowired
+ * 		public MonitoringConfig(MonitoringProperties monitoringProperties) {
+ * 			this.monitoringProperties = monitoringProperties;
+ * 		}
+ *
+ * 		@PostConstruct
+ * 		public void init() {
+ * 			// Example of accessing properties
+ * 			System.out.println("Monitoring Enabled: " + monitoringProperties.isEnable());
+ * 			System.out.println("Meter Registry: " + monitoringProperties.getMeterRegistry());
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *

Configuration Example:

+ * + *
{@code
+ * # application.yml
+ * monitoring:
+ *   isEnable: true
+ *   meterRegistry: com.example.CustomMeterRegistry
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The `MonitoringProperties` class is used to configure monitoring settings for + * an application. The `isEnable` flag determines whether monitoring is active, + * while the `meterRegistry` attribute specifies the type of meter registry to + * be used for managing metrics. + *

+ * + *

+ * The default value for `isEnable` is `true`, meaning monitoring is enabled by + * default unless explicitly disabled. + *

+ * + *

+ * The `meterRegistry` is an instance of `MeterRegistry`, which is responsible + * for collecting and reporting metrics. The default value is an instance of + * `LoggingMeterRegistry`, which logs metrics to the console. You can replace + * this with a custom `MeterRegistry` implementation as needed. + *

+ */ @Data @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/io/hoangtien2k3/commons/filter/properties/PoolProperties.java b/src/main/java/io/hoangtien2k3/commons/filter/properties/PoolProperties.java index f1fc15c..7b92b79 100644 --- a/src/main/java/io/hoangtien2k3/commons/filter/properties/PoolProperties.java +++ b/src/main/java/io/hoangtien2k3/commons/filter/properties/PoolProperties.java @@ -18,6 +18,96 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * The `PoolProperties` class encapsulates configuration settings for a + * connection pool. It uses Lombok annotations to generate boilerplate code such + * as getters, setters, and constructors. + * + *

Attributes:

+ *
    + *
  • maxSize: An integer representing the maximum number of + * connections that the pool can hold. Defaults to `2000` if not explicitly + * set.
  • + *
  • maxPendingAcquire: An integer representing the maximum + * number of connections that can be pending acquisition from the pool. Defaults + * to `2000` if not explicitly set.
  • + *
+ * + *

Lombok Annotations:

+ *
    + *
  • @Data: Automatically generates getters, setters, + * `equals()`, `hashCode()`, and `toString()` methods.
  • + *
  • @AllArgsConstructor: Creates a constructor with all + * fields as parameters, allowing for easy instantiation of the class with all + * properties set.
  • + *
  • @NoArgsConstructor: Provides a no-argument constructor + * for creating instances of the class with default values.
  • + *
+ * + *

Usage Example:

+ * + *
+ * {
+ * 	@code
+ * 	@Configuration
+ * 	@ConfigurationProperties(prefix = "pool")
+ * 	public class PoolConfig {
+ *
+ * 		private final PoolProperties poolProperties;
+ *
+ * 		@Autowired
+ * 		public PoolConfig(PoolProperties poolProperties) {
+ * 			this.poolProperties = poolProperties;
+ * 		}
+ *
+ * 		@PostConstruct
+ * 		public void init() {
+ * 			// Example of accessing properties
+ * 			System.out.println("Max Pool Size: " + poolProperties.getMaxSize());
+ * 			System.out.println("Max Pending Acquire: " + poolProperties.getMaxPendingAcquire());
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *

Configuration Example:

+ * + *
{@code
+ * # application.yml
+ * pool:
+ *   maxSize: 3000
+ *   maxPendingAcquire: 5000
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The `PoolProperties` class is used to configure the parameters for a + * connection pool in an application. It allows you to specify the maximum size + * of the pool and the maximum number of connections that can be pending + * acquisition. + *

+ * + *

+ * The default value for `maxSize` is `2000`, which means the pool can hold up + * to 2000 connections. If you want to allow more or fewer connections, you can + * adjust this value. + *

+ * + *

+ * The `maxPendingAcquire` value is also set to `2000` by default. This property + * defines the maximum number of connections that can be in the process of being + * acquired from the pool. If the number of pending connections exceeds this + * limit, additional requests will have to wait until a connection becomes + * available. + *

+ * + *

+ * Both properties are customizable to fit the specific needs of your + * application's connection pool. Adjusting these values can help optimize + * performance and resource usage based on your application's requirements. + *

+ */ @Data @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/io/hoangtien2k3/commons/filter/properties/ProxyProperties.java b/src/main/java/io/hoangtien2k3/commons/filter/properties/ProxyProperties.java index 7c0caf6..9753de8 100644 --- a/src/main/java/io/hoangtien2k3/commons/filter/properties/ProxyProperties.java +++ b/src/main/java/io/hoangtien2k3/commons/filter/properties/ProxyProperties.java @@ -18,6 +18,103 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * The `ProxyProperties` class is used to configure proxy settings for an + * application. It employs Lombok annotations to automatically generate getters, + * setters, and constructors. + * + *

Attributes:

+ *
    + *
  • enable: A boolean flag indicating whether proxy support + * is enabled. Defaults to `false` if not explicitly set.
  • + *
  • httpHost: A string representing the host address for + * HTTP proxy configuration. This value should be set if an HTTP proxy is + * required.
  • + *
  • httpPort: An integer specifying the port number for HTTP + * proxy configuration. This value should be set in conjunction with + * `httpHost`.
  • + *
  • httpsHost: A string representing the host address for + * HTTPS proxy configuration. This value should be set if an HTTPS proxy is + * required.
  • + *
  • httpsPort: An integer specifying the port number for + * HTTPS proxy configuration. This value should be set in conjunction with + * `httpsHost`.
  • + *
+ * + *

Lombok Annotations:

+ *
    + *
  • @Data: Automatically generates getters, setters, + * `equals()`, `hashCode()`, and `toString()` methods for the class.
  • + *
  • @AllArgsConstructor: Creates a constructor with all + * fields as parameters, allowing for easy instantiation with all properties + * set.
  • + *
  • @NoArgsConstructor: Provides a no-argument constructor + * for creating instances with default values.
  • + *
+ * + *

Usage Example:

+ * + *
+ * {
+ * 	@code
+ * 	@Configuration
+ * 	@ConfigurationProperties(prefix = "proxy")
+ * 	public class ProxyConfig {
+ *
+ * 		private final ProxyProperties proxyProperties;
+ *
+ * 		@Autowired
+ * 		public ProxyConfig(ProxyProperties proxyProperties) {
+ * 			this.proxyProperties = proxyProperties;
+ * 		}
+ *
+ * 		@PostConstruct
+ * 		public void init() {
+ * 			if (proxyProperties.isEnable()) {
+ * 				// Example of accessing and using proxy settings
+ * 				System.out.println("HTTP Proxy Host: " + proxyProperties.getHttpHost());
+ * 				System.out.println("HTTP Proxy Port: " + proxyProperties.getHttpPort());
+ * 				System.out.println("HTTPS Proxy Host: " + proxyProperties.getHttpsHost());
+ * 				System.out.println("HTTPS Proxy Port: " + proxyProperties.getHttpsPort());
+ * 			}
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *

Configuration Example:

+ * + *
{@code
+ * # application.yml
+ * proxy:
+ *   enable: true
+ *   httpHost: "http-proxy.example.com"
+ *   httpPort: 8080
+ *   httpsHost: "https-proxy.example.com"
+ *   httpsPort: 8443
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The `ProxyProperties` class provides a way to configure proxy settings for + * your application, including both HTTP and HTTPS proxies. It is particularly + * useful when your application needs to communicate through a proxy server. + *

+ * + *

+ * By default, proxy support is disabled (i.e., `enable` is `false`). To use a + * proxy, you need to set the `enable` flag to `true` and provide the necessary + * `httpHost`, `httpPort`, `httpsHost`, and `httpsPort` values. + *

+ * + *

+ * The `httpHost` and `httpPort` define the proxy settings for HTTP connections, + * while the `httpsHost` and `httpsPort` define the settings for HTTPS + * connections. Make sure to configure these values appropriately based on your + * network infrastructure and proxy server settings. + *

+ */ @Data @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/io/hoangtien2k3/commons/filter/properties/RetryProperties.java b/src/main/java/io/hoangtien2k3/commons/filter/properties/RetryProperties.java index ac4fb2f..abe8c2c 100644 --- a/src/main/java/io/hoangtien2k3/commons/filter/properties/RetryProperties.java +++ b/src/main/java/io/hoangtien2k3/commons/filter/properties/RetryProperties.java @@ -22,6 +22,112 @@ import lombok.NoArgsConstructor; import org.springframework.http.HttpMethod; +/** + * The `RetryProperties` class is used to configure retry behavior for HTTP + * requests within an application. It leverages Lombok annotations to + * automatically generate getters, setters, and constructors. + * + *

Attributes:

+ *
    + *
  • isEnable: A boolean flag indicating whether retry + * functionality is enabled. Defaults to `true` if not explicitly set.
  • + *
  • count: An integer specifying the number of retry + * attempts. Defaults to `2` if not explicitly set.
  • + *
  • methods: A list of HTTP methods for which retry should + * be applied. By default, it includes `HttpMethod.GET`, `HttpMethod.PUT`, and + * `HttpMethod.DELETE`.
  • + *
  • exceptions: A list of exception classes that should + * trigger a retry. Defaults to `ConnectTimeoutException` and + * `ReadTimeoutException`.
  • + *
+ * + *

Lombok Annotations:

+ *
    + *
  • @Data: Automatically generates getters, setters, + * `equals()`, `hashCode()`, and `toString()` methods for the class.
  • + *
  • @AllArgsConstructor: Creates a constructor with all + * fields as parameters, allowing for easy instantiation with all properties + * set.
  • + *
  • @NoArgsConstructor: Provides a no-argument constructor + * for creating instances with default values.
  • + *
+ * + *

Usage Example:

+ * + *
+ * {
+ * 	@code
+ * 	@Configuration
+ * 	@ConfigurationProperties(prefix = "retry")
+ * 	public class RetryConfig {
+ *
+ * 		private final RetryProperties retryProperties;
+ *
+ * 		@Autowired
+ * 		public RetryConfig(RetryProperties retryProperties) {
+ * 			this.retryProperties = retryProperties;
+ * 		}
+ *
+ * 		@Bean
+ * 		public WebClient.Builder webClientBuilder() {
+ * 			return WebClient.builder().filter((request, next) -> {
+ * 				if (retryProperties.isEnable()) {
+ * 					return next.exchange(request)
+ * 							.retryWhen(Retry.backoff(retryProperties.getCount(), Duration.ofSeconds(1))
+ * 									.filter(throwable -> retryProperties.getExceptions().stream()
+ * 											.anyMatch(exceptionClass -> exceptionClass.isInstance(throwable))));
+ * 				}
+ * 				return next.exchange(request);
+ * 			});
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *

Configuration Example:

+ * + *
{@code
+ * # application.yml
+ * retry:
+ *   isEnable: true
+ *   count: 3
+ *   methods:
+ *     - GET
+ *     - POST
+ *   exceptions:
+ *     - java.net.ConnectException
+ *     - java.net.SocketTimeoutException
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The `RetryProperties` class provides configuration options for retrying HTTP + * requests when certain conditions are met. The `isEnable` flag determines + * whether the retry logic is active. The `count` attribute specifies how many + * times the request should be retried if a retryable exception occurs. + *

+ * + *

+ * The `methods` list specifies which HTTP methods the retry logic should apply + * to. By default, it includes `GET`, `PUT`, and `DELETE` requests, but this can + * be customized based on the application's requirements. + *

+ * + *

+ * The `exceptions` list includes exception classes that trigger a retry. + * Commonly used exceptions like `ConnectTimeoutException` and + * `ReadTimeoutException` are included by default, but this can be customized to + * handle other specific exceptions as needed. + *

+ * + *

+ * Using this configuration allows applications to handle transient errors and + * improve resilience by automatically retrying failed requests, which can be + * particularly useful in distributed systems or when dealing with unreliable + * network conditions. + *

+ */ @Data @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/io/hoangtien2k3/commons/filter/properties/TimeoutProperties.java b/src/main/java/io/hoangtien2k3/commons/filter/properties/TimeoutProperties.java index 0bf972b..c2bba34 100644 --- a/src/main/java/io/hoangtien2k3/commons/filter/properties/TimeoutProperties.java +++ b/src/main/java/io/hoangtien2k3/commons/filter/properties/TimeoutProperties.java @@ -18,6 +18,87 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * The `TimeoutProperties` class is used to configure timeout settings for HTTP + * requests within an application. It leverages Lombok annotations to + * automatically generate getters, setters, and constructors. + * + *

Attributes:

+ *
    + *
  • read: An integer specifying the read timeout in + * milliseconds. The default value is `180000` milliseconds (3 minutes). This + * timeout controls how long the application will wait for data to be read from + * the server before timing out.
  • + *
  • connection: An integer specifying the connection timeout + * in milliseconds. The default value is `500` milliseconds (0.5 seconds). This + * timeout controls how long the application will wait to establish a connection + * to the server before timing out.
  • + *
+ * + *

Lombok Annotations:

+ *
    + *
  • @Data: Automatically generates getters, setters, + * `equals()`, `hashCode()`, and `toString()` methods for the class.
  • + *
  • @AllArgsConstructor: Creates a constructor with all + * fields as parameters, allowing for easy instantiation with all properties + * set.
  • + *
  • @NoArgsConstructor: Provides a no-argument constructor + * for creating instances with default values.
  • + *
+ * + *

Usage Example:

+ * + *
+ * {
+ * 	@code
+ * 	@Configuration
+ * 	@ConfigurationProperties(prefix = "timeout")
+ * 	public class TimeoutConfig {
+ *
+ * 		private final TimeoutProperties timeoutProperties;
+ *
+ * 		@Autowired
+ * 		public TimeoutConfig(TimeoutProperties timeoutProperties) {
+ * 			this.timeoutProperties = timeoutProperties;
+ * 		}
+ *
+ * 		@Bean
+ * 		public WebClient.Builder webClientBuilder() {
+ * 			return WebClient.builder()
+ * 					.clientConnector(new ReactorClientHttpConnector(HttpClient.create()
+ * 							.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeoutProperties.getConnection())
+ * 							.responseTimeout(Duration.ofMillis(timeoutProperties.getRead()))));
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *

Configuration Example:

+ * + *
{@code
+ * # application.yml
+ * timeout:
+ *   read: 180000
+ *   connection: 500
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The `TimeoutProperties` class provides configuration options for setting + * timeout values for HTTP requests. The `read` attribute specifies how long the + * client will wait for data to be read from the server before giving up. The + * `connection` attribute specifies how long the client will wait to establish a + * connection to the server. + *

+ * + *

+ * Using this configuration allows you to fine-tune how your application handles + * network delays and connection issues. By setting appropriate timeout values, + * you can improve the responsiveness of your application and handle network + * conditions more effectively. + *

+ */ @Data @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/io/hoangtien2k3/commons/filter/webclient/WebClientLoggingFilter.java b/src/main/java/io/hoangtien2k3/commons/filter/webclient/WebClientLoggingFilter.java index adbe3e8..da45243 100644 --- a/src/main/java/io/hoangtien2k3/commons/filter/webclient/WebClientLoggingFilter.java +++ b/src/main/java/io/hoangtien2k3/commons/filter/webclient/WebClientLoggingFilter.java @@ -23,6 +23,105 @@ import org.springframework.web.reactive.function.client.ExchangeFunction; import reactor.core.publisher.Mono; +/** + * The {@code WebClientLoggingFilter} class is an implementation of + * {@link ExchangeFilterFunction} used to log HTTP request and response details + * when using Spring's {@link}. This filter logs information about API calls, + * including headers and bodies, with support for obfuscating sensitive header + * values. It utilizes Lombok annotations for logging and constructor injection. + * + *

Attributes:

+ *
    + *
  • OBFUSCATE_HEADER: A constant string used to replace + * sensitive header values in logs. The default value is "xxxxx".
  • + *
  • obfuscateHeader: A list of header names that should be + * obfuscated in the logs. This list is provided during the instantiation of the + * filter.
  • + *
+ * + *

Methods:

+ *
    + *
  • filter: The core method of the + * {@code WebClientLoggingFilter} class. It intercepts the request and response, + * logs request details, and then passes the request to the next + * {@link ExchangeFunction} in the chain. After receiving the response, it logs + * the response details before returning the {@link ClientResponse} to the + * caller. + *
      + *
    • Parameters: + *
        + *
      • request: The original {@link ClientRequest} object + * representing the HTTP request.
      • + *
      • next: The next {@link ExchangeFunction} in the chain + * that will handle the request after this filter.
      • + *
      + *
    • + *
    • Returns: A {@link Mono} of {@link ClientResponse} + * representing the response returned by the next filter in the chain.
    • + *
    + *
  • + *
+ * + *

Logging:

+ *
    + *
  • Request Logging: Logs the HTTP method and URL of the + * request. If the request has a body, it is logged as well. Headers are logged + * in debug mode with sensitive values obfuscated if their names match any in + * the {@code obfuscateHeader} list.
  • + *
  • Response Logging: Logs response headers in debug mode, + * with sensitive values obfuscated if necessary.
  • + *
+ * + *

Usage Example:

+ * + *
+ * {
+ * 	@code
+ * 	@Configuration
+ * 	public class WebClientConfig {
+ *
+ * 		@Bean
+ * 		public WebClient.Builder webClientBuilder() {
+ * 			List obfuscateHeaders = List.of("Authorization", "Cookie");
+ * 			return WebClient.builder().filter(new WebClientLoggingFilter(obfuscateHeaders));
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *

Configuration Example:

+ * + *
{@code
+ * # application.yml
+ * webclient:
+ *   logging:
+ *     obfuscate-headers:
+ *       - Authorization
+ *       - Cookie
+ * }
+ * + *

Detailed Description:

+ *

+ * The {@code WebClientLoggingFilter} class provides detailed logging for HTTP + * requests and responses. It is particularly useful for debugging and + * monitoring API interactions. The filter logs important request and response + * details, including headers and bodies (if present). Sensitive header values, + * such as authorization tokens or cookies, can be obfuscated in the logs by + * specifying their names in the {@code obfuscateHeader} list. + *

+ * + *

+ * Logging levels are controlled by the {@code log} object. Request and response + * bodies are logged if their length is greater than zero. Header names and + * values are logged at the debug level, with sensitive values replaced by a + * placeholder string. + *

+ * + *

+ * This filter can be added to a {@link} configuration to automatically apply + * logging to all requests made through that client. + *

+ */ @Slf4j @RequiredArgsConstructor public class WebClientLoggingFilter implements ExchangeFilterFunction { diff --git a/src/main/java/io/hoangtien2k3/commons/filter/webclient/WebClientMonitoringFilter.java b/src/main/java/io/hoangtien2k3/commons/filter/webclient/WebClientMonitoringFilter.java index 43b8c0f..0fb39a9 100644 --- a/src/main/java/io/hoangtien2k3/commons/filter/webclient/WebClientMonitoringFilter.java +++ b/src/main/java/io/hoangtien2k3/commons/filter/webclient/WebClientMonitoringFilter.java @@ -24,6 +24,107 @@ import org.springframework.web.reactive.function.client.ExchangeFunction; import reactor.core.publisher.Mono; +/** + * The `WebClientMonitoringFilter` class implements `ExchangeFilterFunction` to + * monitor and record metrics for HTTP requests made using Spring's `WebClient`. + * It utilizes Micrometer's `MeterRegistry` to collect and record metrics such + * as response time. + * + *

Attributes:

+ *
    + *
  • METRICS_WEBCLIENT_START_TIME: A constant string used as + * a key to store the start time of the HTTP request in the context.
  • + *
  • meterRegistry: An instance of `MeterRegistry` used to + * register and publish metrics. It is provided during the instantiation of the + * filter.
  • + *
  • tagsProvider: (Commented out) A + * `WebClientExchangeTagsProvider` for generating tags for the metrics. It is + * currently not used but can be configured for more detailed metrics.
  • + *
+ * + *

Methods:

+ *
    + *
  • filter: This method is invoked for every HTTP request + * handled by the `WebClient`. It: + *
      + *
    • Records the start time of the request in the context.
    • + *
    • Registers a `Timer` metric with `MeterRegistry` to measure the duration + * of the request and response.
    • + *
    • Logs the request duration in seconds, including percentiles for response + * time.
    • + *
    + *
      + *
    • Parameters: + *
        + *
      • clientRequest: The `ClientRequest` object representing + * the HTTP request.
      • + *
      • exchangeFunction: The `ExchangeFunction` that processes + * the HTTP request and returns a `ClientResponse`.
      • + *
      + *
    • + *
    • Returns: A `Mono` representing the + * response from the exchange function.
    • + *
    + *
  • + *
+ * + *

Logging:

+ *
    + *
  • Monitoring Logging: Logs the duration of the API call in + * seconds. The duration is calculated based on the time elapsed between the + * start and end of the request.
  • + *
+ * + *

Usage Example:

+ * + *
+ * {
+ * 	@code
+ * 	@Configuration
+ * 	public class WebClientConfig {
+ *
+ * 		@Bean
+ * 		public WebClient.Builder webClientBuilder(MeterRegistry meterRegistry) {
+ * 			return WebClient.builder().filter(new WebClientMonitoringFilter(meterRegistry));
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *

Configuration Example:

+ * + *
{@code
+ * # application.yml
+ * metrics:
+ *   webclient:
+ *     enabled: true
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The `WebClientMonitoringFilter` class provides monitoring capabilities for + * HTTP requests made through Spring's `WebClient`. It is designed to track and + * record metrics such as response times to help in performance monitoring and + * analysis. The filter uses Micrometer's `MeterRegistry` to register and + * publish these metrics, allowing you to monitor the duration of HTTP requests. + *

+ * + *

+ * When a request is processed, the filter captures the start time and records + * the duration of the request once the response is received. The metrics are + * published with percentiles to provide insight into response time + * distributions. You can also log the duration of requests for further + * analysis. + *

+ * + *

+ * To use this filter, configure it as part of your `WebClient` setup and + * provide a `MeterRegistry` bean to register the metrics. This filter is useful + * in performance monitoring scenarios, where understanding request durations + * and response times is crucial for optimizing application performance. + *

+ */ @Slf4j @Data @RequiredArgsConstructor diff --git a/src/main/java/io/hoangtien2k3/commons/filter/webclient/WebClientRetryHandler.java b/src/main/java/io/hoangtien2k3/commons/filter/webclient/WebClientRetryHandler.java index 1e33cfe..caa6d6e 100644 --- a/src/main/java/io/hoangtien2k3/commons/filter/webclient/WebClientRetryHandler.java +++ b/src/main/java/io/hoangtien2k3/commons/filter/webclient/WebClientRetryHandler.java @@ -25,6 +25,111 @@ import reactor.core.publisher.Mono; import reactor.util.retry.Retry; +/** + * The `WebClientRetryHandler` class implements `ExchangeFilterFunction` to + * provide retry functionality for HTTP requests made using Spring's + * `WebClient`. It uses a configurable retry strategy to handle transient + * failures and retry requests based on specific conditions. + * + *

Attributes:

+ *
    + *
  • properties: An instance of `RetryProperties` used to + * configure retry behavior, such as the number of retry attempts, HTTP methods + * to apply retries, and exception types that should trigger retries.
  • + *
+ * + *

Methods:

+ *
    + *
  • filter: This method is invoked for every HTTP request + * handled by the `WebClient`. It: + *
      + *
    • Creates a `Retry` instance with the configured properties, including the + * maximum number of retry attempts and the conditions under which retries + * should be performed.
    • + *
    • Logs a warning message each time a retry is attempted, including the + * number of retries and the cause of the failure.
    • + *
    • Throws an exception if all retry attempts are exhausted.
    • + *
    + *
      + *
    • Parameters: + *
        + *
      • request: The `ClientRequest` object representing the + * HTTP request.
      • + *
      • next: The `ExchangeFunction` that processes the HTTP + * request and returns a `ClientResponse`.
      • + *
      + *
    • + *
    • Returns: A `Mono` representing the + * response from the exchange function, which includes retry handling.
    • + *
    + *
  • + *
+ * + *

Logging:

+ *
    + *
  • Retry Logging: Logs a warning message each time a retry + * is performed, including the retry count and the cause of the failure. This + * helps in monitoring and debugging retry attempts.
  • + *
+ * + *

Usage Example:

+ * + *
+ * {
+ * 	@code
+ * 	@Configuration
+ * 	public class WebClientConfig {
+ *
+ * 		@Bean
+ * 		public WebClient.Builder webClientBuilder(RetryProperties retryProperties) {
+ * 			return WebClient.builder().filter(new WebClientRetryHandler(retryProperties));
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *

Configuration Example:

+ * + *
{@code
+ * # application.yml
+ * retry:
+ *   enable: true
+ *   count: 3
+ *   methods:
+ *     - GET
+ *     - PUT
+ *   exceptions:
+ *     - java.net.ConnectException
+ *     - java.net.SocketTimeoutException
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The `WebClientRetryHandler` class provides a way to handle transient errors + * and retry HTTP requests using Spring's `WebClient`. It allows you to + * configure retry behavior based on the number of retry attempts, the HTTP + * methods that should be retried, and the types of exceptions that should + * trigger retries. The retry logic is built using Reactor's retry + * functionality, which allows for configurable retry policies and handling of + * transient failures. + *

+ * + *

+ * When a request fails, the filter will attempt to retry the request based on + * the configured properties. The retry attempts are logged for monitoring + * purposes, and an exception is thrown if all retry attempts are exhausted. + * This functionality is useful in scenarios where transient errors are expected + * and retrying the request can lead to successful outcomes. + *

+ * + *

+ * To use this filter, configure it as part of your `WebClient` setup and + * provide a `RetryProperties` bean to specify the retry behavior. This filter + * is helpful for ensuring reliability in HTTP communications by automatically + * retrying requests in the event of transient failures. + *

+ */ @Slf4j @RequiredArgsConstructor public class WebClientRetryHandler implements ExchangeFilterFunction { diff --git a/src/main/java/io/hoangtien2k3/commons/model/SagaProcess.java b/src/main/java/io/hoangtien2k3/commons/model/SagaProcess.java index dd2413c..bdb2a29 100644 --- a/src/main/java/io/hoangtien2k3/commons/model/SagaProcess.java +++ b/src/main/java/io/hoangtien2k3/commons/model/SagaProcess.java @@ -24,13 +24,123 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; +/** + * The `SagaProcess` class represents an abstract base class for implementing a + * saga pattern. It defines the structure and behavior of a saga process, which + * consists of a series of steps that need to be executed and potentially rolled + * back in case of errors. + * + *

Attributes:

+ *
    + *
  • executedStep: A `List` of `SagaStep` objects that keeps + * track of the steps that have been executed so far. It is used for rollback + * purposes in case of failures.
  • + *
+ * + *

Abstract Methods:

+ *
    + *
  • getSteps(): This abstract method should be implemented + * by subclasses to return the list of `SagaStep` objects that define the steps + * of the saga process.
  • + *
+ * + *

Methods:

+ *
    + *
  • execute(): Executes the steps of the saga process. It + * processes each step in sequence and handles the results. If any step fails, + * the process triggers a rollback of the previously executed steps. The method: + *
      + *
    • Logs the start of the execution.
    • + *
    • Uses `Flux.fromIterable(getSteps())` to create a stream of steps.
    • + *
    • Applies `flatMap(SagaStep::execute)` to execute each step + * asynchronously.
    • + *
    • Handles the result of each step using `handle()` to either continue or + * trigger an error.
    • + *
    • Subscribes on a bounded elastic scheduler to ensure that the operations + * are performed in a dedicated thread pool.
    • + *
    • On error, calls the `revert()` method to roll back the changes made by + * the previously executed steps.
    • + *
    + *
  • + *
  • revert(): Rolls back the previously executed steps in + * reverse order. It is called when an error occurs during the execution of the + * saga process. The method: + *
      + *
    • Logs the start of the rollback.
    • + *
    • Reverses the order of the steps using + * `Collections.reverse(getSteps())`.
    • + *
    • Filters out the steps that have not been completed successfully and + * applies `flatMap(SagaStep::revert)` to revert each step asynchronously.
    • + *
    + *
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * public class MySagaProcess extends SagaProcess {
+ * 	@Override
+ * 	public List getSteps() {
+ * 		return List.of(new Step1(), new Step2(), new Step3());
+ * 	}
+ * }
+ *
+ * // Create an instance of MySagaProcess
+ * SagaProcess sagaProcess = new MySagaProcess();
+ *
+ * // Execute the saga process
+ * sagaProcess.execute().doOnSuccess(result -> System.out.println("Saga executed successfully"))
+ * 		.doOnError(ex -> System.out.println("Saga execution failed: " + ex.getMessage())).subscribe();
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The `SagaProcess` class is designed to manage complex transactional processes + * using the saga pattern. It provides a way to execute a series of steps while + * ensuring that if any step fails, the previously executed steps are rolled + * back to maintain consistency. The class uses reactive programming constructs + * from Project Reactor to handle asynchronous execution and rollback. + *

+ * + *

+ * Subclasses must implement the `getSteps()` method to provide the specific + * steps of the saga process. The `execute()` method orchestrates the execution + * of these steps and manages rollback in case of errors. The `revert()` method + * ensures that any changes made by the executed steps are undone in reverse + * order. + *

+ * + *

+ * This class is useful in scenarios where long-running transactions or complex + * business processes need to be managed, and where rollback capabilities are + * required to handle failures gracefully. + *

+ */ @Slf4j public abstract class SagaProcess { + /** + * Abstract method that should be implemented by subclasses to return the list + * of `SagaStep` objects that define the steps of the saga process. + * + * @return A list of `SagaStep` objects. + */ public abstract List getSteps(); + /** + * A `List` of `SagaStep` objects that keeps track of the steps that have been + * executed so far. This is used for rollback purposes in case of failures. + */ protected final List executedStep = new LinkedList<>(); + /** + * Executes the steps of the saga process. It processes each step in sequence + * and handles the results. If any step fails, the process triggers a rollback + * of the previously executed steps. + * + * @return A `Flux` indicating the result of the saga execution. + */ public Flux execute() { log.info("==================Start execute================"); return Flux.fromIterable(getSteps()) @@ -47,6 +157,12 @@ public Flux execute() { .onErrorResume(ex -> revert().then(Mono.error(ex))); } + /** + * Rolls back the previously executed steps in reverse order. It is called when + * an error occurs during the execution of the saga process. + * + * @return A `Flux` indicating the result of the rollback. + */ public Flux revert() { log.info("==================Start rollback================"); Collections.reverse(getSteps()); diff --git a/src/main/java/io/hoangtien2k3/commons/model/SagaStep.java b/src/main/java/io/hoangtien2k3/commons/model/SagaStep.java index 77c177f..2faabc9 100644 --- a/src/main/java/io/hoangtien2k3/commons/model/SagaStep.java +++ b/src/main/java/io/hoangtien2k3/commons/model/SagaStep.java @@ -16,11 +16,130 @@ import reactor.core.publisher.Mono; +/** + * The {@code SagaStep} interface defines a single step in a saga process. Each + * step represents an individual operation that can be executed, and, if + * necessary, rolled back in case of failure. The interface provides methods for + * executing, reverting, and checking the completion status of the step. + * + *

Methods:

+ *
    + *
  • complete(): Checks whether the step has been completed + * successfully. This is used in the rollback process to determine which steps + * need to be reverted.
  • + *
  • execute(): Executes the step and returns a + * {@link Mono}<{@link StepResult}> indicating the result of the + * execution. This method is used in the saga process to perform the step's + * operation.
  • + *
  • revert(): Reverts the step's changes if the execution + * fails. It returns a {@link Mono}<{@link Boolean}> indicating whether + * the revert operation was successful.
  • + *
+ * + *

Methods Description:

+ * + *

boolean complete()

+ *

+ * This method is used to check whether the step has been completed + * successfully. It returns {@code true} if the step is complete, and + * {@code false} otherwise. This information is crucial for determining which + * steps need to be rolled back in case of an error. + *

+ * + *

Mono<StepResult> execute()

+ *

+ * This method is responsible for executing the step's operation. It returns a + * {@link Mono}<{@link StepResult}>, where {@link StepResult} encapsulates + * the outcome of the execution. The {@link StepResult} typically contains + * information about whether the execution was successful and any relevant + * messages. This method is called during the saga process to perform the step's + * operation. + *

+ * + *

Mono<Boolean> revert()

+ *

+ * This method is used to revert the step's changes if the execution fails. It + * returns a {@link Mono}<{@link Boolean}>, where {@code true} indicates + * that the revert operation was successful and {@code false} otherwise. This + * method is called during the rollback process to undo the changes made by the + * step if necessary. + *

+ * + *

Usage Example:

+ * + *
+ * {
+ * 	@code
+ * 	public class MySagaStep implements SagaStep {
+ *
+ * 		@Override
+ * 		public boolean complete() {
+ * 			// Check if the step has been completed
+ * 			return true; // or false based on the actual completion status
+ * 		}
+ *
+ * 		@Override
+ * 		public Mono execute() {
+ * 			// Perform the step's operation and return the result
+ * 			return Mono.just(new StepResult(true, "Operation succeeded"));
+ * 		}
+ *
+ * 		@Override
+ * 		public Mono revert() {
+ * 			// Revert the step's changes and return the result
+ * 			return Mono.just(true); // or false based on the actual revert outcome
+ * 		}
+ * 	}
+ * 	// Create an instance of MySagaStep
+ * 	SagaStep sagaStep = new MySagaStep();
+ * 	// Execute the step
+ * 	sagaStep.execute().doOnNext(result -> System.out.println("Step executed successfully: " + result))
+ * 			.doOnError(ex -> System.out.println("Step execution failed: " + ex.getMessage())).subscribe();
+ * 	// Revert the step
+ * 	sagaStep.revert().doOnNext(success -> System.out.println("Step reverted successfully: " + success))
+ * 			.doOnError(ex -> System.out.println("Step revert failed: " + ex.getMessage())).subscribe();
+ * }
+ * 
+ * + *

Detailed Description:

+ * + *

+ * The {@code SagaStep} interface is a fundamental component in implementing the + * saga pattern. It defines the operations that can be performed as part of a + * saga process, including executing the step, checking its completion status, + * and reverting its changes if needed. Each step in the saga must implement + * this interface to be integrated into the saga process. + *

+ * + *

+ * By implementing the {@code SagaStep} interface, you can define specific + * behaviors for each step in your saga, handle successful execution, and manage + * rollback scenarios. This interface provides a structured way to manage + * complex transactional processes and ensure consistency in case of failures. + *

+ */ public interface SagaStep { + /** + * Checks whether the step has been completed successfully. + * + * @return {@code true} if the step is complete, {@code false} otherwise. + */ boolean complete(); + /** + * Executes the step's operation and returns a {@link Mono} containing the + * result. + * + * @return A {@link Mono} containing the outcome of the execution. + */ Mono execute(); + /** + * Reverts the step's changes if the execution fails. + * + * @return A {@link Mono}<{@link Boolean}> indicating whether the revert + * operation was successful. + */ Mono revert(); } diff --git a/src/main/java/io/hoangtien2k3/commons/model/StepResult.java b/src/main/java/io/hoangtien2k3/commons/model/StepResult.java index a8e7844..aec234a 100644 --- a/src/main/java/io/hoangtien2k3/commons/model/StepResult.java +++ b/src/main/java/io/hoangtien2k3/commons/model/StepResult.java @@ -16,20 +16,102 @@ import lombok.Getter; +/** + * The `StepResult` class represents the result of executing a step in a saga + * process. It encapsulates the outcome of the step execution, including whether + * it was successful and any associated message. + * + *

Fields:

+ *
    + *
  • success: A boolean indicating whether the step execution + * was successful. `true` if the execution was successful, `false` + * otherwise.
  • + *
  • message: A string containing any message associated with + * the result. This is typically used to provide additional information or error + * messages when the step fails.
  • + *
+ * + *

Constructors:

+ *

+ * The constructor is private and only accessible through the static factory + * methods. This design ensures that instances of `StepResult` are created with + * specific success and failure states. + *

+ * + *

Static Methods:

+ *
    + *
  • success(): Creates a `StepResult` instance representing + * a successful execution. The message will be `null`.
  • + *
  • failure(String message): Creates a `StepResult` instance + * representing a failed execution. The provided message will be used to + * describe the failure.
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * // Creating a successful result
+ * StepResult successResult = StepResult.success();
+ *
+ * // Creating a failure result with a message
+ * StepResult failureResult = StepResult.failure("An error occurred while executing the step.");
+ *
+ * // Checking the result
+ * if (successResult.isSuccess()) {
+ * 	System.out.println("Step executed successfully.");
+ * } else {
+ * 	System.out.println("Step failed: " + failureResult.getMessage());
+ * }
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The `StepResult` class is used to encapsulate the result of executing a step + * in a saga process. It provides a clear and structured way to represent + * whether the execution was successful or failed, along with an optional + * message that describes the outcome. This is useful for handling success and + * failure scenarios in the saga process and ensuring that appropriate actions + * are taken based on the result of each step. + *

+ */ @Getter public class StepResult { private final boolean success; private final String message; + /** + * Private constructor to create an instance of `StepResult`. + * + * @param success + * Indicates whether the step execution was successful. + * @param message + * A message associated with the result, or `null` if there is no + * message. + */ private StepResult(boolean success, String message) { this.success = success; this.message = message; } + /** + * Creates a `StepResult` instance representing a successful execution. + * + * @return A `StepResult` instance with `success` set to `true` and `message` + * set to `null`. + */ public static StepResult success() { return new StepResult(true, null); } + /** + * Creates a `StepResult` instance representing a failed execution. + * + * @param message + * A message describing the failure. + * @return A `StepResult` instance with `success` set to `false` and the + * provided `message`. + */ public static StepResult failure(String message) { return new StepResult(false, message); } diff --git a/src/main/java/io/hoangtien2k3/commons/model/TokenUser.java b/src/main/java/io/hoangtien2k3/commons/model/TokenUser.java index 42370d5..aef2dbe 100644 --- a/src/main/java/io/hoangtien2k3/commons/model/TokenUser.java +++ b/src/main/java/io/hoangtien2k3/commons/model/TokenUser.java @@ -21,6 +21,75 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * The `TokenUser` class represents a user token with various properties related + * to user identity and organization. It is used to encapsulate user information + * typically obtained from an authentication system or token service. + * + *

Annotations:

+ *
    + *
  • @JsonIgnoreProperties: Specifies that any properties not + * bound to this instance are ignored during deserialization. This helps handle + * cases where the JSON data contains extra properties not defined in the + * `TokenUser` class.
  • + *
  • @Data: Lombok annotation that generates getters, + * setters, equals, hashCode, and toString methods, as well as a constructor + * with all arguments and a no-args constructor.
  • + *
  • @AllArgsConstructor: Lombok annotation that generates a + * constructor with all fields as arguments.
  • + *
  • @NoArgsConstructor: Lombok annotation that generates a + * no-arguments constructor.
  • + *
  • @Builder: Lombok annotation that generates a builder + * pattern for the class, allowing fluent and immutable object creation.
  • + *
+ * + *

Fields:

+ *
    + *
  • id: A unique identifier for the user.
  • + *
  • name: The full name of the user.
  • + *
  • username: The username of the user, typically used for + * login.
  • + *
  • email: The email address of the user.
  • + *
  • individualId: An identifier for the individual + * associated with the user. Annotated with @JsonProperty("individual_id") to + * map the JSON property "individual_id" to this field.
  • + *
  • organizationId: An identifier for the organization + * associated with the user.
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * // Creating a TokenUser instance using the builder pattern
+ * TokenUser user = TokenUser.builder().id("12345").name("Hoang Tien").username("hoangtien2k3")
+ * 		.email("hoangtien2k3@gmail.com").individualId("ind-67890").organizationId("org-123").build();
+ *
+ * // Accessing properties
+ * String userId = user.getId();
+ * String userEmail = user.getEmail();
+ *
+ * // Displaying user information
+ * System.out.println("User Name: " + user.getName());
+ * System.out.println("User Email: " + user.getEmail());
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The `TokenUser` class serves as a data model for holding user information + * extracted from authentication tokens or identity services. It includes fields + * for user identification, personal details, and organizational information. + * The class is annotated with Lombok annotations to simplify the creation and + * manipulation of `TokenUser` instances. + *

+ * + *

+ * With the `@JsonIgnoreProperties` annotation, the class can handle extra + * properties in JSON data gracefully. The `@JsonProperty("individual_id")` + * annotation ensures that the JSON field "individual_id" is correctly mapped to + * the `individualId` field in the class. + *

+ */ @JsonIgnoreProperties @Data @AllArgsConstructor diff --git a/src/main/java/io/hoangtien2k3/commons/model/UserDTO.java b/src/main/java/io/hoangtien2k3/commons/model/UserDTO.java index 3aa5980..3f4d5a9 100644 --- a/src/main/java/io/hoangtien2k3/commons/model/UserDTO.java +++ b/src/main/java/io/hoangtien2k3/commons/model/UserDTO.java @@ -19,6 +19,69 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * The `UserDTO` class represents a Data Transfer Object (DTO) used for + * user-related information. It is typically used to encapsulate user data + * obtained from authentication or authorization services. + * + *

Annotations:

+ *
    + *
  • @Data: Lombok annotation that generates getters, + * setters, equals, hashCode, and toString methods, as well as a constructor + * with all arguments and a no-args constructor.
  • + *
  • @NoArgsConstructor: Lombok annotation that generates a + * no-arguments constructor.
  • + *
  • @AllArgsConstructor: Lombok annotation that generates a + * constructor with all fields as arguments.
  • + *
  • @JsonProperty: Jackson annotation used to map JSON + * properties to Java fields, ensuring the correct mapping between JSON data and + * the DTO fields.
  • + *
+ * + *

Fields:

+ *
    + *
  • id: The unique identifier of the user, mapped from the + * JSON property "sub".
  • + *
  • username: The preferred username of the user, mapped + * from the JSON property "preferred_username".
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * // Creating a UserDTO instance using the all-args constructor
+ * UserDTO user = new UserDTO("12345", "tien");
+ *
+ * // Accessing properties
+ * String userId = user.getId();
+ * String username = user.getUsername();
+ *
+ * // Displaying user information
+ * System.out.println("User ID: " + user.getId());
+ * System.out.println("Username: " + user.getUsername());
+ *
+ * // Creating a UserDTO instance using the no-args constructor and setters
+ * UserDTO anotherUser = new UserDTO();
+ * anotherUser.setId("67890");
+ * anotherUser.setUsername("hoangtien2k3");
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The `UserDTO` class is a simple data structure used to transfer user + * information between different layers of an application or between systems. It + * includes fields for a unique user ID and a preferred username. + *

+ * + *

+ * The class is annotated with Lombok annotations to simplify the creation and + * manipulation of `UserDTO` instances. The `@JsonProperty` annotations are used + * to ensure that JSON properties are correctly mapped to the Java fields, which + * is particularly useful when working with JSON data from external services or + * APIs. + *

+ */ @Data @NoArgsConstructor @AllArgsConstructor diff --git a/src/main/java/io/hoangtien2k3/commons/model/WhiteList.java b/src/main/java/io/hoangtien2k3/commons/model/WhiteList.java index f00c42a..f3845cd 100644 --- a/src/main/java/io/hoangtien2k3/commons/model/WhiteList.java +++ b/src/main/java/io/hoangtien2k3/commons/model/WhiteList.java @@ -19,6 +19,60 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * The `WhiteList` class represents the configuration for public APIs that do + * not require authentication. This class is used to define which URIs and HTTP + * methods are allowed to be accessed without authentication. + * + *

Annotations:

+ *
    + *
  • @Data: Lombok annotation that automatically generates + * getters, setters, equals, hashCode, and toString methods, along with a + * constructor with all arguments and a no-arguments constructor.
  • + *
  • @NoArgsConstructor: Lombok annotation that generates a + * no-arguments constructor.
  • + *
  • @AllArgsConstructor: Lombok annotation that generates a + * constructor with all fields as arguments.
  • + *
+ * + *

Fields:

+ *
    + *
  • uri: The URI pattern that is whitelisted and accessible + * without authentication. For example, "/public/api" or "/v1/resources".
  • + *
  • methods: A list of HTTP methods (e.g., GET, POST, PUT) + * that are allowed for the given URI. This allows specifying which HTTP methods + * are permitted for the URI. If the list is empty, all methods are allowed for + * the URI.
  • + *
+ * + *

Configuration Example:

+ * + *
{@code
+ * # In application.yml
+ * whitelist:
+ *   - uri: /public/api
+ *     methods:
+ *       - GET
+ *       - POST
+ *   - uri: /v1/resources
+ *     methods:
+ *       - GET
+ * }
+ * + *

Usage:

+ *

+ * The `WhiteList` class is used in the configuration of your application to + * define which endpoints can be accessed without authentication. This + * configuration helps in making certain APIs publicly accessible while securing + * other parts of your application. + *

+ * + *

+ * The class is typically used in conjunction with Spring Boot's configuration + * properties support, allowing you to easily inject these configurations into + * your application's security setup. + *

+ */ @Data @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/io/hoangtien2k3/commons/model/logging/HttpLogRequest.java b/src/main/java/io/hoangtien2k3/commons/model/logging/HttpLogRequest.java index 3ff4e4c..eebd448 100644 --- a/src/main/java/io/hoangtien2k3/commons/model/logging/HttpLogRequest.java +++ b/src/main/java/io/hoangtien2k3/commons/model/logging/HttpLogRequest.java @@ -18,6 +18,68 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * The `HttpLogRequest` class is a configuration class used to control whether + * HTTP request logging is enabled or disabled. + * + *

Attributes:

+ *
    + *
  • enable: A boolean flag indicating whether HTTP request + * logging is enabled. Default value is true.
  • + *
+ * + *

Constructor Details:

+ *
    + *
  • HttpLogRequest(): Default constructor that initializes + * enable to true.
  • + *
  • HttpLogRequest(boolean enable): Constructor that allows + * setting the enable flag explicitly.
  • + *
+ * + *

Usage Example:

+ * + *
+ * {
+ * 	@code
+ * 	@Configuration
+ * 	public class LoggingConfig {
+ *
+ * 		@Bean
+ * 		public HttpLogRequest httpLogRequest() {
+ * 			return new HttpLogRequest(true);
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *

Configuration Example:

+ * + *
{@code
+ * # application.yml
+ * http:
+ *   log:
+ *     enable: false
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The `HttpLogRequest` class provides a simple way to configure logging of HTTP + * requests within an application. By setting the enable attribute + * to true, you can enable detailed logging for HTTP requests. If + * set to false, logging is disabled, which can be useful for + * reducing log verbosity in production environments or when detailed request + * logging is not required. + *

+ * + *

+ * This class can be used in conjunction with other logging configuration + * classes or aspects to conditionally enable or disable HTTP request logging + * based on the application's needs. It helps in centralizing the configuration + * of logging behavior and makes it easier to manage and control logging + * settings. + *

+ */ @Data @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/io/hoangtien2k3/commons/model/logging/HttpLogResponse.java b/src/main/java/io/hoangtien2k3/commons/model/logging/HttpLogResponse.java index 0e06dd3..ed09dff 100644 --- a/src/main/java/io/hoangtien2k3/commons/model/logging/HttpLogResponse.java +++ b/src/main/java/io/hoangtien2k3/commons/model/logging/HttpLogResponse.java @@ -18,6 +18,68 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * The {@code HttpLogResponse} class is a configuration class used to manage + * whether HTTP response logging is enabled or disabled. + * + *

Attributes:

+ *
    + *
  • enable: A boolean flag indicating whether HTTP response + * logging is enabled. Default value is {@code true}.
  • + *
+ * + *

Constructor Details:

+ *
    + *
  • HttpLogResponse(): Default constructor that initializes + * {@code enable} to {@code true}.
  • + *
  • HttpLogResponse(boolean enable): Constructor that allows + * setting the {@code enable} flag explicitly.
  • + *
+ * + *

Usage Example:

+ * + *
+ * {
+ * 	@code
+ * 	@Configuration
+ * 	public class LoggingConfig {
+ *
+ * 		@Bean
+ * 		public HttpLogResponse httpLogResponse() {
+ * 			return new HttpLogResponse(true);
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *

Configuration Example:

+ * + *
{@code
+ * # application.yml
+ * http:
+ *   log:
+ *     response:
+ *       enable: false
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The {@code HttpLogResponse} class provides a mechanism to configure logging + * of HTTP responses within an application. By setting the {@code enable} + * attribute to {@code true}, detailed logging of HTTP responses is enabled. + * Setting it to {@code false} disables response logging, which can be useful to + * reduce log verbosity in production environments or when response logging is + * not necessary. + *

+ * + *

+ * This class can be used in combination with other logging configuration + * classes to control the logging behavior for HTTP responses. It centralizes + * the logging configuration and simplifies the management of logging settings + * for HTTP responses. + *

+ */ @Data @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/io/hoangtien2k3/commons/model/logging/LogField.java b/src/main/java/io/hoangtien2k3/commons/model/logging/LogField.java index 8441711..07bbf73 100644 --- a/src/main/java/io/hoangtien2k3/commons/model/logging/LogField.java +++ b/src/main/java/io/hoangtien2k3/commons/model/logging/LogField.java @@ -16,6 +16,88 @@ import lombok.*; +/** + * The `LogField` class is a data transfer object (DTO) designed to encapsulate + * various fields of logging information that can be used for structured logging + * in an application. This class is used to represent and store log data with + * detailed information about requests and responses. + * + *

Attributes:

+ *
    + *
  • traceId: A unique identifier for tracking the trace of a + * request across different services.
  • + *
  • requestId: A unique identifier for the specific request, + * often used to correlate logs for a single request.
  • + *
  • service: The name of the service that generated the log + * entry.
  • + *
  • duration: The duration of the request or operation in + * milliseconds.
  • + *
  • logType: The type of log entry (e.g., INFO, ERROR, + * DEBUG).
  • + *
  • actionType: The type of action or operation being logged + * (e.g., CREATE, UPDATE, DELETE).
  • + *
  • startTime: The start time of the operation in + * milliseconds since epoch.
  • + *
  • endTime: The end time of the operation in milliseconds + * since epoch.
  • + *
  • clientAddress: The IP address of the client making the + * request.
  • + *
  • title: A brief title or description of the log + * entry.
  • + *
  • inputs: The input data or parameters provided in the + * request.
  • + *
  • response: The response data or result returned by the + * service.
  • + *
  • result: The outcome or result of the operation (e.g., + * SUCCESS, FAILURE).
  • + *
+ * + *

Constructors:

+ *
    + *
  • LogField(): Default constructor.
  • + *
  • LogField(String traceId, String requestId, String service, Long + * duration, String logType, String actionType, Long startTime, Long endTime, + * String clientAddress, String title, String inputs, String response, String + * result): Constructor with parameters to initialize all + * attributes.
  • + *
+ * + *

Methods:

+ *
    + *
  • Getters and Setters: Methods to get and set the values + * of each attribute.
  • + *
  • Builder: Provides a builder pattern for creating + * instances of `LogField` with a fluent API.
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * LogField logField = LogField.builder().traceId("12345").requestId("67890").service("UserService").duration(150L)
+ * 		.logType("INFO").actionType("CREATE").startTime(System.currentTimeMillis() - 150)
+ * 		.endTime(System.currentTimeMillis()).clientAddress("192.168.1.1").title("User Created")
+ * 		.inputs("{\"username\":\"johndoe\"}").response("{\"status\":\"success\"}").result("SUCCESS").build();
+ *
+ * log.info("Log entry: {}", logField);
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The `LogField` class is used to structure and manage log data in a consistent + * manner. It includes fields for capturing detailed information about each log + * entry, such as trace IDs, request IDs, service names, durations, log types, + * and more. This structured approach helps in analyzing logs more effectively + * and correlating log entries across different components and services. + *

+ * + *

+ * By using the builder pattern, the `LogField` class provides a flexible and + * convenient way to create instances with various combinations of attributes. + * The class can be integrated with logging frameworks to enhance the quality + * and usability of log data. + *

+ */ @Data @NoArgsConstructor @AllArgsConstructor diff --git a/src/main/java/io/hoangtien2k3/commons/model/logging/LoggerDTO.java b/src/main/java/io/hoangtien2k3/commons/model/logging/LoggerDTO.java index 3c50d07..11cafbd 100644 --- a/src/main/java/io/hoangtien2k3/commons/model/logging/LoggerDTO.java +++ b/src/main/java/io/hoangtien2k3/commons/model/logging/LoggerDTO.java @@ -21,19 +21,155 @@ import lombok.NoArgsConstructor; import reactor.util.context.Context; +/** + * The `LoggerDTO` class represents a Data Transfer Object (DTO) designed for + * logging purposes. It encapsulates various fields related to logging + * information, including context, span details, timing, results, and more. This + * class is used to store and manage logging data for more structured and + * detailed log entries. + * + *

Attributes:

+ *
    + *
  • contextRef: An `AtomicReference` containing the current + * `Context`. This is used to manage and share logging context information + * safely across threads.
  • + *
  • newSpan: A `Span` object representing a new span in a + * distributed tracing system. This is useful for tracking the duration and + * details of specific operations in the system.
  • + *
  • service: A `String` representing the name of the service + * generating the log entry.
  • + *
  • startTime: A `Long` representing the start time of the + * operation in milliseconds since epoch.
  • + *
  • endTime: A `Long` representing the end time of the + * operation in milliseconds since epoch.
  • + *
  • result: A `String` indicating the result of the + * operation (e.g., SUCCESS, FAILURE).
  • + *
  • response: An `Object` holding the response data returned + * by the service or operation.
  • + *
  • logType: A `String` specifying the type of log entry + * (e.g., INFO, ERROR, DEBUG).
  • + *
  • actionType: A `String` describing the type of action or + * operation being logged (e.g., CREATE, UPDATE, DELETE).
  • + *
  • args: An array of `Object` holding the arguments or + * parameters passed to the operation.
  • + *
  • title: A `String` providing a brief title or description + * of the log entry.
  • + *
+ * + *

Constructors:

+ *
    + *
  • LoggerDTO(): Default constructor that initializes an + * empty instance.
  • + *
  • LoggerDTO(AtomicReference<Context> contextRef, Span + * newSpan, String service, Long startTime, Long endTime, String result, Object + * response, String logType, String actionType, Object[] args, String + * title): Constructor that initializes all attributes with provided + * values.
  • + *
+ * + *

Methods:

+ *
    + *
  • Getters and Setters: Methods to access and modify the + * values of each attribute.
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * // Create a new LoggerDTO instance with sample data
+ * LoggerDTO loggerDTO = new LoggerDTO(new AtomicReference<>(Context.current()), // Current context
+ * 		Span.current(), // Current span
+ * 		"UserService", // Service name
+ * 		System.currentTimeMillis() - 100, // Start time
+ * 		System.currentTimeMillis(), // End time
+ * 		"SUCCESS", // Result of the operation
+ * 		"{ \"status\": \"ok\" }", // Response data
+ * 		"INFO", // Log type
+ * 		"CREATE", // Action type
+ * 		new Object[]{"param1", "param2"}, // Arguments
+ * 		"User Created" // Title
+ * );
+ *
+ * // Use the LoggerDTO instance for logging
+ * log.info("Logging entry: {}", loggerDTO);
+ * }
+ * + *

Detailed Description:

+ * + *

+ * The `LoggerDTO` class is designed to hold comprehensive details related to + * logging in an application. It captures information such as context, span, + * service name, timing, results, responses, and more. This structured approach + * helps in generating detailed and organized log entries, making it easier to + * monitor and troubleshoot the application. + *

+ * + *

+ * By using `AtomicReference` for context management and `Span` for distributed + * tracing, the class supports advanced logging scenarios in multi-threaded and + * distributed environments. The `LoggerDTO` class is useful for creating + * detailed log entries with all necessary context and metadata, enhancing the + * quality and usefulness of logs. + *

+ */ @Data @NoArgsConstructor @AllArgsConstructor public class LoggerDTO { - AtomicReference contextRef; - Span newSpan; - String service; - Long startTime; - Long endTime; - String result; - Object response; - String logType; - String actionType; - Object[] args; - String title; + /** + * An `AtomicReference` containing the current `Context`, used for managing and + * sharing logging context information safely across threads. + */ + private AtomicReference contextRef; + + /** + * A `Span` object representing a new span in a distributed tracing system, + * useful for tracking the duration and details of specific operations. + */ + private Span newSpan; + + /** + * The name of the service generating the log entry. + */ + private String service; + + /** + * The start time of the operation in milliseconds since epoch. + */ + private Long startTime; + + /** + * The end time of the operation in milliseconds since epoch. + */ + private Long endTime; + + /** + * The result of the operation (e.g., SUCCESS, FAILURE). + */ + private String result; + + /** + * The response data returned by the service or operation. + */ + private Object response; + + /** + * The type of log entry (e.g., INFO, ERROR, DEBUG). + */ + private String logType; + + /** + * The type of action or operation being logged (e.g., CREATE, UPDATE, DELETE). + */ + private String actionType; + + /** + * An array of arguments or parameters passed to the operation. + */ + private Object[] args; + + /** + * A brief title or description of the log entry. + */ + private String title; } diff --git a/src/main/java/io/hoangtien2k3/commons/utils/MessageUtils.java b/src/main/java/io/hoangtien2k3/commons/utils/MessageUtils.java index b016bae..4028521 100644 --- a/src/main/java/io/hoangtien2k3/commons/utils/MessageUtils.java +++ b/src/main/java/io/hoangtien2k3/commons/utils/MessageUtils.java @@ -20,15 +20,170 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.context.i18n.LocaleContextHolder; +/** + * Utility class for handling internationalized messages. + *

+ * The {@code MessageUtils} class provides methods to retrieve localized + * messages from a resource bundle. It supports message formatting and fallback + * mechanisms for missing or erroneous messages. + *

+ *

Class Overview:

+ *

+ * The class uses {@code ResourceBundle} to fetch messages based on the provided + * locale. It formats the message using {@code MessageFormat} and handles + * exceptions to ensure that the application continues to function even if a + * message is missing or the resource bundle is misconfigured. + *

+ * + *

Fields:

+ *
    + *
  • BASE_NAME: The base name of the resource bundle, which + * is typically the name of the properties file (without the .properties + * extension) containing the messages. Defaults to "messages".
  • + *
+ * + *

Methods:

+ *
    + *
  • getMessage(String code, Locale locale): Retrieves the + * message associated with the given code and locale. If the message is missing + * or an error occurs, the code itself is returned as a fallback. + *

    + * Parameters: + *

    + *
      + *
    • code: The key for the desired message.
    • + *
    • locale: The locale to use for message retrieval.
    • + *
    + *

    + * Returns: + *

    + *
      + *
    • The localized message, or the code itself if the message is not + * found.
    • + *
    + *
  • + * + *
  • getMessage(String code, Locale locale, Object... args): + * Retrieves and formats the message associated with the given code and locale, + * using the provided arguments. + *

    + * Parameters: + *

    + *
      + *
    • code: The key for the desired message.
    • + *
    • locale: The locale to use for message retrieval.
    • + *
    • args: Arguments to format the message.
    • + *
    + *

    + * Returns: + *

    + *
      + *
    • The formatted message, or the code itself if the message is not + * found.
    • + *
    + *
  • + * + *
  • getMessage(String code): Retrieves the message + * associated with the given code, using the default locale from + * {@code LocaleContextHolder}. + *

    + * Parameters: + *

    + *
      + *
    • code: The key for the desired message.
    • + *
    + *

    + * Returns: + *

    + *
      + *
    • The localized message, or the code itself if the message is not + * found.
    • + *
    + *
  • + * + *
  • getMessage(String code, Object... args): Retrieves and + * formats the message associated with the given code, using the default locale + * from {@code LocaleContextHolder} and the provided arguments. + *

    + * Parameters: + *

    + *
      + *
    • code: The key for the desired message.
    • + *
    • args: Arguments to format the message.
    • + *
    + *

    + * Returns: + *

    + *
      + *
    • The formatted message, or the code itself if the message is not + * found.
    • + *
    + *
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * // Assuming there is a message property file named "messages.properties" with
+ * // a key "welcome.message"
+ * String message = MessageUtils.getMessage("welcome.message", Locale.US);
+ * // Output: "Welcome to our application!"
+ *
+ * // With arguments
+ * String formattedMessage = MessageUtils.getMessage("welcome.user", Locale.US, "John");
+ * // Output: "Welcome to our application, John!"
+ *
+ * // Using default locale
+ * String defaultMessage = MessageUtils.getMessage("default.message");
+ * // Output will be based on the default locale set in LocaleContextHolder
+ * }
+ * + *

Notes:

+ *

+ * The `MessageUtils` class relies on `ResourceBundle` for message retrieval. + * Ensure that the resource bundle files (e.g., `messages.properties`, + * `messages_en.properties`) are correctly placed in the classpath. The class + * also uses `LocaleContextHolder` to fetch the default locale, which should be + * properly configured in your Spring application context. + *

+ * + *

+ * Logging is performed for exceptions occurring during message retrieval, + * helping in debugging issues with missing or malformed messages. + *

+ */ @Slf4j public class MessageUtils { private static final String BASE_NAME = "messages"; + /** + * Retrieves the message associated with the given code and locale. + * + * @param code + * The key for the desired message. + * @param locale + * The locale to use for message retrieval. + * @return The localized message, or the code itself if the message is not + * found. + */ public static String getMessage(String code, Locale locale) { return getMessage(code, locale, null); } + /** + * Retrieves and formats the message associated with the given code and locale, + * using the provided arguments. + * + * @param code + * The key for the desired message. + * @param locale + * The locale to use for message retrieval. + * @param args + * Arguments to format the message. + * @return The formatted message, or the code itself if the message is not + * found. + */ public static String getMessage(String code, Locale locale, Object... args) { ResourceBundle resourceBundle = ResourceBundle.getBundle(BASE_NAME, locale); String message; @@ -43,10 +198,30 @@ public static String getMessage(String code, Locale locale, Object... args) { return message; } + /** + * Retrieves the message associated with the given code, using the default + * locale from LocaleContextHolder. + * + * @param code + * The key for the desired message. + * @return The localized message, or the code itself if the message is not + * found. + */ public static String getMessage(String code) { return getMessage(code, LocaleContextHolder.getLocale(), null); } + /** + * Retrieves and formats the message associated with the given code, using the + * default locale from LocaleContextHolder and the provided arguments. + * + * @param code + * The key for the desired message. + * @param args + * Arguments to format the message. + * @return The formatted message, or the code itself if the message is not + * found. + */ public static String getMessage(String code, Object... args) { return getMessage(code, LocaleContextHolder.getLocale(), args); } diff --git a/src/main/java/io/hoangtien2k3/commons/utils/MinioUtils.java b/src/main/java/io/hoangtien2k3/commons/utils/MinioUtils.java index f2d1290..52a716c 100644 --- a/src/main/java/io/hoangtien2k3/commons/utils/MinioUtils.java +++ b/src/main/java/io/hoangtien2k3/commons/utils/MinioUtils.java @@ -40,6 +40,138 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; +/** + * Utility class for handling internationalized messages. + *

+ * The {@code MessageUtils} class provides methods to retrieve localized + * messages from a resource bundle. It supports message formatting and fallback + * mechanisms for missing or erroneous messages. + *

+ *

Class Overview:

+ *

+ * The class uses {@code ResourceBundle} to fetch messages based on the provided + * locale. It formats the message using {@code MessageFormat} and handles + * exceptions to ensure that the application continues to function even if a + * message is missing or the resource bundle is misconfigured. + *

+ * + *

Fields:

+ *
    + *
  • BASE_NAME: The base name of the resource bundle, which + * is typically the name of the properties file (without the .properties + * extension) containing the messages. Defaults to "messages".
  • + *
+ * + *

Methods:

+ *
    + *
  • getMessage(String code, Locale locale): Retrieves the + * message associated with the given code and locale. If the message is missing + * or an error occurs, the code itself is returned as a fallback. + *

    + * Parameters: + *

    + *
      + *
    • code: The key for the desired message.
    • + *
    • locale: The locale to use for message retrieval.
    • + *
    + *

    + * Returns: + *

    + *
      + *
    • The localized message, or the code itself if the message is not + * found.
    • + *
    + *
  • + * + *
  • getMessage(String code, Locale locale, Object... args): + * Retrieves and formats the message associated with the given code and locale, + * using the provided arguments. + *

    + * Parameters: + *

    + *
      + *
    • code: The key for the desired message.
    • + *
    • locale: The locale to use for message retrieval.
    • + *
    • args: Arguments to format the message.
    • + *
    + *

    + * Returns: + *

    + *
      + *
    • The formatted message, or the code itself if the message is not + * found.
    • + *
    + *
  • + * + *
  • getMessage(String code): Retrieves the message + * associated with the given code, using the default locale from + * {@code LocaleContextHolder}. + *

    + * Parameters: + *

    + *
      + *
    • code: The key for the desired message.
    • + *
    + *

    + * Returns: + *

    + *
      + *
    • The localized message, or the code itself if the message is not + * found.
    • + *
    + *
  • + * + *
  • getMessage(String code, Object... args): Retrieves and + * formats the message associated with the given code, using the default locale + * from {@code LocaleContextHolder} and the provided arguments. + *

    + * Parameters: + *

    + *
      + *
    • code: The key for the desired message.
    • + *
    • args: Arguments to format the message.
    • + *
    + *

    + * Returns: + *

    + *
      + *
    • The formatted message, or the code itself if the message is not + * found.
    • + *
    + *
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * // Assuming there is a message property file named "messages.properties" with
+ * // a key "welcome.message"
+ * String message = MessageUtils.getMessage("welcome.message", Locale.US);
+ * // Output: "Welcome to our application!"
+ *
+ * // With arguments
+ * String formattedMessage = MessageUtils.getMessage("welcome.user", Locale.US, "Tien");
+ * // Output: "Welcome to our application, John!"
+ *
+ * // Using default locale
+ * String defaultMessage = MessageUtils.getMessage("default.message");
+ * // Output will be based on the default locale set in LocaleContextHolder
+ * }
+ * + *

Notes:

+ *

+ * The `MessageUtils` class relies on `ResourceBundle` for message retrieval. + * Ensure that the resource bundle files (e.g., `messages.properties`, + * `messages_en.properties`) are correctly placed in the classpath. The class + * also uses `LocaleContextHolder` to fetch the default locale, which should be + * properly configured in your Spring application context. + *

+ * + *

+ * Logging is performed for exceptions occurring during message retrieval, + * helping in debugging issues with missing or malformed messages. + *

+ */ @Slf4j @Component @ConditionalOnProperty(value = "minio.enabled", havingValue = "true", matchIfMissing = false) diff --git a/src/main/java/io/hoangtien2k3/commons/utils/PageUtils.java b/src/main/java/io/hoangtien2k3/commons/utils/PageUtils.java index 28ea953..1f3250f 100644 --- a/src/main/java/io/hoangtien2k3/commons/utils/PageUtils.java +++ b/src/main/java/io/hoangtien2k3/commons/utils/PageUtils.java @@ -14,7 +14,88 @@ */ package io.hoangtien2k3.commons.utils; +/** + * Utility class for pagination-related operations. + *

+ * The {@code PageUtils} class provides a static method to calculate the offset + * for pagination based on the current page number and page size. This is + * particularly useful for retrieving paginated results from a database or other + * data sources. + *

+ * + *

Class Overview:

+ *

+ * This class contains utility methods for handling pagination calculations. + * Currently, it includes a method to compute the offset given a page number and + * page size. + *

+ * + *

Methods:

+ *
    + *
  • getOffset: Calculates the offset for pagination based on + * the provided page number and page size. + *
      + *
    • Parameters: + *
        + *
      • page (Integer): The current page number. Must be greater + * than 0.
      • + *
      • size (Integer): The number of items per page. Must be + * greater than 0.
      • + *
      + *
    • + *
    • Returns: + *

      + * An integer representing the offset. If the page or + * size is null or less than or equal to 0, the method returns 0. + *

      + *
    • + *
    • Usage: + *

      + * This method can be used to calculate the starting index for a particular page + * in a paginated data set. For example, if the page size is 10 and the current + * page is 3, the method will calculate an offset of 20, which is the starting + * index for the 3rd page (page index starts at 0). + *

      + *
    • + *
    + *
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * int page = 3;
+ * int size = 10;
+ * int offset = PageUtils.getOffset(page, size);
+ * // offset will be 20, which is used to fetch records starting from index 20
+ * }
+ * + *

Notes:

+ *

+ * Ensure that the page number and page size are validated before calling the + * method. If either value is invalid (e.g., less than or equal to 0), the + * method returns 0, which might result in incorrect pagination behavior if not + * handled properly in the application logic. + *

+ */ public class PageUtils { + + /** + * Calculates the offset for pagination based on the current page number and + * page size. + *

+ * This method computes the starting index for the given page number and page + * size. The offset is calculated as (page - 1) * size. If the page or size is + * null or not positive, it returns 0. + *

+ * + * @param page + * The current page number. Must be greater than 0. + * @param size + * The number of items per page. Must be greater than 0. + * @return The calculated offset. Returns 0 if page or size is null or less than + * or equal to 0. + */ public static int getOffset(Integer page, Integer size) { if (page == null || page <= 0 || size == null || size <= 0) return 0; return (page - 1) * size; diff --git a/src/main/java/io/hoangtien2k3/commons/utils/ReactiveOAuth2Utils.java b/src/main/java/io/hoangtien2k3/commons/utils/ReactiveOAuth2Utils.java index 190fec8..45a9ca0 100644 --- a/src/main/java/io/hoangtien2k3/commons/utils/ReactiveOAuth2Utils.java +++ b/src/main/java/io/hoangtien2k3/commons/utils/ReactiveOAuth2Utils.java @@ -17,10 +17,100 @@ import org.springframework.security.oauth2.client.*; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +/** + * Utility class for creating a {@link ReactiveOAuth2AuthorizedClientManager} + * instance. + *

+ * The {@code ReactiveOAuth2Utils} class provides a static method to configure + * and instantiate a {@link ReactiveOAuth2AuthorizedClientManager} with a + * specific {@link ReactiveOAuth2AuthorizedClientProvider}. This is useful for + * managing OAuth2 clients in a reactive environment, particularly for scenarios + * involving client credentials grant types. + *

+ * + *

Class Overview:

+ *

+ * This class contains a utility method for creating and configuring a + * {@link ReactiveOAuth2AuthorizedClientManager}. The manager is responsible for + * handling OAuth2 authorized clients and their respective tokens in a reactive + * Spring application. + *

+ * + *

Methods:

+ *
    + *
  • createAuthorizedClientManager: Configures and creates an + * instance of {@link ReactiveOAuth2AuthorizedClientManager} using the provided + * client registration repository and authorized client service. + *
      + *
    • Parameters: + *
        + *
      • clientRegistrationRepository + * ({@link ReactiveClientRegistrationRepository}): Repository for managing + * client registrations. It provides information about client details such as + * client ID, client secret, and scopes.
      • + *
      • authorizedClientService + * ({@link ReactiveOAuth2AuthorizedClientService}): Service for managing + * authorized clients and their tokens. It handles storing and retrieving + * authorized client information.
      • + *
      + *
    • + *
    • Returns: + *

      + * A {@link ReactiveOAuth2AuthorizedClientManager} instance configured with the + * provided client registration repository and authorized client service. This + * manager is used to manage and authorize OAuth2 clients. + *

      + *
    • + *
    • Usage: + *

      + * This method is used to set up an authorized client manager for handling + * OAuth2 authentication in a reactive application. The manager will use client + * credentials grant type to obtain access tokens and manage OAuth2 clients. + *

      + *
    • + *
    + *
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * ReactiveClientRegistrationRepository clientRegistrationRepository = // obtain or create repository
+ * ReactiveOAuth2AuthorizedClientService authorizedClientService = // obtain or create service
+ * ReactiveOAuth2AuthorizedClientManager authorizedClientManager =
+ *         ReactiveOAuth2Utils.createAuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
+ * }
+ * + *

Notes:

+ *

+ * Ensure that the provided {@link ReactiveClientRegistrationRepository} and + * {@link ReactiveOAuth2AuthorizedClientService} are correctly configured and + * initialized before passing them to the method. This setup is crucial for + * proper OAuth2 client management and token handling. + *

+ */ public class ReactiveOAuth2Utils { + + /** + * Creates and configures a {@link ReactiveOAuth2AuthorizedClientManager} + * instance. + *

+ * This method sets up a {@link ReactiveOAuth2AuthorizedClientManager} with a + * client credentials provider, using the provided + * {@link ReactiveClientRegistrationRepository} and + * {@link ReactiveOAuth2AuthorizedClientService}. + *

+ * + * @param clientRegistrationRepository + * The repository for managing client registrations. + * @param authorizedClientService + * The service for managing authorized clients and their tokens. + * @return A configured {@link ReactiveOAuth2AuthorizedClientManager} instance. + */ public static ReactiveOAuth2AuthorizedClientManager createAuthorizedClientManager( ReactiveClientRegistrationRepository clientRegistrationRepository, ReactiveOAuth2AuthorizedClientService authorizedClientService) { + ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder() .clientCredentials() diff --git a/src/main/java/io/hoangtien2k3/commons/utils/SQLUtils.java b/src/main/java/io/hoangtien2k3/commons/utils/SQLUtils.java index c5aaa11..8a5ca33 100644 --- a/src/main/java/io/hoangtien2k3/commons/utils/SQLUtils.java +++ b/src/main/java/io/hoangtien2k3/commons/utils/SQLUtils.java @@ -16,7 +16,86 @@ import org.apache.commons.lang3.StringUtils; +/** + * Utility class for handling SQL-related operations. + *

+ * The {@code SQLUtils} class provides a static method to replace special + * characters in a string to make it suitable for use in SQL queries. This is + * particularly useful for escaping characters that have special meanings in SQL + * like `%` and `_`, which are used in wildcard patterns. + *

+ * + *

Class Overview:

+ *

+ * This class contains utility methods for SQL operations. Specifically, it + * provides a method to replace special characters in a string that can + * interfere with SQL query syntax. + *

+ * + *

Methods:

+ *
    + *
  • replaceSpecialDigit: Replaces special characters in a + * string to prevent them from being interpreted as SQL wildcards. + *
      + *
    • Parameters: + *
        + *
      • value (String): The string to be processed. It + * can be any value that might contain special characters.
      • + *
      + *
    • + *
    • Returns: + *

      + * The processed string with special characters replaced. Specifically, the + * percent sign (`%`) and underscore (`_`) are replaced with their escaped + * versions (`\%` and `\_` respectively). If the input string is empty or + * null, an empty string is returned. + *

      + *
    • + *
    • Usage: + *

      + * This method is used to escape special characters in SQL queries to prevent + * them from being interpreted as wildcards. For example, if you need to include + * a literal percent sign in your SQL query, you would use this method to + * replace `%` with `\%`. + *

      + *
    • + *
    + *
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * String rawValue = "100%";
+ * String safeValue = SQLUtils.replaceSpecialDigit(rawValue);
+ * // safeValue will be "100\\%"
+ * }
+ * + *

Notes:

+ *

+ * This utility method is designed to handle basic escaping of special + * characters for SQL queries. Depending on the SQL database being used, + * additional escaping or handling might be required for other special + * characters or SQL injection prevention. + *

+ */ public class SQLUtils { + + /** + * Replaces special characters in a string to prevent them from being + * interpreted as SQL wildcards. + *

+ * Specifically, the percent sign (`%`) and underscore (`_`) are replaced with + * their escaped versions (`\%` and `\_` respectively). This is useful for + * ensuring that these characters are treated as literals in SQL queries rather + * than wildcards. + *

+ * + * @param value + * The string to be processed. Can be null or empty. + * @return The processed string with special characters replaced. Returns an + * empty string if the input is null or empty. + */ public static String replaceSpecialDigit(String value) { if (!StringUtils.isEmpty(value)) { value = value.replace("%", "\\%").replace("_", "\\_"); diff --git a/src/main/java/io/hoangtien2k3/commons/utils/StreamUtil.java b/src/main/java/io/hoangtien2k3/commons/utils/StreamUtil.java index a5105ee..42a0706 100644 --- a/src/main/java/io/hoangtien2k3/commons/utils/StreamUtil.java +++ b/src/main/java/io/hoangtien2k3/commons/utils/StreamUtil.java @@ -18,12 +18,91 @@ import java.io.InputStream; import lombok.extern.slf4j.Slf4j; +/** + * Utility class for handling stream operations. + *

+ * The {@code StreamUtil} class provides a static method for converting an + * {@link InputStream} into a byte array. This is useful for reading data from a + * stream and processing it as a byte array. + *

+ * + *

Class Overview:

+ *

+ * This class contains utility methods for working with streams. Specifically, + * it provides a method to read data from an {@code InputStream} and convert it + * into a byte array. + *

+ * + *

Methods:

+ *
    + *
  • streamToByteArray: Reads all the bytes from an + * {@code InputStream} and returns them as a byte array. + *
      + *
    • Parameters: + *
        + *
      • inStream (InputStream): The input stream to be + * read.
      • + *
      + *
    • + *
    • Returns: + *

      + * A byte array containing the data read from the input stream. If an error + * occurs during reading, an empty byte array is returned. + *

      + *
    • + *
    • Usage: + *

      + * This method is used to read data from an input stream into a byte array. This + * is useful when you need to process or manipulate the entire contents of a + * stream as a byte array. + *

      + *
    • + *
    • Exceptions: + *

      + * If an error occurs during the reading process (e.g., an {@link Exception} is + * thrown), an error message is logged, and an empty byte array is returned. + *

      + *
    • + *
    + *
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * InputStream inputStream = new FileInputStream("path/to/file");
+ * byte[] data = StreamUtil.streamToByteArray(inputStream);
+ * // data now contains the byte array representation of the file
+ * }
+ * + *

Notes:

+ *

+ * The buffer size used for reading the stream is 100 bytes. If the stream + * contains more data, it will be read in chunks until the end of the stream is + * reached. The method ensures that all data from the input stream is captured + * in the resulting byte array. + *

+ */ @Slf4j public class StreamUtil { + + /** + * Converts an {@link InputStream} into a byte array. + *

+ * This method reads data from the input stream in chunks and writes it to a + * {@link ByteArrayOutputStream}. Once all data has been read, it is converted + * into a byte array and returned. + *

+ * + * @param inStream + * The input stream to be read. Must not be null. + * @return A byte array containing the data read from the input stream. If an + * error occurs, an empty byte array is returned. + */ public static byte[] streamToByteArray(InputStream inStream) { ByteArrayOutputStream swapStream = new ByteArrayOutputStream(); byte[] buff = new byte[100]; - int rc = 0; + int rc; byte[] in_b = new byte[] {}; try { while ((rc = inStream.read(buff, 0, 100)) > 0) { diff --git a/src/main/java/io/hoangtien2k3/commons/utils/TruncateUtils.java b/src/main/java/io/hoangtien2k3/commons/utils/TruncateUtils.java index e87079f..d69b0d0 100644 --- a/src/main/java/io/hoangtien2k3/commons/utils/TruncateUtils.java +++ b/src/main/java/io/hoangtien2k3/commons/utils/TruncateUtils.java @@ -20,8 +20,153 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.util.MultiValueMap; +/** + * Utility class for truncating strings and handling serialization of objects. + *

+ * The {@code TruncateUtils} class provides methods for truncating a string to + * ensure it does not exceed a specified byte length when encoded in UTF-8. It + * also includes methods for serializing objects and handling form data + * truncation. + *

+ * + *

Class Overview:

+ *

+ * This class contains methods to truncate strings based on byte length, + * serialize objects to strings, and handle form data truncation. + *

+ * + *

Methods:

+ *
    + *
  • truncate: Truncates a string to fit within a specified + * byte length when encoded in UTF-8. + *
      + *
    • Parameters: + *
        + *
      • {@code s} ({@code String}): The string to be truncated. Can be + * {@code null} or empty.
      • + *
      • {@code maxByte} ({@code int}): The maximum byte length for the truncated + * string.
      • + *
      + *
    • + *
    • Returns: + *

      + * A truncated version of the input string if it exceeds the specified byte + * length, otherwise the original string. + *

      + *
    • + *
    • Exceptions: + *

      + * Logs an error if an exception occurs during the truncation process. Returns + * the original string if an error occurs. + *

      + *
    • + *
    + *
  • + *
  • truncateBody: Truncates a string to fit within a + * specified byte length when encoded in UTF-8. + *
      + *
    • Parameters: + *
        + *
      • {@code s} ({@code String}): The string to be truncated.
      • + *
      • {@code maxByte} ({@code int}): The maximum byte length for the truncated + * string.
      • + *
      + *
    • + *
    • Returns: + *

      + * A truncated version of the input string if it exceeds the specified byte + * length, otherwise the original string. + *

      + *
    • + *
    • Notes: + *

      + * This method calculates the number of bytes required for each character in the + * string to ensure that the truncation does not break multi-byte characters. + *

      + *
    • + *
    + *
  • + *
  • truncateBody(Object responseBody): Serializes an object + * to a JSON string and truncates it. + *
      + *
    • Parameters: + *
        + *
      • {@code responseBody} ({@code Object}): The object to be serialized and + * truncated.
      • + *
      + *
    • + *
    • Returns: + *

      + * A JSON representation of the object, truncated if necessary. If serialization + * fails, returns a placeholder string. + *

      + *
    • + *
    • Exceptions: + *

      + * Logs an error if an exception occurs during serialization. Returns a + * placeholder string if an error occurs. + *

      + *
    • + *
    + *
  • + *
  • truncateBody(MultiValueMap formData): + * Truncates and concatenates form data. + *
      + *
    • Parameters: + *
        + *
      • {@code formData} ({@code MultiValueMap}): The form data + * to be truncated and concatenated.
      • + *
      + *
    • + *
    • Returns: + *

      + * A concatenated string representation of the form data, with values truncated + * as necessary. + *

      + *
    • + *
    + *
  • + *
+ * + *

Usage Example:

+ * + *
{@code
+ * String longString = "A very long string that needs to be truncated...";
+ * String truncatedString = TruncateUtils.truncate(longString, 50);
+ * // truncatedString will contain the first 50 bytes of the original string
+ *
+ * Object responseObject = new SomeObject();
+ * String jsonString = TruncateUtils.truncateBody(responseObject);
+ * // jsonString will contain the JSON representation of responseObject
+ * }
+ * + *

Notes:

+ *

+ * The UTF-8 byte length calculation considers multi-byte characters to ensure + * that truncation does not cut characters in the middle. The class uses + * {@code ObjectMapper} for serialization of objects and handles exceptions + * gracefully by logging errors and returning default values when necessary. + *

+ */ @Slf4j public class TruncateUtils { + + /** + * Truncates a string to fit within a specified byte length when encoded in + * UTF-8. + *

+ * This method first checks if the string is null or empty, and if so, returns + * it as is. If the string is longer than the specified byte length, it is + * truncated while preserving multi-byte characters. + *

+ * + * @param s + * The string to be truncated. Can be null or empty. + * @param maxByte + * The maximum byte length for the truncated string. + * @return A truncated version of the input string if it exceeds the specified + * byte length, otherwise the original string. + */ public static String truncate(String s, int maxByte) { try { if (DataUtil.isNullOrEmpty(s)) { @@ -38,6 +183,22 @@ public static String truncate(String s, int maxByte) { return s; } + /** + * Truncates a string to fit within a specified byte length when encoded in + * UTF-8. + *

+ * This method iterates over the characters in the string, calculating the byte + * length for each character and truncating the string when the total byte + * length exceeds the specified limit. + *

+ * + * @param s + * The string to be truncated. + * @param maxByte + * The maximum byte length for the truncated string. + * @return A truncated version of the input string if it exceeds the specified + * byte length, otherwise the original string. + */ public static String truncateBody(String s, int maxByte) { int b = 0; for (int i = 0; i < s.length(); i++) { @@ -69,6 +230,20 @@ public static String truncateBody(String s, int maxByte) { return s; } + /** + * Serializes an object to a JSON string and truncates it to fit within a + * specified byte length. + *

+ * This method uses Jackson's `ObjectMapper` to convert the object to a JSON + * string and then truncates it. If serialization fails, a placeholder string is + * returned. + *

+ * + * @param responseBody + * The object to be serialized and truncated. + * @return A JSON representation of the object, truncated if necessary. Returns + * a placeholder string if serialization fails. + */ public static String truncateBody(Object responseBody) { ObjectMapper objectMapper = new ObjectMapper(); try { @@ -79,11 +254,21 @@ public static String truncateBody(Object responseBody) { } } + /** + * Truncates and concatenates form data. + *

+ * This method iterates over the entries in a {@link MultiValueMap}, truncates + * the values, and concatenates them into a single string representation. + *

+ * + * @param formData + * The form data to be truncated and concatenated. + */ private String truncateBody(MultiValueMap formData) { StringBuilder messageResponse = new StringBuilder(); Set keys = formData.keySet(); for (String key : keys) { - messageResponse.append(key + ":" + truncateBody(formData.get(key))); + messageResponse.append(key).append(":").append(truncateBody(formData.get(key))); } return messageResponse.toString(); }