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

Lukas-Finster 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 2890723b60 Implemented: Retrieval of custom httpHeaders and use as 
service IN parameters (OFBIZ-12515)
2890723b60 is described below

commit 2890723b608a57aacbd3c4b66e2e216cc3a30a71
Author: Lukas Finster <[email protected]>
AuthorDate: Tue Aug 11 16:18:19 2026 +0200

    Implemented: Retrieval of custom httpHeaders and use as service IN
    parameters (OFBIZ-12515)
---
 .../rest-api/api/exampleApiDefinition.rest.xml     |  6 +++
 framework/rest-api/dtd/rest-api.xsd                |  2 +
 framework/rest-api/servicedef/services.xml         |  6 +++
 .../apache/ofbiz/ws/rs/model/ModelApiReader.java   |  7 ++-
 .../apache/ofbiz/ws/rs/model/ModelOperation.java   | 48 ++++++++++++++++++++-
 .../apache/ofbiz/ws/rs/model/ModelResource.java    | 50 +++++++++++++++++++++-
 .../ofbiz/ws/rs/openapi/OFBizOpenApiReader.java    |  6 +++
 .../ofbiz/ws/rs/process/RestRequestHandler.java    | 15 +++++++
 .../ofbiz/ws/rs/test/RestTestHttpRequest.java      | 39 ++++++++++++++---
 .../apache/ofbiz/ws/rs/test/RestTestServices.java  | 18 ++++++++
 10 files changed, 186 insertions(+), 11 deletions(-)

diff --git a/framework/rest-api/api/exampleApiDefinition.rest.xml 
b/framework/rest-api/api/exampleApiDefinition.rest.xml
index 137f56de4f..dc110db901 100644
--- a/framework/rest-api/api/exampleApiDefinition.rest.xml
+++ b/framework/rest-api/api/exampleApiDefinition.rest.xml
@@ -47,6 +47,12 @@ under the License.
         </operation>
     </resource>
 
+    <resource name="orders" path="/useCustomHeaderAsServiceParameter">
+        <operation verb="post" consumes="application/json" 
produces="application/json" customHeaders="x-custom-header">
+            <service name="useCustomHeaderAsServiceParameter"/>
+        </operation>
+    </resource>
+
     <mapping name="RestOrderExample" 
className="org.apache.ofbiz.ws.rs.examples.RestOrderExample"/>
 
 </api>
\ No newline at end of file
diff --git a/framework/rest-api/dtd/rest-api.xsd 
b/framework/rest-api/dtd/rest-api.xsd
index f193cff990..8c2fbd7938 100644
--- a/framework/rest-api/dtd/rest-api.xsd
+++ b/framework/rest-api/dtd/rest-api.xsd
@@ -44,6 +44,7 @@ under the License.
             <xs:attribute name="description" type="xs:string"/>
             <xs:attribute name="publish" type="xs:boolean" default="true"/>
             <xs:attribute name="auth" type="xs:boolean" default="true"/>
+            <xs:attribute name="customHeaders" type="xs:string"/>
         </xs:complexType>
     </xs:element>
     <xs:element name="operation">
@@ -82,6 +83,7 @@ under the License.
             <xs:attribute name="description" type="xs:string"/>
             <xs:attribute name="auth" type="xs:boolean" default="true"/>
             <xs:attribute name="addApiResponses" type="xs:string"/>
+            <xs:attribute name="customHeaders" type="xs:string"/>
         </xs:complexType>
     </xs:element>
     <xs:element name="service">
diff --git a/framework/rest-api/servicedef/services.xml 
b/framework/rest-api/servicedef/services.xml
index ba0eb06b60..e83b3966ee 100644
--- a/framework/rest-api/servicedef/services.xml
+++ b/framework/rest-api/servicedef/services.xml
@@ -59,4 +59,10 @@ under the License.
         <attribute name="httpResponseStatus" type="java.lang.Integer" 
mode="OUT" optional="false"/>
     </service>
 
+    <service name="useCustomHeaderAsServiceParameter" engine="java"
+            location="org.apache.ofbiz.ws.rs.test.RestTestServices" 
invoke="useCustomHeaderAsServiceParameter">
+        <description>TestService that returns success, but overwrites it with 
a custom http status code</description>
+        <attribute name="x-custom-header" type="String" mode="INOUT" 
optional="true"/>
+    </service>
+
 </services>
diff --git 
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApiReader.java
 
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApiReader.java
index 72f5c3a1be..f8c7ef17b3 100644
--- 
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApiReader.java
+++ 
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApiReader.java
@@ -108,7 +108,8 @@ public final class ModelApiReader {
                 
.displayName(UtilXml.checkEmpty(resourceEle.getAttribute("displayName")).intern())
                 
.path(UtilXml.checkEmpty(resourceEle.getAttribute("path")).intern())
                 
.publish(Boolean.parseBoolean(UtilXml.checkEmpty(resourceEle.getAttribute("publish")).intern()))
-                
.auth(Boolean.parseBoolean(UtilXml.checkEmpty(resourceEle.getAttribute("auth")).intern()));
+                
.auth(Boolean.parseBoolean(UtilXml.checkEmpty(resourceEle.getAttribute("auth")).intern()))
+                
.customHeaders(UtilXml.checkEmpty(resourceEle.getAttribute("customHeaders")).intern());
     }
 
     private static void createOperations(Element resourceEle, ModelResource 
resource) {
@@ -123,7 +124,9 @@ public final class ModelApiReader {
                         
.consumes(UtilXml.checkEmpty(operationEle.getAttribute("consumes")).intern())
                         
.description(UtilXml.checkEmpty(operationEle.getAttribute("description")).intern())
                         
.auth(Boolean.parseBoolean(UtilXml.checkEmpty(operationEle.getAttribute("auth")).intern()))
-                        
.addApiResponses(UtilXml.checkEmpty(operationEle.getAttribute("addApiResponses")).intern());
+                        
.addApiResponses(UtilXml.checkEmpty(operationEle.getAttribute("addApiResponses")).intern())
+                        
.customHeaders(UtilXml.checkEmpty(operationEle.getAttribute("customHeaders"),
+                                
resourceEle.getAttribute("customHeaders")).intern());
                 resource.addOperation(op);
             } else {
                 Debug.logWarning("Error during creation of ModelApi, due to 
missing 'service' Attribute in ApiModelXml for"
diff --git 
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelOperation.java
 
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelOperation.java
index 995c52a71b..cdc15f8f41 100644
--- 
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelOperation.java
+++ 
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelOperation.java
@@ -33,6 +33,7 @@ public class ModelOperation {
     private String description;
     private boolean auth;
     private String addApiResponses;
+    private String customHeaders;
 
     /**
      * Returns whether the user is authenticated.
@@ -271,10 +272,55 @@ public class ModelOperation {
         return this;
     }
 
+    /**
+     * Gets the raw, comma-separated custom headers string configured for this 
resource.
+     *
+     * @return the customHeaders as a raw comma-separated string, or {@code 
null} if unset
+     */
+    public String getCustomHeaders() {
+        return customHeaders;
+    }
+
+    /**
+     * Sets the raw custom headers for this resource, as a comma-separated 
string.
+     *
+     * @param customHeaders the customHeaders to set, expected as a 
comma-separated list
+     */
+    public void setCustomHeaders(String customHeaders) {
+        this.customHeaders = customHeaders;
+    }
+
+    /**
+     * Fluent-style setter for customHeaders, allowing method chaining when 
building
+     * a {@link ModelOperation}.
+     *
+     * @param customHeaders the comma-separated custom headers to set
+     * @return this {@link ModelOperation} instance, for chaining
+     */
+    public ModelOperation customHeaders(String customHeaders) {
+        this.customHeaders = customHeaders;
+        return this;
+    }
+
+    /**
+     * Parses the raw {@link #customHeaders} string into a list of individual 
header names,
+     * splitting on commas.
+     *
+     * @return a {@link List} of individual custom header names; an empty list 
if
+     * {@link #customHeaders} is unset or empty
+     */
+    public List<String> getCustomHeadersList() {
+        if (UtilValidate.isEmpty(customHeaders)) {
+            return new ArrayList<>();
+        }
+        return StringUtil.split(customHeaders, ",");
+    }
+
     @Override
     public String toString() {
         return "service: " + service + ", path: " + path + ", verb: " + verb + 
", description: " + description
-                + ", produces: " + produces + ", addApiResponses:" + 
addApiResponses;
+                + ", produces: " + produces + ", addApiResponses:" + 
addApiResponses + ", customHeaders: "
+                + customHeaders;
     }
 
 }
diff --git 
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelResource.java
 
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelResource.java
index 624599e226..2f01ffbf56 100644
--- 
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelResource.java
+++ 
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelResource.java
@@ -21,6 +21,9 @@ package org.apache.ofbiz.ws.rs.model;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.ofbiz.base.util.StringUtil;
+import org.apache.ofbiz.base.util.UtilValidate;
+
 public class ModelResource {
 
     private List<ModelOperation> operations;
@@ -31,6 +34,7 @@ public class ModelResource {
     private String description;
     private boolean publish;
     private boolean auth;
+    private String customHeaders;
 
     /**
      * Returns whether the user is authenticated.
@@ -255,11 +259,55 @@ public class ModelResource {
         return this;
     }
 
+    /**
+     * Gets the raw, comma-separated custom headers string configured for this 
resource.
+     *
+     * @return the customHeaders as a raw comma-separated string, or {@code 
null} if unset
+     */
+    public String getCustomHeaders() {
+        return customHeaders;
+    }
+
+    /**
+     * Sets the raw custom headers for this resource, as a comma-separated 
string.
+     *
+     * @param customHeaders the customHeaders to set, expected as a 
comma-separated list
+     */
+    public void setCustomHeaders(String customHeaders) {
+        this.customHeaders = customHeaders;
+    }
+
+    /**
+     * Fluent-style setter for customHeaders, allowing method chaining when 
building
+     * a {@link ModelResource}.
+     *
+     * @param customHeaders the comma-separated custom headers to set
+     * @return this {@link ModelResource} instance, for chaining
+     */
+    public ModelResource customHeaders(String customHeaders) {
+        this.customHeaders = customHeaders;
+        return this;
+    }
+
+    /**
+     * Parses the raw {@link #customHeaders} string into a list of individual 
header names,
+     * splitting on commas.
+     *
+     * @return a {@link List} of individual custom header names; an empty list 
if
+     * {@link #customHeaders} is unset or empty
+     */
+    public List<String> getCustomHeadersList() {
+        if (UtilValidate.isEmpty(customHeaders)) {
+            return new ArrayList<>();
+        }
+        return StringUtil.split(customHeaders, ",");
+    }
+
     @Override
     public String toString() {
         // TODO Auto-generated method stub
         return "name: " + name + ", path: " + path + ", displayName: " + 
displayName + ", description: " + description
-                + ", publish: " + publish;
+                + ", publish: " + publish + ", customHeaders: " + 
customHeaders;
     }
 
 }
diff --git 
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/openapi/OFBizOpenApiReader.java
 
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/openapi/OFBizOpenApiReader.java
index 6c45403d7f..3c30c67f2f 100644
--- 
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/openapi/OFBizOpenApiReader.java
+++ 
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/openapi/OFBizOpenApiReader.java
@@ -192,6 +192,7 @@ public final class OFBizOpenApiReader extends Reader 
implements OpenApiReader {
             addServiceInSchema(service, op);
             addServiceOperationApiResponses(service, operation);
             addAdditionalOperationApiResponses(service, op, operation);
+            addCustomHeaders(op, operation);
             setPathItemOperation(pathItemObject, verb.toUpperCase(), 
operation);
 
             if (!pathExists) {
@@ -374,4 +375,9 @@ public final class OFBizOpenApiReader extends Reader 
implements OpenApiReader {
         });
     }
 
+    private void addCustomHeaders(ModelOperation op, Operation operation) {
+        op.getCustomHeadersList().forEach((headerName) -> {
+            operation.addParametersItem(new 
HeaderParameter().name(headerName).schema(new StringSchema()).required(true));
+        });
+    }
 }
diff --git 
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/process/RestRequestHandler.java
 
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/process/RestRequestHandler.java
index d1d205bc18..41e590c8f9 100644
--- 
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/process/RestRequestHandler.java
+++ 
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/process/RestRequestHandler.java
@@ -212,6 +212,16 @@ public abstract class RestRequestHandler implements 
Inflector<ContainerRequestCo
         return extract(requestContext.getUriInfo().getQueryParameters());
     }
 
+    /**
+     * Extracts the header Parameters
+     *
+     * @param requestContext the request context
+     * @return a map containing headers
+     */
+    protected Map<String, Object> 
extractHeaderParameters(ContainerRequestContext requestContext) {
+        return extract(requestContext.getHeaders());
+    }
+
     /**
      * Converts a multivalued parameter map into a standard argument map.
      *
@@ -248,6 +258,7 @@ public abstract class RestRequestHandler implements 
Inflector<ContainerRequestCo
         Map<String, Object> arguments = new HashMap<>();
         arguments.putAll(extractPathParameters(requestContext));
         arguments.putAll(extractQueryParameters(requestContext));
+        arguments.putAll(extractHeaderParameters(requestContext));
         return execute(requestContext, arguments);
     }
 
@@ -256,6 +267,7 @@ public abstract class RestRequestHandler implements 
Inflector<ContainerRequestCo
         arguments.putAll(extractRequestBody(requestContext));
         arguments.putAll(extractPathParameters(requestContext));
         arguments.putAll(extractQueryParameters(requestContext));
+        arguments.putAll(extractHeaderParameters(requestContext));
         return execute(requestContext, arguments);
     }
 
@@ -264,6 +276,7 @@ public abstract class RestRequestHandler implements 
Inflector<ContainerRequestCo
         arguments.putAll(extractRequestBody(requestContext));
         arguments.putAll(extractPathParameters(requestContext));
         arguments.putAll(extractQueryParameters(requestContext));
+        arguments.putAll(extractHeaderParameters(requestContext));
         return execute(requestContext, arguments);
     }
 
@@ -272,6 +285,7 @@ public abstract class RestRequestHandler implements 
Inflector<ContainerRequestCo
         arguments.putAll(extractRequestBody(requestContext));
         arguments.putAll(extractPathParameters(requestContext));
         arguments.putAll(extractQueryParameters(requestContext));
+        arguments.putAll(extractHeaderParameters(requestContext));
         return execute(requestContext, arguments);
     }
 
@@ -279,6 +293,7 @@ public abstract class RestRequestHandler implements 
Inflector<ContainerRequestCo
         Map<String, Object> arguments = new HashMap<>();
         arguments.putAll(extractPathParameters(requestContext));
         arguments.putAll(extractQueryParameters(requestContext));
+        arguments.putAll(extractHeaderParameters(requestContext));
         return execute(requestContext, arguments);
     }
 
diff --git 
a/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/test/RestTestHttpRequest.java
 
b/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/test/RestTestHttpRequest.java
index 9a1c071717..85e5c230da 100644
--- 
a/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/test/RestTestHttpRequest.java
+++ 
b/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/test/RestTestHttpRequest.java
@@ -62,7 +62,7 @@ class RestTestHttpRequest implements JupiterTestHelper {
         try {
             response = fetchClient.post();
         } catch (HttpClientException e) {
-            Debug.logError(e, "Error returning rest access token", "MODULE");
+            Debug.logError(e, "Error returning rest access token", MODULE);
             return;
         }
 
@@ -70,7 +70,7 @@ class RestTestHttpRequest implements JupiterTestHelper {
             JsonNode root = mapper.readTree(response);
             accessToken = root.get("data").get("access_token").asText();
         } catch (JsonProcessingException | NullPointerException e) {
-            Debug.logError(e, "Error parsing rest auth response", "MODULE");
+            Debug.logError(e, "Error parsing rest auth response", MODULE);
         }
     }
 
@@ -85,14 +85,14 @@ class RestTestHttpRequest implements JupiterTestHelper {
         try {
             response = client.post();
         } catch (HttpClientException e) {
-            Debug.logError(e, "Error returning rest access token", "MODULE");
+            Debug.logError(e, "Error during rest POST to 
/exampleApi/returnSuccess", MODULE);
         }
         int statusCode = 0;
         try {
             JsonNode root = mapper.readTree(response);
             statusCode = root.get("statusCode").asInt(999999999);
         } catch (JsonProcessingException | NullPointerException e) {
-            Debug.logError(e, "Error parsing rest auth response", "MODULE");
+            Debug.logError(e, "Error parsing rest auth response", MODULE);
         }
 
         assertEquals(200, statusCode);
@@ -104,22 +104,47 @@ class RestTestHttpRequest implements JupiterTestHelper {
         HttpClient client = initHttpClient();
         client.setHeader("Content-Type", "application/json");
         client.setHeader("Authorization", "Bearer " + accessToken);
-        client.setUrl(BASE_URL + 
"/exampleApi//returnSuccessButOverwriteStatusCode");
+        client.setUrl(BASE_URL + 
"/exampleApi/returnSuccessButOverwriteStatusCode");
 
         String response = "";
         try {
             response = client.post();
         } catch (HttpClientException e) {
-            Debug.logError(e, "Error returning rest access token", "AAAA");
+            Debug.logError(e, "Error during rest POST to 
/exampleApi/returnSuccessButOverwriteStatusCode", MODULE);
         }
         int statusCode = 0;
         try {
             JsonNode root = mapper.readTree(response);
             statusCode = root.get("statusCode").asInt(999999999);
         } catch (JsonProcessingException | NullPointerException e) {
-            Debug.logError(e, "Error parsing rest auth response", "MODULE");
+            Debug.logError(e, "Error parsing rest auth response", MODULE);
         }
 
         assertEquals(201, statusCode);
     }
+
+    @Test
+    void useCustomHeaderAsServiceParameter() throws Exception {
+        HttpClient client = initHttpClient();
+        client.setHeader("Content-Type", "application/json");
+        client.setHeader("Authorization", "Bearer " + accessToken);
+        client.setHeader("x-custom-header", "Foo");
+        client.setUrl(BASE_URL + 
"/exampleApi/useCustomHeaderAsServiceParameter");
+
+        String response = "";
+        try {
+            response = client.post();
+        } catch (HttpClientException e) {
+            Debug.logError(e, "Error during rest POST to 
/exampleApi/useCustomHeaderAsServiceParameter", MODULE);
+        }
+        String customHeaderValue = "";
+        try {
+            JsonNode root = mapper.readTree(response);
+            customHeaderValue = 
root.get("data").get("x-custom-header").asText();
+        } catch (JsonProcessingException | NullPointerException e) {
+            Debug.logError(e, "Error parsing rest auth response", MODULE);
+        }
+
+        assertEquals("Foo", customHeaderValue);
+    }
 }
diff --git 
a/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/test/RestTestServices.java
 
b/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/test/RestTestServices.java
index 9fa10f7d7f..8fe50fd0cd 100644
--- 
a/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/test/RestTestServices.java
+++ 
b/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/test/RestTestServices.java
@@ -20,6 +20,7 @@ package org.apache.ofbiz.ws.rs.test;
 
 import java.util.Map;
 
+import org.apache.ofbiz.base.util.UtilValidate;
 import org.apache.ofbiz.service.DispatchContext;
 import org.apache.ofbiz.service.ModelService;
 import org.apache.ofbiz.service.ServiceUtil;
@@ -65,4 +66,21 @@ public class RestTestServices {
         result.put(RestApiUtil.RESPONSE_STATUS_KEY, 201);
         return result;
     }
+
+    /**
+     * TestService requiring a customHeader 'x-custom-header' to be present
+     *
+     * @param dctx
+     * @param context
+     * @return result
+     */
+    public static Map<String, Object> 
useCustomHeaderAsServiceParameter(DispatchContext dctx, Map<String, ? extends 
Object> context) {
+        String customHeader = (String) context.get("x-custom-header");
+        if (UtilValidate.isEmpty(customHeader)) {
+            return ServiceUtil.returnError("Missing custom header 
'x-custom-header'");
+        }
+        Map<String, Object> result = ServiceUtil.returnSuccess();
+        result.put("x-custom-header", customHeader);
+        return result;
+    }
 }

Reply via email to