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 @@
+ * 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.
+ *
+ * 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`.
+ *
+ * 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}).
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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".
+ *
+ * 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:
+ *
+ *
+ * The class uses Spring AOP and relies on two pointcuts to identify which
+ * methods should be logged:
+ *
+ * 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:
+ *
+ *
+ * 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:
+ *
+ * This class is annotated with `@Component` to be managed by the Spring
+ * container and `@RequiredArgsConstructor` to inject required dependencies
+ * automatically.
+ *
+ *
+ * Example of usage:
+ *
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ * Example Usage:
+ *
+ * {@code
+ * @LogPerformance(logType = "API_CALL", actionType = "FETCH_DATA", logInput = true, logOutput = true, title = "Fetching Data")
+ * public Mono
+ *
+ * Retention and Target:
+ *
+ * {@code
+ * Cache
+ *
+ * @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.
+ *
+ *
+ *
+ *
+ *
+ * {@code
+ * @LogPerformance
+ * public void someMethod() {
+ * // method implementation
+ * }
+ * }
+ *
+ *
+ *
+ *
+ *
+ *
+ * {@code
+ * @LogPerformance(logOutput = true, logInput = true, logType = "MyLogType", actionType = "MyActionType")
+ * public Mono
+ *
+ * @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(
AtomicReferenceExample 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
+ * }
+ * }
+ *
+ *
+ * Thread Safety:
+ *
+ * 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+ * 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+ * 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+ * 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. + *
+ * + *
+ * 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.
+ *
+ * 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. + *
+ *false
true
true
boolean
and Boolean
+ * types.JavaTimeModule
and custom
+ * SimpleModule
.+ * A new instance of {@link ObjectMapper} configured to ignore unknown + * properties and accept single values as arrays. + *
+ *false
true
+ * 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. + *
+ *false
true
NON_NULL
Boolean
.
+ * p
(JsonParser
): The parser to read JSON
+ * content.ctxt
(DeserializationContext
): The context for
+ * deserialization.
+ * A Boolean
value based on the input JSON text.
+ *
{@code + * ObjectMapper mapper = ObjectMapperFactory.getInstance(); + * MyClass obj = mapper.readValue(jsonString, MyClass.class); + * }+ * + *
+ * 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+ * 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. + *
+ * + *+ * This class provides a static method to retrieve {@link Unmarshaller} + * instances, which are cached for efficiency. + *
+ * + *clz
(Class
): The class for which the
+ * {@link Unmarshaller} is to be created or retrieved.
+ * An {@link Unmarshaller} instance for the specified class, or
+ * null
if an error occurs during creation.
+ *
JAXBException
: Thrown if an error occurs during the creation
+ * of the {@link Unmarshaller}.Map
that
+ * caches {@link Unmarshaller} instances.{@code + * Unmarshaller unmarshaller = UnmarshallerFactory.getInstance(MyClass.class); + * MyClass myObject = (MyClass) unmarshaller.unmarshal(new StringReader(xmlData)); + * }+ * + *
+ * 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+ * 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: + *
+ *+ * 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+ * 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+ * 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+ * The performance data is logged using a dedicated logger (`perfLogger`), and + * request/response data is logged using a separate logger (`reqResLogger`). + * + *
+ * { + * @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); + * } + * } + * } + *+ * + *
{@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. + * + *
+ * The filter can handle large response bodies by configuring the maximum + * in-memory size, and it supports both Mono and Flux types of responses. + * + *
+ * { + * @code + * @Configuration + * public class WebFilterConfig { + * + * @Bean + * @Profile("!prod") + * public ResponseLogFilter responseLogFilter() { + * return new ResponseLogFilter(); + * } + * } + * } + *+ * + *
{@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. + * + *
+ * { + * @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()); + * } + * } + * } + *+ * + *
{@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. + * + *
+ * { + * @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()); + * } + * } + * } + *+ * + *
{@code + * # application.yml + * monitoring: + * isEnable: true + * meterRegistry: com.example.CustomMeterRegistry + * }+ * + *
+ * 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. + * + *+ * { + * @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()); + * } + * } + * } + *+ * + *
{@code + * # application.yml + * pool: + * maxSize: 3000 + * maxPendingAcquire: 5000 + * }+ * + *
+ * 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. + * + *+ * { + * @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()); + * } + * } + * } + * } + *+ * + *
{@code + * # application.yml + * proxy: + * enable: true + * httpHost: "http-proxy.example.com" + * httpPort: 8080 + * httpsHost: "https-proxy.example.com" + * httpsPort: 8443 + * }+ * + *
+ * 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. + * + *+ * { + * @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); + * }); + * } + * } + * } + *+ * + *
{@code + * # application.yml + * retry: + * isEnable: true + * count: 3 + * methods: + * - GET + * - POST + * exceptions: + * - java.net.ConnectException + * - java.net.SocketTimeoutException + * }+ * + *
+ * 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. + * + *+ * { + * @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())))); + * } + * } + * } + *+ * + *
{@code + * # application.yml + * timeout: + * read: 180000 + * connection: 500 + * }+ * + *
+ * 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. + * + *+ * { + * @code + * @Configuration + * public class WebClientConfig { + * + * @Bean + * public WebClient.Builder webClientBuilder() { + * List+ * + *obfuscateHeaders = List.of("Authorization", "Cookie"); + * return WebClient.builder().filter(new WebClientLoggingFilter(obfuscateHeaders)); + * } + * } + * } + *
{@code + * # application.yml + * webclient: + * logging: + * obfuscate-headers: + * - Authorization + * - Cookie + * }+ * + *
+ * 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. + * + *+ * { + * @code + * @Configuration + * public class WebClientConfig { + * + * @Bean + * public WebClient.Builder webClientBuilder(MeterRegistry meterRegistry) { + * return WebClient.builder().filter(new WebClientMonitoringFilter(meterRegistry)); + * } + * } + * } + *+ * + *
{@code + * # application.yml + * metrics: + * webclient: + * enabled: true + * }+ * + *
+ * 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. + * + *+ * { + * @code + * @Configuration + * public class WebClientConfig { + * + * @Bean + * public WebClient.Builder webClientBuilder(RetryProperties retryProperties) { + * return WebClient.builder().filter(new WebClientRetryHandler(retryProperties)); + * } + * } + * } + *+ * + *
{@code + * # application.yml + * retry: + * enable: true + * count: 3 + * methods: + * - GET + * - PUT + * exceptions: + * - java.net.ConnectException + * - java.net.SocketTimeoutException + * }+ * + *
+ * 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. + * + *{@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(); + * }
+ * 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+ * 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. + *
+ * + *+ * 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. + *
+ * + *+ * 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. + *
+ * + *+ * { + * @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(); + * } + *
+ * 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+ * 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. + *
+ * + *{@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()); + * } + * }+ * + *
+ * 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. + * + *{@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()); + * }+ * + *
+ * 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. + * + *{@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"); + * }+ * + *
+ * 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. + * + *{@code + * # In application.yml + * whitelist: + * - uri: /public/api + * methods: + * - GET + * - POST + * - uri: /v1/resources + * methods: + * - GET + * }+ * + *
+ * 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. + * + *true
.enable
to true
.enable
flag explicitly.+ * { + * @code + * @Configuration + * public class LoggingConfig { + * + * @Bean + * public HttpLogRequest httpLogRequest() { + * return new HttpLogRequest(true); + * } + * } + * } + *+ * + *
{@code + * # application.yml + * http: + * log: + * enable: false + * }+ * + *
+ * 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. + * + *+ * { + * @code + * @Configuration + * public class LoggingConfig { + * + * @Bean + * public HttpLogResponse httpLogResponse() { + * return new HttpLogResponse(true); + * } + * } + * } + *+ * + *
{@code + * # application.yml + * http: + * log: + * response: + * enable: false + * }+ * + *
+ * 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. + * + *{@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); + * }+ * + *
+ * 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. + * + *{@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); + * }+ * + *
+ * 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+ * 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. + *
+ *+ * 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. + *
+ * + *+ * Parameters: + *
+ *+ * Returns: + *
+ *+ * Parameters: + *
+ *+ * Returns: + *
+ *+ * Parameters: + *
+ *+ * Returns: + *
+ *+ * Parameters: + *
+ *+ * Returns: + *
+ *{@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 + * }+ * + *
+ * 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. + *
+ *+ * 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. + *
+ * + *+ * Parameters: + *
+ *+ * Returns: + *
+ *+ * Parameters: + *
+ *+ * Returns: + *
+ *+ * Parameters: + *
+ *+ * Returns: + *
+ *+ * Parameters: + *
+ *+ * Returns: + *
+ *{@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 + * }+ * + *
+ * 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. + *
+ * + *+ * 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. + *
+ * + *page
(Integer): The current page number. Must be greater
+ * than 0.size
(Integer): The number of items per page. Must be
+ * greater than 0.
+ * An integer representing the offset. If the page
or
+ * size
is null or less than or equal to 0, the method returns 0.
+ *
+ * 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). + *
+ *{@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 + * }+ * + *
+ * 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. + *
+ * + *+ * 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. + *
+ * + *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.+ * 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. + *
+ *+ * 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. + *
+ *{@code + * ReactiveClientRegistrationRepository clientRegistrationRepository = // obtain or create repository + * ReactiveOAuth2AuthorizedClientService authorizedClientService = // obtain or create service + * ReactiveOAuth2AuthorizedClientManager authorizedClientManager = + * ReactiveOAuth2Utils.createAuthorizedClientManager(clientRegistrationRepository, authorizedClientService); + * }+ * + *
+ * 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. + *
+ * + *+ * 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. + *
+ * + *value
(String
): The string to be processed. It
+ * can be any value that might contain special characters.
+ * 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.
+ *
+ * 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 `\%`. + *
+ *{@code + * String rawValue = "100%"; + * String safeValue = SQLUtils.replaceSpecialDigit(rawValue); + * // safeValue will be "100\\%" + * }+ * + *
+ * 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 benull
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. + *
+ * + *+ * 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. + *
+ * + *inStream
(InputStream
): The input stream to be
+ * read.+ * A byte array containing the data read from the input stream. If an error + * occurs during reading, an empty byte array is returned. + *
+ *+ * 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. + *
+ *+ * 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. + *
+ *{@code + * InputStream inputStream = new FileInputStream("path/to/file"); + * byte[] data = StreamUtil.streamToByteArray(inputStream); + * // data now contains the byte array representation of the file + * }+ * + *
+ * 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 benull
.
+ * @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. + *
+ * + *+ * This class contains methods to truncate strings based on byte length, + * serialize objects to strings, and handle form data truncation. + *
+ * + *+ * A truncated version of the input string if it exceeds the specified byte + * length, otherwise the original string. + *
+ *+ * Logs an error if an exception occurs during the truncation process. Returns + * the original string if an error occurs. + *
+ *+ * A truncated version of the input string if it exceeds the specified byte + * length, otherwise the original string. + *
+ *+ * 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. + *
+ *+ * A JSON representation of the object, truncated if necessary. If serialization + * fails, returns a placeholder string. + *
+ *+ * Logs an error if an exception occurs during serialization. Returns a + * placeholder string if an error occurs. + *
+ *+ * A concatenated string representation of the form data, with values truncated + * as necessary. + *
+ *{@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 + * }+ * + *
+ * 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 benull
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