You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by de...@apache.org on 2025/08/21 10:04:31 UTC
(ofbiz-plugins) 01/01: Added support for multiple rest.xml files per component (OFBIZ-13288)
This is an automated email from the ASF dual-hosted git repository.
deepak pushed a commit to branch OFBIZ-13288-multiple-rest-file
in repository https://gitbox.apache.org/repos/asf/ofbiz-plugins.git
commit 1abc3bd3bd2718a4c48f1e094e4aa40ae37bbbd2
Author: Deepak Dixit <de...@hotwax.co>
AuthorDate: Thu Aug 21 15:34:15 2025 +0530
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
---
example/api/example.rest.xml | 3 +-
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 ++++++++++++++--
6 files changed, 71 insertions(+), 15 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/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 cc554c964..a9fe177ae 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
@@ -83,12 +83,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);
@@ -102,15 +116,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) {