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

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


The following commit(s) were added to refs/heads/trunk by this push:
     new 8a31d2f4c Added support for multiple rest.xml files per component 
(#141)
8a31d2f4c is described below

commit 8a31d2f4ca1613f36106388e9fec5244cf90dd07
Author: Deepak Dixit <dee...@apache.org>
AuthorDate: Mon Aug 25 10:35:25 2025 +0530

    Added support for multiple rest.xml files per component (#141)
    
    * Added support for multiple rest.xml files per component
    (OFBIZ-13288)
    
    - Enhanced loadApiDefinitions to scan and load all rest.xml files from the 
component's api folder, instead of being restricted to a single file
    - Introduced duplicate path handling: when multiple rest.xml files define 
the same endpoint path, the most recently loaded definition takes precedence
    - Added a new path attribute in ModelApi class (with getter/setter methods) 
to capture the API entry path and improve flexibility in routing
    - Refactored OFBizOpenApiReader to build nested URLs using the ModelApi 
path, ensuring consistency between swagger documentation and runtime endpoints
    
    * Use ServiceNameContextHolder to set the service name in 
ServiceRequestHandler
    
    * Adds example-product.rest.xml showcasing multiple rest.xml files per 
component
---
 example/api/example.rest.xml                       |  3 +-
 example/api/{example.rest.xml => product.rest.xml} | 24 +++++----------
 rest-api/dtd/rest-api.xsd                          |  1 +
 .../apache/ofbiz/ws/rs/core/OFBizApiConfig.java    | 34 +++++++++++++++-------
 .../org/apache/ofbiz/ws/rs/model/ModelApi.java     | 21 +++++++++++++
 .../apache/ofbiz/ws/rs/model/ModelApiReader.java   |  2 ++
 .../ofbiz/ws/rs/openapi/OFBizOpenApiReader.java    | 25 ++++++++++++++--
 7 files changed, 79 insertions(+), 31 deletions(-)

diff --git a/example/api/example.rest.xml b/example/api/example.rest.xml
index 69dfba7dc..02b296056 100644
--- a/example/api/example.rest.xml
+++ b/example/api/example.rest.xml
@@ -20,12 +20,13 @@ under the License.
 
 <api name="ExampleRestApi"
      displayName="Example REST API"
+     path="example-rest"
      description="This API exposes example services as REST endpoints."
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
      
xsi:noNamespaceSchemaLocation="https://ofbiz.apache.org/dtds/rest-api.xsd";>
 
     <resource name="ExampleResource"
-              path="/example"
+              path="example"
               displayName="Example Resource"
               description="Handles example-related operations">
 
diff --git a/example/api/example.rest.xml b/example/api/product.rest.xml
similarity index 60%
copy from example/api/example.rest.xml
copy to example/api/product.rest.xml
index 69dfba7dc..32161beae 100644
--- a/example/api/example.rest.xml
+++ b/example/api/product.rest.xml
@@ -20,27 +20,19 @@ under the License.
 
 <api name="ExampleRestApi"
      displayName="Example REST API"
-     description="This API exposes example services as REST endpoints."
+     path="example-product"
+     description="This API exposes example product services as REST endpoints."
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
      
xsi:noNamespaceSchemaLocation="https://ofbiz.apache.org/dtds/rest-api.xsd";>
 
-    <resource name="ExampleResource"
-              path="/example"
-              displayName="Example Resource"
-              description="Handles example-related operations">
+    <resource name="ExampleProductResource"
+              path="product"
+              displayName="Example Product Resource"
+              description="Handles example-product operations">
 
-        <operation verb="post" description="Create Example"
+        <operation verb="post" description="Find product by id"
                    consumes="application/json">
-            <service name="createExample"/>
-        </operation>
-
-        <operation verb="delete" description="delete Example"
-                   consumes="application/json">
-            <service name="deleteExample"/>
-        </operation>
-        <operation verb="put" description="Update Example"
-                   consumes="application/json">
-            <service name="updateExample"/>
+            <service name="findProductById"/>
         </operation>
     </resource>
 </api>
\ No newline at end of file
diff --git a/rest-api/dtd/rest-api.xsd b/rest-api/dtd/rest-api.xsd
index 2b7f4a210..1c74d6481 100644
--- a/rest-api/dtd/rest-api.xsd
+++ b/rest-api/dtd/rest-api.xsd
@@ -25,6 +25,7 @@ under the License.
                 <xs:element minOccurs="0" maxOccurs="unbounded" 
ref="resource"/>
             </xs:sequence>
             <xs:attribute name="name" type="xs:string" use="required"/>
+            <xs:attribute name="path" type="xs:string" use="required"/>
             <xs:attribute name="displayName" type="xs:string"/>
             <xs:attribute name="description" type="xs:string"/>
             <xs:attribute name="publish" type="xs:boolean" default="true"/>
diff --git 
a/rest-api/src/main/java/org/apache/ofbiz/ws/rs/core/OFBizApiConfig.java 
b/rest-api/src/main/java/org/apache/ofbiz/ws/rs/core/OFBizApiConfig.java
index 370ac1c7d..2d42a09ac 100644
--- a/rest-api/src/main/java/org/apache/ofbiz/ws/rs/core/OFBizApiConfig.java
+++ b/rest-api/src/main/java/org/apache/ofbiz/ws/rs/core/OFBizApiConfig.java
@@ -85,12 +85,26 @@ public class OFBizApiConfig extends ResourceConfig {
         components.forEach(component -> {
             String cName = component.getComponentName();
             try {
-                String apiSchema = ComponentConfig.getRootLocation(cName) + 
"/api/" + cName + ".rest.xml";
-                File apiSchemaF = new File(apiSchema);
-                if (apiSchemaF.exists()) {
-                    Debug.logInfo("Processing REST API " + cName + ".rest.xml" 
+ " from component " + cName, MODULE);
-                    ModelApi api = ModelApiReader.getModelApi(apiSchemaF);
-                    MICRO_APIS.put(cName, api);
+                String apiDirPath = ComponentConfig.getRootLocation(cName) + 
"/api";
+                File apiDir = new File(apiDirPath);
+                if (apiDir.exists() && apiDir.isDirectory()) {
+                    File[] restXmlFiles = apiDir.listFiles((dir, name) -> 
name.endsWith(".rest.xml"));
+                    for (File apiSchemaF : restXmlFiles) {
+                        ModelApi api = ModelApiReader.getModelApi(apiSchemaF);
+                        if (!api.isPublish()) {
+                            Debug.logInfo("API {}[{}] is declared to be a 
non-publish, ignoring...", api.getName(), api.getPath(), MODULE);
+                            continue;
+                        }
+                        String path = api.getPath();
+                        if (MICRO_APIS.containsKey(path)) {
+                            Debug.logWarning("Duplicate REST API definition 
detected for path: " + path +
+                                    " at location " +  apiSchemaF +
+                                    ". Overriding existing entry from 
component: " + cName, MODULE);
+                        } else {
+                            Debug.logInfo("Processing REST API path: " + path 
+ " from component " + cName, MODULE);
+                        }
+                        MICRO_APIS.put(path, api);
+                    }
                 }
             } catch (ComponentException e) {
                 Debug.logError(e, MODULE);
@@ -104,15 +118,13 @@ public class OFBizApiConfig extends ResourceConfig {
             return;
         }
         MICRO_APIS.forEach((k, v) -> {
-            if (!v.isPublish()) {
-                Debug.logInfo("API '" + v.getName() + "' is declared to be a 
non-publish, ignoring...", MODULE);
-                return;
-            }
             Debug.logInfo("Registring Resource Definitions from API - " + k, 
MODULE);
             List<ModelResource> resources = v.getResources();
+            String entryPath = v.getPath();
             resources.forEach(modelResource -> {
                 if (modelResource.isPublish()) {
-                    Resource.Builder resourceBuilder = 
Resource.builder(modelResource.getPath())
+                    String path = entryPath + "/" + modelResource.getPath() + 
"/";
+                    Resource.Builder resourceBuilder = Resource.builder(path)
                             .name(modelResource.getName());
                     for (ModelOperation op : modelResource.getOperations()) {
                         String verb = op.getVerb().toUpperCase();
diff --git a/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApi.java 
b/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApi.java
index d57a805a0..1a54f2479 100644
--- a/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApi.java
+++ b/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApi.java
@@ -25,6 +25,7 @@ public class ModelApi {
 
     private List<ModelResource> resources;
     private String name;
+    private String path;
     private String displayName;
     private String description;
     private boolean publish;
@@ -61,6 +62,16 @@ public class ModelApi {
         return name;
     }
 
+    /**
+     * Gets the value of the path property.
+     *
+     * @return possible object is {@link String }
+     *
+     */
+    public String getPath() {
+        return path;
+    }
+
     /**
      * Sets the value of the name property.
      *
@@ -71,6 +82,16 @@ public class ModelApi {
         this.name = value;
     }
 
+    /**
+     * Sets the value of the path property.
+     *
+     * @param value allowed object is {@link String }
+     *
+     */
+    public void setPath(String path) {
+        this.path = path;
+    }
+
     /**
      * Gets the value of the displayName property.
      *
diff --git 
a/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApiReader.java 
b/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApiReader.java
index 0bc8c3cb9..e33e4733b 100644
--- a/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApiReader.java
+++ b/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApiReader.java
@@ -46,9 +46,11 @@ public final class ModelApiReader {
         }
         docElement.normalize();
         ModelApi api = new ModelApi();
+
         
api.setDisplayName(UtilXml.checkEmpty(docElement.getAttribute("displayName")).intern());
         
api.setName(UtilXml.checkEmpty(docElement.getAttribute("name")).intern());
         
api.setDescription(UtilXml.checkEmpty(docElement.getAttribute("description")).intern());
+        
api.setPath(UtilXml.checkEmpty(docElement.getAttribute("path")).intern());
         
api.setPublish(Boolean.parseBoolean(UtilXml.checkEmpty(docElement.getAttribute("publish")).intern()));
         for (Element resourceEle : UtilXml.childElementList(docElement, 
"resource")) {
             createModelResource(resourceEle, api);
diff --git 
a/rest-api/src/main/java/org/apache/ofbiz/ws/rs/openapi/OFBizOpenApiReader.java 
b/rest-api/src/main/java/org/apache/ofbiz/ws/rs/openapi/OFBizOpenApiReader.java
index bc585ce9a..7c2dc76ba 100644
--- 
a/rest-api/src/main/java/org/apache/ofbiz/ws/rs/openapi/OFBizOpenApiReader.java
+++ 
b/rest-api/src/main/java/org/apache/ofbiz/ws/rs/openapi/OFBizOpenApiReader.java
@@ -18,6 +18,7 @@
  
*******************************************************************************/
 package org.apache.ofbiz.ws.rs.openapi;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -109,11 +110,14 @@ public final class OFBizOpenApiReader extends Reader 
implements OpenApiReader {
             }
             List<ModelResource> resources = v.getResources();
             resources.forEach(modelResource -> {
+                List<String> segments = new ArrayList<>();
+                segments.add(v.getPath());
                 Tag resourceTab = new 
Tag().name(modelResource.getDisplayName()).description(modelResource.getDescription());
                 openApi.addTagsItem(resourceTab);
-                String basePath = modelResource.getPath();
+                segments.add(modelResource.getPath());
                 for (ModelOperation op : modelResource.getOperations()) {
-                    String uri = basePath + op.getPath();
+                    segments.add(op.getPath());
+                    String uri = buildNestedUrl(segments);
                     boolean pathExists = false;
                     PathItem pathItemObject = paths.get(uri);
                     if (UtilValidate.isEmpty(pathItemObject)) {
@@ -166,13 +170,28 @@ public final class OFBizOpenApiReader extends Reader 
implements OpenApiReader {
                     addServiceOperationApiResponses(service, operation);
                     setPathItemOperation(pathItemObject, verb.toUpperCase(), 
operation);
                     if (!pathExists) {
-                        paths.addPathItem(basePath + op.getPath(), 
pathItemObject);
+                        paths.addPathItem(uri, pathItemObject);
                     }
                 }
             });
         });
     }
 
+    public static String buildNestedUrl(List<String> segments) {
+        StringBuilder pathBuilder = new StringBuilder();
+        for (String segment : segments) {
+            if (segment == null || segment.trim().isEmpty()) {
+                continue;
+            }
+
+            // Trim leading/trailing slashes
+            segment = segment.replaceAll("^/+", "").replaceAll("/+$", "");
+            if (!segment.isEmpty()) {
+                pathBuilder.append("/").append(segment);
+            }
+        }
+        return pathBuilder.toString();
+    }
     private void addExportableServices() {
         Set<String> serviceNames = context.getAllServiceNames();
         for (String serviceName : serviceNames) {

Reply via email to