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 <[email protected]>
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) {