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) {