This is an automated email from the ASF dual-hosted git repository.

diveshdut pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 31fc974cd1 OFBIZ-13457 Add REST sort order-by alias resolver (#1556)
31fc974cd1 is described below

commit 31fc974cd12372af3ccd9773563930176ac97fab
Author: Divesh Dutta <[email protected]>
AuthorDate: Tue Aug 4 15:16:52 2026 +0530

    OFBIZ-13457 Add REST sort order-by alias resolver (#1556)
    
    ## Summary
    
    Adds a generic REST framework helper for resolving public `sort`
    expressions into EntityQuery-compatible order-by fields.
    
    ## Changes
    
    - Adds `RestApiUtil.resolveOrderBy(...)`
    - Reuses existing REST sort syntax validation
    - Supports endpoint-defined public sort aliases mapped to entity fields
    - Preserves descending sort syntax such as `-displayName`
    - Appends default order-by fields as stable tie-breakers
    - Rejects unsupported, malformed, or duplicate mapped fields
    - Adds focused unit coverage in `RestApiUtilPaginationTest`
---
 .../org/apache/ofbiz/ws/rs/util/RestApiUtil.java   | 49 ++++++++++++++++++++++
 .../ws/rs/util/RestApiUtilPaginationTest.java      | 27 ++++++++++++
 2 files changed, 76 insertions(+)

diff --git 
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/util/RestApiUtil.java 
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/util/RestApiUtil.java
index b8d3a9b7f3..22e14ffab7 100644
--- 
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/util/RestApiUtil.java
+++ 
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/util/RestApiUtil.java
@@ -362,6 +362,55 @@ public final class RestApiUtil {
         return validatedFields;
     }
 
+    /**
+     * Resolves a REST sort expression into EntityQuery order-by fields.
+     * Endpoint contracts can expose semantic sort names while entities use
+     * different field names, so public names are validated before mapping.
+     *
+     * @param sortExpression the requested sort expression
+     * @param sortFieldMap endpoint-supported sort fields mapped to entity 
fields
+     * @param defaultOrderBy fallback order fields and stable tie-breakers
+     * @return entity order-by fields suitable for {@code EntityQuery.orderBy}
+     * @throws IllegalArgumentException when a token is malformed, unsupported,
+     *         or maps to a duplicate entity field
+     */
+    public static List<String> resolveOrderBy(String sortExpression, 
Map<String, String> sortFieldMap, List<String> defaultOrderBy) {
+        List<String> orderBy = new ArrayList<>();
+        if (UtilValidate.isEmpty(sortExpression)) {
+            if (UtilValidate.isNotEmpty(defaultOrderBy)) {
+                orderBy.addAll(defaultOrderBy);
+            }
+            return orderBy;
+        }
+
+        Set<String> allowedFields = UtilValidate.isNotEmpty(sortFieldMap) ? 
sortFieldMap.keySet() : null;
+        List<String> validatedFields = validateSortFields(sortExpression, 
allowedFields);
+        Set<String> seenEntityFields = new HashSet<>();
+        for (String requestedField : validatedFields) {
+            boolean descending = requestedField.startsWith("-");
+            String sortKey = descending ? requestedField.substring(1) : 
requestedField;
+            String entityField = UtilValidate.isNotEmpty(sortFieldMap) ? 
sortFieldMap.get(sortKey) : sortKey;
+            if (UtilValidate.isEmpty(entityField)) {
+                throw new IllegalArgumentException("Unsupported sort field: " 
+ sortKey);
+            }
+            if (!seenEntityFields.add(entityField)) {
+                throw new IllegalArgumentException("Duplicate sort field: " + 
entityField);
+            }
+            orderBy.add((descending ? "-" : "") + entityField);
+        }
+
+        if (UtilValidate.isNotEmpty(defaultOrderBy)) {
+            for (String defaultField : defaultOrderBy) {
+                String normalizedDefaultField = defaultField.startsWith("-") ? 
defaultField.substring(1) : defaultField;
+                if (!seenEntityFields.contains(normalizedDefaultField)) {
+                    orderBy.add(defaultField);
+                    seenEntityFields.add(normalizedDefaultField);
+                }
+            }
+        }
+        return orderBy;
+    }
+
     /**
      * Validates candidate filter parameters against an optional 
endpoint-defined
      * allowlist while preserving insertion order.
diff --git 
a/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/util/RestApiUtilPaginationTest.java
 
b/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/util/RestApiUtilPaginationTest.java
index cf2c676270..262de1c468 100644
--- 
a/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/util/RestApiUtilPaginationTest.java
+++ 
b/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/util/RestApiUtilPaginationTest.java
@@ -79,6 +79,33 @@ public final class RestApiUtilPaginationTest {
         assertEquals("Duplicate sort field: fieldOne", exception.getMessage());
     }
 
+    @Test
+    public void resolvesOrderByWithFieldAliasesAndStableDefaults() {
+        List<String> orderBy = RestApiUtil.resolveOrderBy("-displayName",
+                UtilMisc.toMap("displayName", "entityName", "externalId", 
"entityId"),
+                List.of("entityName", "entityId"));
+
+        assertEquals(List.of("-entityName", "entityId"), orderBy);
+    }
+
+    @Test
+    public void resolvesOrderByToDefaultsWhenSortMissing() {
+        List<String> defaultOrderBy = List.of("entityName", "entityId");
+
+        assertEquals(defaultOrderBy, RestApiUtil.resolveOrderBy(null,
+                UtilMisc.toMap("externalId", "entityId"), defaultOrderBy));
+    }
+
+    @Test
+    public void rejectsOrderByAliasesThatMapToDuplicateEntityFields() {
+        IllegalArgumentException exception = 
assertThrows(IllegalArgumentException.class, () ->
+                RestApiUtil.resolveOrderBy("displayName,-entityName",
+                        UtilMisc.toMap("displayName", "entityName", 
"entityName", "entityName"),
+                        List.of("entityId")));
+
+        assertEquals("Duplicate sort field: entityName", 
exception.getMessage());
+    }
+
     @Test
     public void serializesAvailableRelationsAsHttpLinkHeader() {
         Map<String, Object> links = new LinkedHashMap<>();

Reply via email to