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
commit aeef97110f01af3a0db2d2bb88c190541b0a9d7f Author: Lukas Finster <[email protected]> AuthorDate: Wed Aug 5 11:10:50 2026 +0200 Implemented: Custom errorCodes independent from http status codes in rest-api component (OFBIZ-12580) --- framework/rest-api/servicedef/services.xml | 7 ++++ .../org/apache/ofbiz/ws/rs/response/Error.java | 47 +++++++++++++++++----- .../rs/spi/impl/GenericServiceExceptionMapper.java | 8 ++-- .../org/apache/ofbiz/ws/rs/util/RestApiUtil.java | 45 ++++++++++++++++++++- .../apache/ofbiz/ws/rs/test/RestServicesTests.java | 11 +++++ .../apache/ofbiz/ws/rs/test/RestTestServices.java | 41 +++++++++++++++++++ .../apache/ofbiz/ws/rs/util/RestApiUtilTest.java | 4 +- .../org/apache/ofbiz/service/ModelService.java | 1 + .../apache/ofbiz/service/ModelServiceReader.java | 1 + 9 files changed, 148 insertions(+), 17 deletions(-) diff --git a/framework/rest-api/servicedef/services.xml b/framework/rest-api/servicedef/services.xml index ed90202f09..0f91c1a509 100644 --- a/framework/rest-api/servicedef/services.xml +++ b/framework/rest-api/servicedef/services.xml @@ -41,4 +41,11 @@ under the License. <attribute name="Order" type="org.apache.ofbiz.ws.rs.examples.RestOrderExample" mode="IN" optional="false"/> <attribute name="orderId" type="String" mode="OUT" optional="true"/> </service> + + <!-- TestServices --> + <service name="returnCustomErrorTest" engine="java" + location="org.apache.ofbiz.ws.rs.test.RestTestServices" invoke="returnCustomErrorTest"> + <description>TestService that returns a custom errorCode</description> + </service> + </services> diff --git a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/response/Error.java b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/response/Error.java index 7ea073e4a2..edfb0d005f 100644 --- a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/response/Error.java +++ b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/response/Error.java @@ -34,8 +34,8 @@ public class Error { private String errorMessage; @JsonProperty("errorType") private String type; - @JsonProperty("errorDescription") - private String errorDesc; + private String errorCode; + private String errorDescription; private List<String> additionalErrors; /** @@ -133,8 +133,19 @@ public class Error { * <p>These methods allow chaining to populate error metadata in a concise way. * Each method sets a field and returns the same {@code Error} instance.</p> */ - public Error errorDesc(String errorDesc) { - this.setErrorDesc(errorDesc); + public Error errorDescription(String errorDescription) { + this.setErrorDescription(errorDescription); + return this; + } + + /** + * Fluent builder methods for constructing an {@code Error} response. + * + * <p>These methods allow chaining to populate error metadata in a concise way. + * Each method sets a field and returns the same {@code Error} instance.</p> + */ + public Error errorCode(String errorCode) { + this.setErrorCode(errorCode); return this; } @@ -183,6 +194,24 @@ public class Error { this.statusDescription = statusDescription; } + /** + * Returns the errorCode + * + * @return errorCode + */ + public String getErrorCode() { + return errorCode; + } + + /** + * Sets the errorCode + * + * @param errorCode the errorCode to set + */ + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + /** * Returns the error message. * @@ -224,17 +253,17 @@ public class Error { * * @return the error description */ - public String getErrorDesc() { - return errorDesc; + public String getErrorDescription() { + return errorDescription; } /** * Sets the error description. * - * @param errorDesc the error description to set + * @param errorDescription the error description to set */ - public void setErrorDesc(String errorDesc) { - this.errorDesc = errorDesc; + public void setErrorDescription(String errorDescription) { + this.errorDescription = errorDescription; } } diff --git a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/spi/impl/GenericServiceExceptionMapper.java b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/spi/impl/GenericServiceExceptionMapper.java index 78988ad546..905678158b 100644 --- a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/spi/impl/GenericServiceExceptionMapper.java +++ b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/spi/impl/GenericServiceExceptionMapper.java @@ -90,7 +90,7 @@ public class GenericServiceExceptionMapper implements jakarta.ws.rs.ext.Exceptio .statusCode(Response.Status.BAD_REQUEST.getStatusCode()) .description(Response.Status.BAD_REQUEST.getReasonPhrase()) .message(RestApiUtil.getErrorMessage(service, "GenericServiceValidationErrorMessage", request.getLocale())) - .errorDesc((validationException.getMessage())) + .errorDescription((validationException.getMessage())) .additionalErrors(validationException.getMessageList()); builder = Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON).entity(error); } else if (actualCause instanceof GenericNoSuchEntityException @@ -99,7 +99,7 @@ public class GenericServiceExceptionMapper implements jakarta.ws.rs.ext.Exceptio .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) .description(Response.Status.INTERNAL_SERVER_ERROR.getReasonPhrase()) .message(RestApiUtil.getErrorMessage(service, "NoSuchEntityDefaultMessage", request.getLocale())) - .errorDesc(ExceptionUtils.getRootCauseMessage(gse)); + .errorDescription(ExceptionUtils.getRootCauseMessage(gse)); builder = Response.status(Response.Status.INTERNAL_SERVER_ERROR).type(MediaType.APPLICATION_JSON) .entity(error); } else if (actualCause instanceof GenericEntityException) { @@ -108,7 +108,7 @@ public class GenericServiceExceptionMapper implements jakarta.ws.rs.ext.Exceptio .description(ResponseStatus.Custom.UNPROCESSABLE_ENTITY.getReasonPhrase()) .message(RestApiUtil.getErrorMessage(service, "GenericServiceExecutionGenericEntityOperationErrorMessage", request.getLocale())) - .errorDesc(ExceptionUtils.getRootCauseMessage(gse)); + .errorDescription(ExceptionUtils.getRootCauseMessage(gse)); builder = Response.status(ResponseStatus.Custom.UNPROCESSABLE_ENTITY).type(MediaType.APPLICATION_JSON) .entity(error); } else { @@ -117,7 +117,7 @@ public class GenericServiceExceptionMapper implements jakarta.ws.rs.ext.Exceptio .description(Response.Status.INTERNAL_SERVER_ERROR.getReasonPhrase()) .message(RestApiUtil.getErrorMessage(service, "GenericServiceExecutionGenericExceptionErrorMessage", request.getLocale())) - .errorDesc(ExceptionUtils.getRootCauseMessage(gse)); + .errorDescription(ExceptionUtils.getRootCauseMessage(gse)); builder = Response.status(Response.Status.INTERNAL_SERVER_ERROR).type(MediaType.APPLICATION_JSON) .entity(error); } 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 22e14ffab7..d8907ab3eb 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 @@ -33,6 +33,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import org.apache.ofbiz.base.util.UtilGenerics; import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.service.ModelService; @@ -40,6 +41,7 @@ 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; @@ -47,6 +49,7 @@ import jakarta.ws.rs.core.Response.ResponseBuilder; public final class RestApiUtil { + public static final String RESPONSE_STATUS_KEY = "httpResponseStatus"; private static final String DEFAULT_MSG_UI_LABEL_RESOURCE = "ApiUiLabels"; private static final String QUERY_STRING_SEPARATOR = "&"; @@ -168,9 +171,19 @@ public final class RestApiUtil { additionalErrorMessages.add(errorMessageList.get(i)); } } - Error error = new Error().type("ServiceError").statusCode(ResponseStatus.Custom.UNPROCESSABLE_ENTITY.getStatusCode()) + String errorCode = null; + if (!UtilValidate.isEmpty(result.get(ModelService.ERROR_CODE))) { + errorCode = result.get(ModelService.ERROR_CODE).toString(); + } + StatusType status = extractResponseCode(result); + if (status == null) { + status = ResponseStatus.Custom.UNPROCESSABLE_ENTITY; + } + + Error error = new Error().type("ServiceError").statusCode(status.getStatusCode()) .description(ResponseStatus.Custom.UNPROCESSABLE_ENTITY.getReasonPhrase()) - .message(getErrorMessage(service, "GenericServiceErrorMessage", locale)).errorDesc(errorMessage); + .message(getErrorMessage(service, "GenericServiceErrorMessage", locale)) + .errorDescription(errorMessage).errorCode(errorCode); if (!additionalErrorMessages.isEmpty()) { error.setAdditionalErrors(additionalErrorMessages); } @@ -624,4 +637,32 @@ public final class RestApiUtil { return value; } } + + /** + * Extracts the http status code from the service result to override the default. + * The parameter is removed from the resultMap to not show in the resulting data. + * @param data + * @return + */ + public static StatusType extractResponseCode(Object data) { + Map<String, ? extends Object> resultMap = UtilGenerics.<String, Object>checkMap(data, String.class, Object.class); + Integer statusCode = null; + + try { + statusCode = (Integer) resultMap.get(RESPONSE_STATUS_KEY); + } catch (ClassCastException e) { + // do nothing + } + + if (statusCode == null) { + return null; + } + resultMap.remove(RESPONSE_STATUS_KEY); + + StatusType statusType = Response.Status.fromStatusCode(statusCode); + if (statusType == null) { + statusType = ResponseStatus.Custom.fromStatusCode(statusCode); + } + return statusType; + } } diff --git a/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/test/RestServicesTests.java b/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/test/RestServicesTests.java index 17d9ec0b36..018d8f9ca5 100644 --- a/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/test/RestServicesTests.java +++ b/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/test/RestServicesTests.java @@ -30,6 +30,7 @@ import java.util.Map; import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.entity.GenericValue; +import org.apache.ofbiz.service.ModelService; import org.apache.ofbiz.service.ServiceUtil; import org.apache.ofbiz.testtools.JunitJupiterTest; import org.apache.ofbiz.testtools.JupiterTestHelper; @@ -229,4 +230,14 @@ public class RestServicesTests implements JupiterTestHelper { for (int i = 0; i < padding; i++) sb.append('='); return sb.toString(); } + + @Test + public void testReturnCustomErrorCode() throws Exception { + GenericValue adminLogin = getDelegator().findOne("UserLogin", UtilMisc.toMap("userLoginId", "admin"), false); + Map<String, Object> result = getDispatcher().runSync( + "returnCustomErrorTest", + UtilMisc.toMap("userLogin", (Object) adminLogin)); + String errorCode = (String) result.get(ModelService.ERROR_CODE); + assertTrue(errorCode.equals("999")); + } } 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 new file mode 100644 index 0000000000..948c378bd8 --- /dev/null +++ b/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/test/RestTestServices.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * 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 java.util.Map; + +import org.apache.ofbiz.service.DispatchContext; +import org.apache.ofbiz.service.ModelService; +import org.apache.ofbiz.service.ServiceUtil; + +public class RestTestServices { + + /** + * Testservice returning a custom ErrorCode + * + * @param dctx + * @param context + * @return result + */ + public static Map<String, Object> returnCustomErrorTest(DispatchContext dctx, Map<String, ? extends Object> context) { + Map<String, Object> result = ServiceUtil.returnError("Some error"); + result.put(ModelService.ERROR_CODE, 999); + 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 679dc99574..d8d79c8296 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 @@ -73,7 +73,7 @@ public final class RestApiUtilTest { assertEquals(expected.getAdditionalErrors(), actual.getAdditionalErrors()); assertEquals(expected.getClass(), actual.getClass()); - assertEquals(expected.getErrorDesc(), actual.getErrorDesc()); + assertEquals(expected.getErrorDescription(), actual.getErrorDescription()); assertEquals(expected.getErrorMessage(), actual.getErrorMessage()); assertEquals(expected.getStatusCode(), actual.getStatusCode()); assertEquals(expected.getStatusDescription(), actual.getStatusDescription()); @@ -153,7 +153,7 @@ public final class RestApiUtilTest { Error error = (Error) response.getEntity(); - assertEquals("errorOne", error.getErrorDesc()); + assertEquals("errorOne", error.getErrorDescription()); assertEquals(List.of("errorTwo", "errorThree"), error.getAdditionalErrors()); } } diff --git a/framework/service/src/main/java/org/apache/ofbiz/service/ModelService.java b/framework/service/src/main/java/org/apache/ofbiz/service/ModelService.java index 8fb9e511fd..3dfc3bc1d0 100644 --- a/framework/service/src/main/java/org/apache/ofbiz/service/ModelService.java +++ b/framework/service/src/main/java/org/apache/ofbiz/service/ModelService.java @@ -111,6 +111,7 @@ public class ModelService extends AbstractMap<String, Object> implements Seriali public static final String RESPOND_SUCCESS = "success"; public static final String RESPOND_ERROR = "error"; public static final String RESPOND_FAIL = "fail"; + public static final String ERROR_CODE = "errorCode"; public static final String ERROR_MESSAGE = "errorMessage"; public static final String ERROR_MESSAGE_LIST = "errorMessageList"; public static final String ERROR_MESSAGE_MAP = "errorMessageMap"; diff --git a/framework/service/src/main/java/org/apache/ofbiz/service/ModelServiceReader.java b/framework/service/src/main/java/org/apache/ofbiz/service/ModelServiceReader.java index 604868eda2..3beec16fb4 100644 --- a/framework/service/src/main/java/org/apache/ofbiz/service/ModelServiceReader.java +++ b/framework/service/src/main/java/org/apache/ofbiz/service/ModelServiceReader.java @@ -471,6 +471,7 @@ public final class ModelServiceReader implements Serializable { } // Add the default optional parameters + service.addParam(createInternalParam(ModelService.ERROR_CODE, "String", ModelService.OUT_PARAM)); service.addParam(createInternalParam(ModelService.RESPONSE_MESSAGE, "String", ModelService.OUT_PARAM)); service.addParam(createInternalParam(ModelService.ERROR_MESSAGE, "String", ModelService.OUT_PARAM)); service.addParam(createInternalParam(ModelService.ERROR_MESSAGE_LIST, "java.util.List", ModelService.OUT_PARAM));

