From cb1e1d1892cfa89b5df2de9bed181d792005a16e Mon Sep 17 00:00:00 2001 From: AlexBob Date: Thu, 5 Sep 2024 18:15:32 +0800 Subject: [PATCH] Refactor utility classes for improved query, JSON, and error handling: * ParamSql updated for clearer SQL parameter structure and documentation. * CriteriaUtils extended with pagination, sorting utilities, and parameter building for dynamic SQL. * Introduced JsonException to manage JSON processing issues with detailed status messages. * Enhanced QueryJson for constructing JSON path-based queries and sorting. * BeanUtils upgraded with JSON path bean conversion, cache key gen, and advanced copying features. * RestServerException now supports custom error codes and detailed messaging. These refinements optimize data manipulation, error management, and query dynamics. --- .../boot/commons/exception/JsonException.java | 28 ++++- .../exception/RestServerException.java | 59 +++++++++- .../plate/boot/commons/utils/BeanUtils.java | 103 +++++++++++++++++- .../commons/utils/query/CriteriaUtils.java | 90 ++++++++++++++- .../boot/commons/utils/query/ParamSql.java | 28 ++++- .../boot/commons/utils/query/QueryJson.java | 55 +++++++++- 6 files changed, 357 insertions(+), 6 deletions(-) diff --git a/boot/platform/src/main/java/com/plate/boot/commons/exception/JsonException.java b/boot/platform/src/main/java/com/plate/boot/commons/exception/JsonException.java index 6f753d61..af39f90e 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/exception/JsonException.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/exception/JsonException.java @@ -3,18 +3,44 @@ import java.io.IOException; /** - * @author Alex bob + * Exception class specifically designed to handle JSON processing errors, extending {@link RestServerException}. + * This exception is typically thrown when there is an issue with parsing or generating JSON data. */ public class JsonException extends RestServerException { + /** + * Constructs a new {@code JsonException} instance initialized with a default status code of 500 and a predefined error message, + * wrapping the provided {@link IOException} which represents a JSON processing error. + * + * @param jsonProcessingException The {@link IOException} that was encountered during JSON processing, providing specifics about the processing failure. + */ public JsonException(IOException jsonProcessingException) { this(500, "Json processing exception", jsonProcessingException); } + /** + * Constructs a new {@code JsonException} with a specified status code, error message, and additional details. + * This exception is typically utilized to wrap issues encountered during JSON processing, adding a layer of specificity + * over the broader {@link RestServerException}. + * + * @param status The HTTP status code representing the type of error occurred. This helps in categorizing the exception. + * @param message A human-readable description of the error, providing context about what went wrong during JSON processing. + * @param msg An optional object containing further information or metadata related to the error, which can assist in diagnosing the issue. + */ public JsonException(int status, String message, Object msg) { super(status, message, msg); } + /** + * Constructs and returns a new {@code JsonException} instance wrapping the provided {@link IOException}, + * which is indicative of a JSON processing error. This method is a convenience factory for creating + * {@code JsonException} objects without needing to explicitly reference the constructor arguments. + * + * @param jsonProcessingException The {@link IOException} that occurred during JSON processing, + * providing details about the processing error. + * @return A new instance of {@code JsonException} initialized with the given {@code IOException} + * as the cause, carrying a default status code and message indicative of a JSON processing failure. + */ public static JsonException withError(IOException jsonProcessingException) { return new JsonException(jsonProcessingException); } diff --git a/boot/platform/src/main/java/com/plate/boot/commons/exception/RestServerException.java b/boot/platform/src/main/java/com/plate/boot/commons/exception/RestServerException.java index d171366b..e40b4dd8 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/exception/RestServerException.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/exception/RestServerException.java @@ -6,25 +6,82 @@ import java.io.Serializable; /** - * @author Alex bob + * Represents a custom exception class for handling REST server errors, which includes an error message, a status code, + * and additional details. This exception extends {@link RuntimeException} and implements {@link Serializable} to support + * serialization when transmitted across networks or persisted. + *

+ * It provides factory methods to conveniently create instances with predefined or custom error messages and codes, + * facilitating standardization of error responses in a RESTful API context. + * + *

Features:

+ * */ @Data @EqualsAndHashCode(callSuper = true) public class RestServerException extends RuntimeException implements Serializable { + /** + * Encapsulates additional metadata or details about the error condition. + * This field can hold any type of object, enabling the transmission of structured + * information alongside the exception, such as maps, lists, or custom objects that + * provide context for the error scenario. + */ protected Object msg; + /** + * The error code associated with this {@link RestServerException}. + * This field represents a custom error code that provides more granular information + * about the specific error scenario beyond standard HTTP status codes. + * It is intended for use in identifying and differentiating between various error conditions. + */ protected int code; + /** + * Constructs a new instance of {@code RestServerException} with specified error code, message, and additional details. + * This exception is intended for conveying REST server-side error conditions, providing a more granular error code + * alongside a standard exception message and an optional object that can carry detailed contextual information. + * + * @param code The custom error code associated with the exception. This can be used to differentiate between + * various error scenarios beyond the standard HTTP status codes. + * @param message The human-readable error message explaining the exception circumstances. Should be concise and informative. + * @param msg An optional object containing additional metadata or details about the error. Can be any type, + * facilitating the passing of structured error information (e.g., maps, lists, or domain-specific objects). + */ public RestServerException(int code, String message, Object msg) { super(message); this.msg = msg; this.code = code; } + /** + * Creates a new instance of {@code RestServerException} with a predefined HTTP status code of 500 (Internal Server Error), + * a custom message, and additional details encapsulated in the {@code msg} parameter. + * This method serves as a convenience factory for generating exceptions that indicate a generic server error + * along with specific contextual information. + * + * @param message A descriptive message explaining the reason for the exception. + * @param msg An arbitrary object containing additional details about the error. This can be used to provide more + * extensive error context or metadata. + * @return A new instance of {@code RestServerException} initialized with the provided message, a status code of 500, + * and the additional details object. + */ public static RestServerException withMsg(String message, Object msg) { return withMsg(500, message, msg); } + /** + * Creates a new instance of {@code RestServerException} with a specified error code, message, and additional details. + * This static factory method allows for customization of the error response by providing a unique error code and + * a message along with an arbitrary object that can contain further information about the error condition. + * + * @param code The custom error code to identify the specific error scenario. This code supplements the HTTP status code. + * @param message The error message describing the exception's nature. Should be informative for debugging purposes. + * @param msg An optional object holding additional metadata or details related to the error. Can be any type. + * @return A new instance of {@code RestServerException} initialized with the provided code, message, and additional details. + */ public static RestServerException withMsg(int code, String message, Object msg) { return new RestServerException(code, message, msg); } diff --git a/boot/platform/src/main/java/com/plate/boot/commons/utils/BeanUtils.java b/boot/platform/src/main/java/com/plate/boot/commons/utils/BeanUtils.java index defb53da..b23bb329 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/utils/BeanUtils.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/utils/BeanUtils.java @@ -27,7 +27,9 @@ import java.util.StringJoiner; /** - * @author Alex bob + * Converts a JavaBean object into a Map representation. + * Each property of the bean is represented as a key-value pair in the map, + * with the property name as the key and its corresponding value as the value. */ @Log4j2 @Component @@ -46,6 +48,21 @@ public final class BeanUtils implements InitializingBean { } } + /** + * Converts a JSON node at a specified JSON Path into an object of the provided class type. + *

+ * This method takes a JsonNode, a JSON Path string, and a target class type, then navigates to the + * JSON node located at the specified path within the input JSON. If the node exists, it is converted + * into an instance of the target class. If the path does not exist or conversion fails, exceptions are thrown. + * + * @param The type of the object to be returned. + * @param json The JsonNode from which to extract data. + * @param path A comma-separated string representing the JSON Path to the desired node. + * @param clazz The class of the type that the JSON node should be converted into. + * @return An instance of the specified class containing the data from the JSON node at the given path. + * @throws JsonException If the JSON Pointer path does not exist in the JSON structure, + * or if there is an issue converting the JsonNode to the target class. + */ public static T jsonPathToBean(JsonNode json, String path, Class clazz) { try { String[] paths = StringUtils.commaDelimitedListToStringArray(path); @@ -65,6 +82,14 @@ public static T jsonPathToBean(JsonNode json, String path, Class clazz) { } } + /** + * Converts the given object into a byte array using JSON serialization. + * + * @param The type of the object to be serialized. + * @param object The object instance to be converted into bytes. + * @return A byte array representing the serialized form of the input object. + * @throws JsonException If the object cannot be serialized into JSON. + */ public static byte[] objectToBytes(T object) { try { return ContextUtils.OBJECT_MAPPER.writeValueAsBytes(object); @@ -73,11 +98,26 @@ public static byte[] objectToBytes(T object) { } } + /** + * Generates a cache key based on the hash codes of the provided objects. + * This method is useful for creating unique keys for caching purposes when the key is derived from multiple parameters. + * + * @param objects A variable number of objects whose hash codes will be combined to form the cache key. + * @return A string representation of the combined hash code, serving as a unique cache key. + */ public static String cacheKey(Object... objects) { int hashCode = Objects.hash(objects); return String.valueOf(hashCode); } + /** + * Inserts an object into the specified cache if its size does not exceed the maximum allowed in-memory size. + * + * @param cache The cache instance where the object will be stored. + * @param cacheKey The key under which the object will be stored in the cache. + * @param obj The object to be cached. Its size will be evaluated against the maximum allowed size. + * If the object exceeds the limit, it will not be cached and a warning will be logged. + */ public static void cachePut(Cache cache, String cacheKey, Object obj) { DataSize objectSize = getBeanSize(obj); if (objectSize.toBytes() > MAX_IN_MEMORY_SIZE.toBytes()) { @@ -88,6 +128,15 @@ public static void cachePut(Cache cache, String cacheKey, Object obj) { cache.put(cacheKey, obj); } + /** + * Calculates the size of the given Java bean by serializing it into a byte array and measuring its length. + * This method provides an estimate of the space the object occupies when serialized. + * + * @param obj The Java bean object whose size is to be calculated. Must not be null. + * @return The size of the bean as a {@link DataSize} object, representing the size in a human-readable format. + * If the object is empty or serialization fails, returns a DataSize of 0 bytes. + * @throws IllegalArgumentException If the provided object is null, since null cannot be sized. + */ public static DataSize getBeanSize(Object obj) { if (ObjectUtils.isEmpty(obj)) { log.warn("Object is empty,This object not null."); @@ -104,16 +153,41 @@ public static DataSize getBeanSize(Object obj) { } } + /** + * Copies the properties from the source object to a new instance of the specified target class. + * This method utilizes Spring's BeanUtils to perform the deep copy, allowing for copying nested properties as well. + * + * @param The type of the target class. + * @param source The source object from which properties are to be copied. + * @param clazz The class of the target object to be instantiated and populated with the source's properties. + * @return A new instance of the target class with properties copied from the source object. + * @throws IllegalArgumentException If the source is null or the clazz cannot be instantiated. + */ public static T copyProperties(Object source, Class clazz) { T target = org.springframework.beans.BeanUtils.instantiateClass(clazz); BeanUtils.copyProperties(source, target, true); return target; } + /** + * Copies the properties from the source object to the target object. + * + * @param source The source object whose properties are to be copied. + * @param target The target object where the properties from the source will be set. + */ public static void copyProperties(Object source, Object target) { BeanUtils.copyProperties(source, target, false); } + /** + * Copies properties from the source object to the target object. + * Optionally, properties with null values can be ignored during the copy process. + * + * @param source The source object from which properties are to be copied. + * @param target The target object to which properties are to be copied. + * @param ignoreNullValue If true, properties with null values in the source object will not be copied to the target object. + * If false, all properties including those with null values are copied. + */ public static void copyProperties(Object source, Object target, boolean ignoreNullValue) { Map targetMap = BeanUtils.beanToMap(source); String[] nullKeys = new String[0]; @@ -127,14 +201,41 @@ public static void copyProperties(Object source, Object target, boolean ignoreNu } } + /** + * Converts a JavaBean object into a Map representation. + * + * @param The type of the bean. + * @param bean The JavaBean object to be converted. + * @return A Map containing the properties of the JavaBean with keys as String and values as Object. + * The returned Map reflects the properties of the input bean including any nested beans. + */ public static Map beanToMap(T bean) { return BeanUtils.beanToMap(bean, false); } + /** + * Converts a JavaBean object into a Map, with keys representing the property names + * and values representing the corresponding property values. + * + * @param The type of the bean to convert. + * @param bean The JavaBean object to be converted into a Map. + * @param ignoreNullValue If true, properties with null values will not be included in the Map. + * @return A Map where each key-value pair corresponds to a property name and its value from the input bean. + * If ignoreNullValue is true, properties with null values are excluded. + */ public static Map beanToMap(T bean, final boolean ignoreNullValue) { return BeanUtils.beanToMap(bean, false, ignoreNullValue); } + /** + * Converts a JavaBean object into a Map, with options to transform field names to snake_case and ignore null values. + * + * @param The type of the bean to convert. + * @param bean The JavaBean object to be converted into a Map. + * @param isToUnderlineCase If true, converts camelCase keys in the Map to snake_case. Defaults to false. + * @param ignoreNullValue If true, excludes keys with null values from the resulting Map. Defaults to false. + * @return A Map representation of the input JavaBean, optionally with keys transformed to snake_case and null values excluded. + */ public static Map beanToMap(T bean, final boolean isToUnderlineCase, final boolean ignoreNullValue) { if (ObjectUtils.isEmpty(bean)) { return null; diff --git a/boot/platform/src/main/java/com/plate/boot/commons/utils/query/CriteriaUtils.java b/boot/platform/src/main/java/com/plate/boot/commons/utils/query/CriteriaUtils.java index fb098c0a..993d3ddc 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/utils/query/CriteriaUtils.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/utils/query/CriteriaUtils.java @@ -14,21 +14,52 @@ import java.util.stream.Collectors; /** - * @author Alex bob + * Utility class to hold both SQL string segment and its related parameters. + * This is particularly useful for dynamically constructing SQL queries + * with bind variables in a structured manner. */ public final class CriteriaUtils { public static final Set SKIP_CRITERIA_KEYS = Set.of("extend", "createdTime", "updatedTime"); + /** + * Applies pagination to a SQL query string based on the provided {@link Pageable} object. + * This is a convenience overload that delegates to {@link #applyPage(Pageable, String)} + * with a null prefix, used primarily when no prefix is needed for generating the SQL LIMIT and OFFSET clauses. + * + * @param pageable The pagination information, including page size and offset. + * @return A string representing the pagination part of the SQL query, i.e., "LIMIT {pageSize} OFFSET {offset}". + */ public static String applyPage(Pageable pageable) { return applyPage(pageable, null); } + /** + * Applies pagination to a SQL query string based on the provided {@link Pageable} object and an optional prefix. + * Generates a LIMIT and OFFSET clause with the specified page size and offset from the {@link Pageable} instance, + * optionally prefixed to align with a specific table alias or column name in the SQL query. + * + * @param pageable The pagination information containing the desired page size and offset for query pagination. + * @param prefix An optional prefix to be applied before the LIMIT and OFFSET placeholders in the SQL query. + * This can be useful when addressing specific table aliases or column names. + * @return A string appended with the pagination SQL segment, formatted as "LIMIT {pageSize} OFFSET {offset}", + * with optional prefixing, ready to be integrated into a larger SQL query. + */ public static String applyPage(Pageable pageable, String prefix) { String orderSql = applySort(pageable.getSort(), prefix); return String.format(orderSql + " limit %d offset %d", pageable.getPageSize(), pageable.getOffset()); } + /** + * Applies sorting to a SQL query string based on the provided {@link Sort} object, with an optional prefix for property names. + * Transforms the sort orders into SQL-compatible sort clauses, considering case insensitivity and JSON field access notation. + * If the sort is unsorted or null, defaults to sorting by 'id' in descending order. + * + * @param sort The sorting criteria specifying the properties and directions for sorting. + * @param prefix An optional prefix to prepend to each sorted property name, useful when dealing with table aliases or nested properties. + * @return A string representing the sorting part of the SQL query, starting with "ORDER BY", + * followed by comma-separated sort clauses, each in the format "property_name ASC/DESC". + */ public static String applySort(Sort sort, String prefix) { if (sort == null || sort.isUnsorted()) { return " order by id desc "; @@ -49,6 +80,22 @@ public static String applySort(Sort sort, String prefix) { return " order by " + sortSql; } + /** + * Constructs a ParamSql instance representing a part of an SQL WHERE clause + * and its corresponding parameters extracted from a given Java object, while + * providing options to skip certain keys and apply a prefix to the keys. + * The method first converts the object into a map and processes a special + * "query" key using QueryJson to generate SQL and parameters. It then handles + * a "securityCode" key if present and not skipped. Afterward, it filters out + * keys that should be skipped, including default ones defined in SKIP_CRITERIA_KEYS + * and combines these with additional parameters generated from the remaining object map. + * @param object The source object from which SQL conditions and parameters are derived. + * @param skipKeys A collection of keys to be excluded from processing, can be null. + * @param prefix A prefix to prepend to the keys in the generated SQL, useful for nested queries. + + * @return A ParamSql object containing a StringJoiner with concatenated SQL conditions + * (joined by 'and') and a map of parameters for prepared statement binding. + */ @SuppressWarnings("unchecked") public static ParamSql buildParamSql(Object object, Collection skipKeys, String prefix) { @@ -83,6 +130,23 @@ public static ParamSql buildParamSql(Object object, Collection skipKeys, return ParamSql.of(sql, params); } + /** + * Constructs a ParamSql instance for dynamic SQL WHERE clause generation + * based on a provided map of column-value pairs. Supports optional prefixing + * for column names to handle table aliases or nested properties. Determines + * the SQL condition type (equality, 'like', or 'in') dynamically based on + * the value's type, enabling flexible query construction. + * + * @param objectMap A map where keys represent column names (in camelCase) + * and values are the criteria for filtering. Values can be + * Strings, Collections, or other types supporting equality checks. + * @param prefix An optional string prefix to prepend to each column name, + * typically used to reference specific tables or entities in a query. + * @return A ParamSql object encapsulating the constructed WHERE clause + * conditions joined by 'and', and a map of parameters for prepared + * statement binding, where keys correspond to named parameters + * and values are the user-provided filter values. + */ public static ParamSql buildParamSql(Map objectMap, String prefix) { StringJoiner whereSql = new StringJoiner(" and "); for (Map.Entry entry : objectMap.entrySet()) { @@ -105,6 +169,18 @@ public static ParamSql buildParamSql(Map objectMap, String prefi return ParamSql.of(whereSql, objectMap); } + /** + * Constructs a Criteria instance by converting the provided object into a map, + * excluding specified keys, and then further processing this map to create + * the Criteria object. The method removes keys listed in the predefined + * SKIP_CRITERIA_KEYS set as well as any additional keys specified in the + * skipKes collection from the object map before constructing the Criteria. + * + * @param object The Java object to convert into Criteria. Its properties will form the basis of the Criteria. + * @param skipKes A collection of strings representing keys to exclude from the object during conversion. + * These are in addition to the default skipped keys predefined in SKIP_CRITERIA_KEYS. + * @return A Criteria instance representing the processed object, excluding the specified keys. + */ public static Criteria build(Object object, Collection skipKes) { Map objectMap = BeanUtils.beanToMap(object, true); if (!ObjectUtils.isEmpty(objectMap)) { @@ -117,6 +193,18 @@ public static Criteria build(Object object, Collection skipKes) { return build(objectMap); } + /** + * Constructs a Criteria instance from a map of search criteria. + * Each entry in the map represents a search criterion where the key is the field name + * and the value is the criterion value. The method supports String patterns with 'like', + * collections of values with 'in', and direct value matching for other types. + * + * @param objectMap A map mapping field names to their respective search criterion values. + * String values will be treated for 'like' matching, + * Collections will be used for 'in' clauses, and all other types for equality. + * @return A Criteria instance representing the combined search criteria. + * Returns an empty Criteria if the input map is null or empty. + */ public static Criteria build(Map objectMap) { if (ObjectUtils.isEmpty(objectMap)) { return Criteria.empty(); diff --git a/boot/platform/src/main/java/com/plate/boot/commons/utils/query/ParamSql.java b/boot/platform/src/main/java/com/plate/boot/commons/utils/query/ParamSql.java index 7f0192e3..a740e467 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/utils/query/ParamSql.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/utils/query/ParamSql.java @@ -5,14 +5,40 @@ import java.util.StringJoiner; /** - * @author Alex bob + * Represents a SQL parameter structure consisting of a conditional SQL fragment + * and a map of parameters to be bound to a PreparedStatement. + * This record facilitates the construction of dynamic SQL queries with placeholders + * for improved performance and security against SQL injection. + * + * @param sql A {@link StringJoiner} containing the dynamically built WHERE clause + * fragments of a SQL query, joined by 'and'. + * @param params A {@link Map} mapping parameter names to their respective values, + * which are intended to replace placeholders in the SQL query. */ public record ParamSql(StringJoiner sql, Map params) implements Serializable { + /** + * Creates a new instance of {@link ParamSql} with the provided conditional SQL + * fragment and parameters map. + * + * @param sql A {@link StringJoiner} object containing the dynamically + * constructed WHERE clause segments of a SQL query, concatenated by 'and'. + * @param params A {@link Map} of parameter names to values, which will be + * substituted for placeholders within the SQL query to prevent SQL injection. + * @return A new {@link ParamSql} instance encapsulating the given SQL fragment + * and parameters map, ready for use in preparing a parameterized SQL statement. + */ public static ParamSql of(StringJoiner sql, Map params) { return new ParamSql(sql, params); } + /** + * Constructs a WHERE clause segment for a SQL query based on the accumulated conditions. + * If conditions have been added, it prepends the 'WHERE' keyword followed by the conditions; + * otherwise, it returns an empty string. + * + * @return A String representing the WHERE clause with conditions or an empty string if no conditions exist. + */ public String whereSql() { if (this.sql.length() > 0) { return " where " + this.sql; diff --git a/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryJson.java b/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryJson.java index 8213c976..e37c2be6 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryJson.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryJson.java @@ -10,7 +10,8 @@ import java.util.*; /** - * @author Alex bob + * Searches for a keyword within the provided key and returns the keyword along with its SQL equivalent. + * If no keyword match is found, returns null. */ public class QueryJson { @@ -51,6 +52,19 @@ public class QueryJson { KEYWORDS.put("False", "is false"); } + /** + * Transforms a Spring Sort object into a new Sort object suitable for sorting based on JSON properties, + * considering nested structures denoted by dot-separated keys. Optionally prefixes property keys. + *

+ * This method processes each order in the provided Sort object. If a property key represents a nested + * JSON path (indicated by containing dots), it constructs a new sorting property that can be used + * directly in SQL queries involving JSON data, using the '->>' operator to access nested JSON fields. + * Non-nested properties or those not requiring transformation are preserved as is. + * + * @param sort The original Spring Sort object defining the sorting orders. If null or empty, returns an unsorted Sort. + * @param prefix An optional prefix to prepend to each sorting property, useful for aliasing in SQL queries. + * @return A new Sort object with transformed sorting properties, ready for sorting queries that involve JSON columns. + */ public static Sort sortJson(Sort sort, String prefix) { if (sort == null || sort.isEmpty()) { return Sort.unsorted(); @@ -75,6 +89,19 @@ public static Sort sortJson(Sort sort, String prefix) { return Sort.by(orders); } + /** + * Constructs a SQL parameter object based on a JSON-like map structure and an optional prefix for column names. + *

+ * This method iterates through the provided map, treating keys as JSON paths to construct + * WHERE clause conditions and bind parameters accordingly. Supports complex JSON paths + * and keyword-based operations like 'Between' and 'NotBetween'. + * + * @param params A map where each key represents a JSON path to a value that should be used in query conditions. + * The value associated with each key is the target value for comparison or range (for Between/NotBetween). + * @param prefix An optional prefix to prepend to column names, useful when querying nested or aliased tables/views. + * @return A {@link ParamSql} object containing a {@link StringJoiner} with concatenated WHERE clause conditions + * and a map of bind parameters to be used in a prepared statement. + */ public static ParamSql queryJson(Map params, String prefix) { Map bindParams = Maps.newHashMap(); StringJoiner whereSql = new StringJoiner(" and "); @@ -98,6 +125,17 @@ public static ParamSql queryJson(Map params, String prefix) { return ParamSql.of(whereSql, bindParams); } + /** + * Generates a JSON path key along with the corresponding parameter name for constructing SQL queries. + * It supports handling special keywords in the last key segment for operations like 'Between' and 'NotBetween'. + * + * @param keys An array of strings representing keys in a JSON path, typically derived from a dot-separated string. + * @param prefix An optional prefix to be prepended to the first key, used to namespace column names in SQL queries. + * @return A Map.Entry containing: + * - Key: A string representing the constructed JSON path expression suitable for SQL query with placeholders. + * - Value: A list of strings representing the parameter names to bind values to in the SQL prepared statement. + * @throws IllegalArgumentException If the keys array is null or empty. + */ private static Map.Entry> jsonPathKeyAndParamName(String[] keys, String prefix) { if (keys == null || keys.length < 1) { throw new IllegalArgumentException("Keys array cannot be null or empty."); @@ -147,6 +185,13 @@ private static Map.Entry> jsonPathKeyAndParamName(String[] } + /** + * Appends intermediate keys from a given array into a StringBuilder to form a part of a JSON path expression. + * Each key is surrounded by '->' to denote nested elements in a JSON structure when used within SQL queries. + * + * @param joinKeys An array of strings representing intermediate keys in a JSON path. + * @return StringBuilder containing the concatenated intermediate keys formatted for a JSON path expression. + */ private static StringBuilder appendIntermediateKeys(String[] joinKeys) { StringBuilder jsonPath = new StringBuilder(); for (String path : joinKeys) { @@ -155,6 +200,14 @@ private static StringBuilder appendIntermediateKeys(String[] joinKeys) { return jsonPath; } + /** + * Searches for a keyword within a predefined map of keywords and returns the matching entry. + * The search prioritizes longer keywords and is case-insensitive, considering the end of the input string. + * + * @param inputStr The string to search for a matching keyword suffix. + * @return An entry containing the matched keyword and its associated value, + * or null if no match is found. + */ private static Map.Entry findKeyWord(String inputStr) { return KEYWORDS.entrySet().stream() .filter(entry -> StringUtils.endsWithIgnoreCase(inputStr, entry.getKey()))