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 3dece2e2e4 Implemented: return of additional / custom http status
codes including publishing to Swagger UI (OFBIZ-12516)
3dece2e2e4 is described below
commit 3dece2e2e407d5dea2225b8855f58574e14b734d
Author: Lukas Finster <[email protected]>
AuthorDate: Thu Aug 6 12:44:29 2026 +0200
Implemented: return of additional / custom http status codes including
publishing to Swagger UI (OFBIZ-12516)
* Allows the return of custom http status codes. Custom status codes can
* be defined in ResponseStatus.java with examples for swagger UI
* inOpenApiUtil. They are linked to a rest endpoint via the attribute
* addApiResponses via <my>.rest.xml
---
.../rest-api/api/exampleApiDefinition.rest.xml | 16 +++
framework/rest-api/dtd/rest-api.xsd | 1 +
framework/rest-api/servicedef/services.xml | 13 ++-
.../apache/ofbiz/ws/rs/core/ResponseStatus.java | 8 +-
.../apache/ofbiz/ws/rs/model/ModelApiReader.java | 3 +-
.../apache/ofbiz/ws/rs/model/ModelOperation.java | 41 ++++++-
.../ofbiz/ws/rs/openapi/OFBizOpenApiReader.java | 56 ++++++++-
.../org/apache/ofbiz/ws/rs/util/OpenApiUtil.java | 76 +++++++++----
.../org/apache/ofbiz/ws/rs/util/RestApiUtil.java | 15 ++-
.../ofbiz/ws/rs/test/RestTestHttpRequest.java | 125 +++++++++++++++++++++
.../apache/ofbiz/ws/rs/test/RestTestServices.java | 27 +++++
.../apache/ofbiz/ws/rs/util/RestApiUtilTest.java | 3 +-
framework/rest-api/testdef/rest-apiTests.xml | 3 +
13 files changed, 357 insertions(+), 30 deletions(-)
diff --git a/framework/rest-api/api/exampleApiDefinition.rest.xml
b/framework/rest-api/api/exampleApiDefinition.rest.xml
index ac3bb836ff..137f56de4f 100644
--- a/framework/rest-api/api/exampleApiDefinition.rest.xml
+++ b/framework/rest-api/api/exampleApiDefinition.rest.xml
@@ -31,6 +31,22 @@ under the License.
</operation>
</resource>
+ <!-- TEST-services -->
+ <!-- The exposed endpoints are neccessary for tests defined in
RestTestHttpRequest.java to run.-->
+ <!-- If you decide to remove / not publish this rest.xml, those tests need
to be commented out. -->
+
+ <resource name="orders" path="/returnSuccess">
+ <operation verb="post" consumes="application/json"
produces="application/json">
+ <service name="returnSuccess"/>
+ </operation>
+ </resource>
+
+ <resource name="orders" path="/returnSuccessButOverwriteStatusCode">
+ <operation verb="post" consumes="application/json"
produces="application/json">
+ <service name="returnSuccessButOverwriteStatusCode"/>
+ </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 44c28948a3..f193cff990 100644
--- a/framework/rest-api/dtd/rest-api.xsd
+++ b/framework/rest-api/dtd/rest-api.xsd
@@ -81,6 +81,7 @@ under the License.
<xs:attribute name="path" type="xs:string" use="optional"/>
<xs:attribute name="description" type="xs:string"/>
<xs:attribute name="auth" type="xs:boolean" default="true"/>
+ <xs:attribute name="addApiResponses" 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 0f91c1a509..ba0eb06b60 100644
--- a/framework/rest-api/servicedef/services.xml
+++ b/framework/rest-api/servicedef/services.xml
@@ -47,5 +47,16 @@ under the License.
location="org.apache.ofbiz.ws.rs.test.RestTestServices"
invoke="returnCustomErrorTest">
<description>TestService that returns a custom errorCode</description>
</service>
-
+
+ <service name="returnSuccess" engine="java"
+ location="org.apache.ofbiz.ws.rs.test.RestTestServices"
invoke="returnSuccess">
+ <description>TestService that returns success, but overwrites it with
a custom http status code</description>
+ </service>
+
+ <service name="returnSuccessButOverwriteStatusCode" engine="java"
+ location="org.apache.ofbiz.ws.rs.test.RestTestServices"
invoke="returnSuccessButOverwriteStatusCode">
+ <description>TestService that returns success, but overwrites it with
a custom http status code</description>
+ <attribute name="httpResponseStatus" type="java.lang.Integer"
mode="OUT" optional="false"/>
+ </service>
+
</services>
diff --git
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/core/ResponseStatus.java
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/core/ResponseStatus.java
index d85791f280..4e1e95268d 100644
---
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/core/ResponseStatus.java
+++
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/core/ResponseStatus.java
@@ -29,7 +29,13 @@ public final class ResponseStatus {
*
"https://tools.ietf.org/html/rfc4918#section-11.2">https://tools.ietf.org/html/rfc4918#section-11.2</a>
*/
UNPROCESSABLE_ENTITY(422, "Unprocessable Entity");
-
+ /** ============ Custom http Responses START =======
+ *
+ * List Custom Status Codes comma-separated here
+ * MY_CUSTOM_STATUS_CODE(999, "Custom Status Code");
+ *
+ * ============ Custom http Responses END =========
+ */
private final int code;
private final String reason;
private final Family family;
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 e8987b4962..72f5c3a1be 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
@@ -122,7 +122,8 @@ public final class ModelApiReader {
.produces(UtilXml.checkEmpty(operationEle.getAttribute("produces")).intern())
.consumes(UtilXml.checkEmpty(operationEle.getAttribute("consumes")).intern())
.description(UtilXml.checkEmpty(operationEle.getAttribute("description")).intern())
-
.auth(Boolean.parseBoolean(UtilXml.checkEmpty(operationEle.getAttribute("auth")).intern()));
+
.auth(Boolean.parseBoolean(UtilXml.checkEmpty(operationEle.getAttribute("auth")).intern()))
+
.addApiResponses(UtilXml.checkEmpty(operationEle.getAttribute("addApiResponses")).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 19ece9f210..995c52a71b 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
@@ -17,6 +17,11 @@
* under the License.
*******************************************************************************/
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 ModelOperation {
@@ -27,6 +32,7 @@ public class ModelOperation {
private String path;
private String description;
private boolean auth;
+ private String addApiResponses;
/**
* Returns whether the user is authenticated.
@@ -46,6 +52,39 @@ public class ModelOperation {
this.auth = auth;
}
+ /**
+ * @return the addApiResponses
+ */
+ public String getAddApiResponses() {
+ return addApiResponses;
+ }
+
+ /**
+ * @param addApiResponses the addApiResponses to set
+ */
+ public void setAddApiResponses(String addApiResponses) {
+ this.addApiResponses = addApiResponses;
+ }
+
+ /**
+ * @param addApiResponses the addApiResponses to set
+ */
+ public ModelOperation addApiResponses(String addApiResponses) {
+ this.addApiResponses = addApiResponses;
+ return this;
+ }
+
+ /**
+ * @return the addApiResponses as list
+ */
+ public List<String> getAddApiResponsesList() {
+ if (UtilValidate.isEmpty(addApiResponses)) {
+ return new ArrayList<>();
+ }
+ return StringUtil.split(addApiResponses, ",");
+ }
+
+
/**
* Sets whether this operation requires JWT authentication and returns
* this instance.
@@ -235,7 +274,7 @@ public class ModelOperation {
@Override
public String toString() {
return "service: " + service + ", path: " + path + ", verb: " + verb +
", description: " + description
- + ", produces: " + produces;
+ + ", produces: " + produces + ", addApiResponses:" +
addApiResponses;
}
}
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 48a934daf3..6c45403d7f 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
@@ -32,6 +32,7 @@ import org.apache.ofbiz.service.ModelParam;
import org.apache.ofbiz.service.ModelService;
import org.apache.ofbiz.webapp.WebAppUtil;
import org.apache.ofbiz.ws.rs.core.OFBizApiConfig;
+import org.apache.ofbiz.ws.rs.core.ResponseStatus;
import org.apache.ofbiz.ws.rs.listener.ApiContextListener;
import org.apache.ofbiz.ws.rs.model.ModelApi;
import org.apache.ofbiz.ws.rs.model.ModelMapping;
@@ -65,6 +66,7 @@ import jakarta.servlet.ServletContext;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.StatusType;
public final class OFBizOpenApiReader extends Reader implements OpenApiReader {
private static final String MODULE = OFBizOpenApiReader.class.getName();
@@ -187,8 +189,9 @@ public final class OFBizOpenApiReader extends Reader
implements OpenApiReader {
}
addServiceOutSchema(service);
- addServiceInSchema(service);
+ addServiceInSchema(service, op);
addServiceOperationApiResponses(service, operation);
+ addAdditionalOperationApiResponses(service, op, operation);
setPathItemOperation(pathItemObject, verb.toUpperCase(),
operation);
if (!pathExists) {
@@ -302,8 +305,12 @@ public final class OFBizOpenApiReader extends Reader
implements OpenApiReader {
schemas.put("api.response." + service.getName() + ".success",
OpenApiUtil.getOutSchema(service));
}
+ private void addServiceInSchema(ModelService service, ModelOperation op) {
+ schemas.put("api.request." + service.getName(),
OpenApiUtil.getInSchema(service, op));
+ }
+
private void addServiceInSchema(ModelService service) {
- schemas.put("api.request." + service.getName(),
OpenApiUtil.getInSchema(service));
+ schemas.put("api.request." + service.getName(),
OpenApiUtil.getInSchema(service, null));
}
private void addPredefinedSchemas() {
@@ -322,4 +329,49 @@ public final class OFBizOpenApiReader extends Reader
implements OpenApiReader {
operation.setResponses(apiResponsesObject);
}
+ private void addAdditionalOperationApiResponses(ModelService service,
ModelOperation op, Operation operation) {
+ ApiResponses apiResponsesObject = operation.getResponses();
+
+ if (apiResponsesObject == null) {
+ apiResponsesObject = new ApiResponses();
+ }
+
+ final ApiResponses apiResponsesObjectCopy = apiResponsesObject;
+ op.getAddApiResponsesList().forEach((statusCode) -> {
+
+ StatusType statusType =
Response.Status.fromStatusCode(Integer.valueOf(statusCode));
+ if (statusType == null) {
+ statusType =
ResponseStatus.Custom.fromStatusCode(Integer.valueOf(statusCode));
+ }
+
+ if (statusType != null) {
+ String schemaName = "";
+ ApiResponse customResponse =
OpenApiUtil.getCustomApiResponseByStatusCode(statusCode);
+ if (customResponse != null) {
+ apiResponsesObjectCopy.addApiResponse(statusCode,
customResponse);
+ Schema<?> schema = customResponse.getContent()
+ .get(jakarta.ws.rs.core.MediaType.APPLICATION_JSON)
+ .getSchema();
+
+ String ref = schema.get$ref();
+ schemaName = ref.substring(ref.lastIndexOf('/') + 1);
+ } else {
+ schemaName =
"api.response.service.".concat(service.getName()).concat(".").concat(String.valueOf(statusType.getStatusCode()));
+
+ ApiResponse response = new
ApiResponse().description(statusType.getReasonPhrase()).content(new
Content().addMediaType(
+ javax.ws.rs.core.MediaType.APPLICATION_JSON,
+ new MediaType().schema(new
Schema<>().$ref("#/components/schemas/" + schemaName))));
+ apiResponsesObjectCopy.addApiResponse(statusCode,
response);
+ }
+
+ if (statusType.getStatusCode() > 399) {
+ schemas.put(schemaName,
OpenApiUtil.getGenericErrorSchema(null));
+ } else {
+ schemas.put(schemaName, OpenApiUtil.getOutSchema(service));
+ }
+
+ }
+ });
+ }
+
}
diff --git
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/util/OpenApiUtil.java
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/util/OpenApiUtil.java
index 4a3897d0aa..9c060622db 100644
---
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/util/OpenApiUtil.java
+++
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/util/OpenApiUtil.java
@@ -40,6 +40,7 @@ import org.apache.ofbiz.webapp.WebAppUtil;
import org.apache.ofbiz.ws.rs.common.AuthenticationScheme;
import org.apache.ofbiz.ws.rs.core.ResponseStatus;
import org.apache.ofbiz.ws.rs.listener.ApiContextListener;
+import org.apache.ofbiz.ws.rs.model.ModelOperation;
import io.swagger.v3.oas.models.headers.Header;
import io.swagger.v3.oas.models.media.ArraySchema;
@@ -67,9 +68,16 @@ public final class OpenApiUtil {
private static final Map<String, String> LIST_TYPE_MAP = new HashMap<>();
private static final Map<String, String> CLASS_ALIAS = new HashMap<>();
private static final Map<String, Class<?>> JAVA_OPEN_API_MAP = new
HashMap<>();
- private static final Map<String, String> FIELD_TYPE_MAP = new
HashMap<String, String>();
+ private static final Map<String, String> FIELD_TYPE_MAP = new HashMap<>();
private static final Map<String, ApiResponse> RESPONSES = new HashMap<>();
private static final Map<String, Schema<?>> SCHEMAS = new HashMap<>();
+ private static final Map<String, ApiResponse> CUSTOM_RESPONSES = new
HashMap<>();
+ private static final Schema<?> GENERIC_ERROR_SCHEMA = new MapSchema()
+ .addProperty("statusCode", new IntegerSchema().description("HTTP
Status Code"))
+ .addProperty("statusDescription", new
StringSchema().description("HTTP Status Code Description"))
+ .addProperty("errorType", new StringSchema().description("Error
Type for the error"))
+ .addProperty("errorMessage", new StringSchema().description("Error
Message"));
+
static {
CLASS_ALIAS.put("String", "String");
@@ -161,21 +169,45 @@ public final class OpenApiUtil {
buildApiResponseSchemas();
buildApiResponses();
+ builCustomApiResponses();
}
private static void buildApiResponseSchemas() {
- Schema<?> genericErrorSchema = new MapSchema();
- genericErrorSchema.addProperty("statusCode", new
IntegerSchema().description("HTTP Status Code"));
-
- genericErrorSchema.addProperty("statusDescription", new
StringSchema().description("HTTP Status Code Description"));
- genericErrorSchema.addProperty("errorType", new
StringSchema().description("Error Type for the error"));
- genericErrorSchema.addProperty("errorMessage", new
StringSchema().description("Error Message"));
- SCHEMAS.put("api.response.unauthorized.noheader", genericErrorSchema);
- SCHEMAS.put("api.response.unauthorized.invalidtoken",
genericErrorSchema);
- SCHEMAS.put("api.response.forbidden", genericErrorSchema);
- SCHEMAS.put("api.response.service.badrequest", genericErrorSchema);
- SCHEMAS.put("api.response.service.unprocessableentity",
genericErrorSchema);
- SCHEMAS.put("api.response.service.methodnotallowed",
genericErrorSchema);
+ SCHEMAS.put("api.response.unauthorized.noheader",
GENERIC_ERROR_SCHEMA);
+ SCHEMAS.put("api.response.unauthorized.invalidtoken",
GENERIC_ERROR_SCHEMA);
+ SCHEMAS.put("api.response.forbidden", GENERIC_ERROR_SCHEMA);
+ SCHEMAS.put("api.response.service.badrequest", GENERIC_ERROR_SCHEMA);
+ SCHEMAS.put("api.response.service.unprocessableentity",
GENERIC_ERROR_SCHEMA);
+ SCHEMAS.put("api.response.service.methodnotallowed",
GENERIC_ERROR_SCHEMA);
+ }
+
+ /*
+ * Define your custom ApiResponses here
+ */
+ private static void builCustomApiResponses() {
+ /**
+ * Map<String, Object> customResponseExample = UtilMisc.toMap(
+ * "statusCode",
ResponseStatus.Custom.MY_CUSTOM_STATUS_CODE.getStatusCode(),
+ * "statusDescription",
ResponseStatus.Custom.MY_CUSTOM_STATUS_CODE.getReasonPhrase(),
+ * "errorType", "An Error Type",
+ * "errorCode", "1234",
+ * "errorMessage", "Something went Wrong.",
+ * "errorDescription", "A description of what went wrong.");
+ *
+ * final ApiResponse customResponse = new ApiResponse()
+ * .description("Custom Error: A description of said error.")
+ * .content(new Content()
+ *
.addMediaType(jakarta.ws.rs.core.MediaType.APPLICATION_JSON, new MediaType()
+ * .schema(new Schema<>()
+ * .$ref("#/components/schemas/" +
"api.response.custom.response.example"))
+ * .example(customResponseExample)));
+ *
+ *
CUSTOM_RESPONSES.put(String.valueOf(ResponseStatus.Custom.MY_CUSTOM_STATUS_CODE.getStatusCode()),
customResponse);
+ **/
+ }
+
+ public static ApiResponse getCustomApiResponseByStatusCode(String
statusCode) {
+ return CUSTOM_RESPONSES.get(statusCode);
}
/**
@@ -208,12 +240,12 @@ public final class OpenApiUtil {
"errorMessage", "Forbidden: Insufficient rights to perform
this API call.");
Map<String, Object> badRequestExample = UtilMisc.toMap("statusCode",
Response.Status.BAD_REQUEST.getStatusCode(),
"statusDescription",
Response.Status.BAD_REQUEST.getReasonPhrase(),
- "errorType", "ServiceValidationException",
+ "errorType", "ServiceValidationException", "errorCode", "1000",
"errorMessage", "createProduct validation failed. The request
contained invalid information and could not be processed.",
"errorDescription", "The following required parameter is
missing: [IN] [createProduct.internalName]");
Map<String, Object> unprocessableEntExample =
UtilMisc.toMap("statusCode",
ResponseStatus.Custom.UNPROCESSABLE_ENTITY.getStatusCode(),
"statusDescription",
ResponseStatus.Custom.UNPROCESSABLE_ENTITY.getReasonPhrase(),
- "errorType", "GenericEntityException",
+ "errorType", "GenericEntityException", "errorCode", "2000",
"errorMessage", "createProduct execution failed. The request
contained invalid information and could not be processed.",
"errorDescription", "StandardException: A truncation error was
encountered trying to shrink CHAR 'string' to length 1.");
Map<String, Object> methodNotAllowedExample =
UtilMisc.toMap("statusCode", Response.Status.METHOD_NOT_ALLOWED.getStatusCode(),
@@ -316,7 +348,7 @@ public final class OpenApiUtil {
* @param service the {@link ModelService} for which to build the input
schema
* @return an OpenAPI {@link Schema} representing the service request body
*/
- public static Schema<Object> getInSchema(ModelService service) {
+ public static Schema<Object> getInSchema(ModelService service,
ModelOperation op) {
Schema<Object> parentSchema = new Schema<Object>();
parentSchema.setDescription("In Schema for service: " +
service.getName() + " request");
parentSchema.setType("object");
@@ -431,14 +463,20 @@ public final class OpenApiUtil {
Schema<Object> dataSchema = new Schema<Object>();
parentSchema.addProperty("data", dataSchema);
service.getOutParamNamesMap().forEach((name, type) -> {
- Schema<?> attrSchema = getAttributeSchema(service,
service.getParam(name));
- if (attrSchema != null) {
- dataSchema.addProperty(name, getAttributeSchema(service,
service.getParam(name)));
+ if (!name.equals(RestApiUtil.RESPONSE_STATUS_KEY)) {
+ Schema<?> attrSchema = getAttributeSchema(service,
service.getParam(name));
+ if (attrSchema != null) {
+ dataSchema.addProperty(name, getAttributeSchema(service,
service.getParam(name)));
+ }
}
});
return parentSchema;
}
+ public static Schema<?> getGenericErrorSchema(ModelService service) {
+ return GENERIC_ERROR_SCHEMA;
+ }
+
private static boolean isTypeGenericEntityOrGenericValue(String type) {
if (type == null) {
return false;
diff --git
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/util/RestApiUtil.java
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/util/RestApiUtil.java
index d8907ab3eb..a8e2ab5cf0 100644
---
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/util/RestApiUtil.java
+++
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/util/RestApiUtil.java
@@ -41,11 +41,11 @@ import org.apache.ofbiz.ws.rs.core.ResponseStatus;
import org.apache.ofbiz.ws.rs.response.Error;
import org.apache.ofbiz.ws.rs.response.Success;
-import jakarta.ws.rs.core.Response.StatusType;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.ResponseBuilder;
+import jakarta.ws.rs.core.Response.StatusType;
public final class RestApiUtil {
@@ -58,15 +58,22 @@ public final class RestApiUtil {
}
/**
- * Builds a JSON success response with HTTP 200 and the given message and
data.
+ * Builds a JSON success response with HTTP 200 / CUSTOM-STATUS-CODE and
the given message and data.
+ * Checks for a custom status code within data. Defaults to HTTP 200 if
non present.
*
* @param message a human-readable success message
* @param data the response payload
* @return a JAX-RS {@link Response} with status 200 and a JSON {@link
Success} body
*/
public static Response success(String message, Object data) {
- Success success = new Success(Response.Status.OK.getStatusCode(),
Response.Status.OK.getReasonPhrase(), message, data);
- ResponseBuilder builder =
Response.status(Response.Status.OK).type(MediaType.APPLICATION_JSON).entity(success);
+ StatusType status = extractResponseCode(data);
+
+ if (status == null) {
+ status = Response.Status.OK;
+ }
+
+ Success success = new Success(status.getStatusCode(),
status.getReasonPhrase(), message, data);
+ ResponseBuilder builder =
Response.status(status.getStatusCode()).type(MediaType.APPLICATION_JSON).entity(success);
String linkHeaderValue = getPaginationLinkHeaderValue(data);
if (UtilValidate.isNotEmpty(linkHeaderValue)) {
builder.header("Link", linkHeaderValue);
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
new file mode 100644
index 0000000000..9a1c071717
--- /dev/null
+++
b/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/test/RestTestHttpRequest.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+
*******************************************************************************/
+package org.apache.ofbiz.ws.rs.test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Base64;
+
+import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.base.util.HttpClient;
+import org.apache.ofbiz.base.util.HttpClientException;
+import org.apache.ofbiz.base.util.SSLUtil;
+import org.apache.ofbiz.testtools.JunitJupiterTest;
+import org.apache.ofbiz.testtools.JupiterTestHelper;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+@JunitJupiterTest
+class RestTestHttpRequest implements JupiterTestHelper {
+
+ private static final String MODULE = RestTestHttpRequest.class.getName();
+ private static final String BASE_URL = "https://localhost:8443/rest";
+ private static String accessToken;
+ private static ObjectMapper mapper = new ObjectMapper();
+
+ private static HttpClient initHttpClient() {
+ HttpClient http = new HttpClient();
+ http.followRedirects(true);
+ http.setAllowUntrusted(true);
+ http.setHostVerificationLevel(SSLUtil.getHostCertNoCheck());
+ return http;
+ }
+
+ @BeforeAll
+ public static void init() {
+ String creds =
Base64.getEncoder().encodeToString(("admin:ofbiz").getBytes());
+
+ HttpClient fetchClient = initHttpClient();
+ fetchClient.setUrl(BASE_URL + "/auth/token");
+ fetchClient.setHeader("Authorization", "Basic " + creds);
+ fetchClient.setHeader("Accept", "application/json");
+ String response = "";
+ try {
+ response = fetchClient.post();
+ } catch (HttpClientException e) {
+ Debug.logError(e, "Error returning rest access token", "MODULE");
+ return;
+ }
+
+ try {
+ 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");
+ }
+ }
+
+ @Test
+ void returnSuccessreturnsExpectedStatusCode() throws Exception {
+ HttpClient client = initHttpClient();
+ client.setHeader("Content-Type", "application/json");
+ client.setHeader("Authorization", "Bearer " + accessToken);
+ client.setUrl(BASE_URL + "/exampleApi/returnSuccess");
+
+ String response = "";
+ try {
+ response = client.post();
+ } catch (HttpClientException e) {
+ Debug.logError(e, "Error returning rest access token", "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");
+ }
+
+ assertEquals(200, statusCode);
+ }
+
+ //Corresponding service overwrites statusCode with 201
+ @Test
+ void returnSuccessOverwriteStatusCode() throws Exception {
+ HttpClient client = initHttpClient();
+ client.setHeader("Content-Type", "application/json");
+ client.setHeader("Authorization", "Bearer " + accessToken);
+ client.setUrl(BASE_URL +
"/exampleApi//returnSuccessButOverwriteStatusCode");
+
+ String response = "";
+ try {
+ response = client.post();
+ } catch (HttpClientException e) {
+ Debug.logError(e, "Error returning rest access token", "AAAA");
+ }
+ 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");
+ }
+
+ assertEquals(201, statusCode);
+ }
+}
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 948c378bd8..9fa10f7d7f 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
@@ -23,6 +23,7 @@ import java.util.Map;
import org.apache.ofbiz.service.DispatchContext;
import org.apache.ofbiz.service.ModelService;
import org.apache.ofbiz.service.ServiceUtil;
+import org.apache.ofbiz.ws.rs.util.RestApiUtil;
public class RestTestServices {
@@ -38,4 +39,30 @@ public class RestTestServices {
result.put(ModelService.ERROR_CODE, 999);
return result;
}
+
+ // ============== Status Code Test Services =================== //
+ /**
+ * TestService returning a success
+ *
+ * @param dctx
+ * @param context
+ * @return result
+ */
+ public static Map<String, Object> returnSuccess(DispatchContext dctx,
Map<String, ? extends Object> context) {
+ Map<String, Object> result = ServiceUtil.returnSuccess();
+ return result;
+ }
+
+ /**
+ * TestService returning a success but explicitly returns status code 201
instead of default 200
+ *
+ * @param dctx
+ * @param context
+ * @return result
+ */
+ public static Map<String, Object>
returnSuccessButOverwriteStatusCode(DispatchContext dctx, Map<String, ? extends
Object> context) {
+ Map<String, Object> result = ServiceUtil.returnSuccess();
+ result.put(RestApiUtil.RESPONSE_STATUS_KEY, 201);
+ return result;
+ }
}
diff --git
a/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/util/RestApiUtilTest.java
b/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/util/RestApiUtilTest.java
index d8d79c8296..112fa2d74a 100644
---
a/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/util/RestApiUtilTest.java
+++
b/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/util/RestApiUtilTest.java
@@ -48,7 +48,8 @@ public final class RestApiUtilTest {
@Test
void testSuccess() {
String message = "Success";
- String data = "some data";
+ Map<String, Object> data = new HashMap<>();
+ data.put("dataKey", "dataValue");
Response response = RestApiUtil.success(message, data);
diff --git a/framework/rest-api/testdef/rest-apiTests.xml
b/framework/rest-api/testdef/rest-apiTests.xml
index 0a0d8a8ded..393a7bd642 100644
--- a/framework/rest-api/testdef/rest-apiTests.xml
+++ b/framework/rest-api/testdef/rest-apiTests.xml
@@ -24,4 +24,7 @@ under the License.
<test-case case-name="rest-api-services-tests">
<jupiter-test-suite
class-name="org.apache.ofbiz.ws.rs.test.RestServicesTests"/>
</test-case>
+ <test-case case-name="rest-api-http-requests">
+ <jupiter-test-suite
class-name="org.apache.ofbiz.ws.rs.test.RestTestHttpRequest"/>
+ </test-case>
</test-suite>