This is an automated email from the ASF dual-hosted git repository.

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git

commit e2db339418c6c4b1922950a8cbe812d729bfedf3
Author: Robert Lazarski <[email protected]>
AuthorDate: Tue May 5 03:06:36 2026 -1000

    AXIS2-6104 Bring Moshi JSON-RPC receivers to feature parity with Gson
    
    The Moshi rpc/ package (JsonRpcMessageReceiver, 
JsonInOnlyRPCMessageReceiver,
    JsonUtils) now has identical error handling to the Gson equivalents:
    
    - JsonRpcFaultException detection: unwraps InvocationTargetException,
      checks for JsonRpcFaultException, sets HTTP status + structured
      Axis2JsonErrorResponse on the outgoing MessageContext
    - createSecureFault: new method in moshi JsonUtils with the same
      CWE-209-safe opaque errorRef pattern (both 1-arg and 4-arg overloads)
    - ITE unwrap: all catch blocks now log the root cause, not the wrapper
    
    The shared classes (Axis2JsonErrorResponse, JsonRpcFaultException,
    PaginatedResponse, PaginationRequest) live in gson.rpc and are
    referenced by both implementations — they contain no Gson-specific
    imports and are serialization-library-agnostic POJOs.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 .../moshi/rpc/JsonInOnlyRPCMessageReceiver.java    | 31 ++++++------
 .../json/moshi/rpc/JsonRpcMessageReceiver.java     | 55 +++++++++++++++-------
 .../org/apache/axis2/json/moshi/rpc/JsonUtils.java | 51 ++++++++++++++++++++
 3 files changed, 105 insertions(+), 32 deletions(-)

diff --git 
a/modules/json/src/org/apache/axis2/json/moshi/rpc/JsonInOnlyRPCMessageReceiver.java
 
b/modules/json/src/org/apache/axis2/json/moshi/rpc/JsonInOnlyRPCMessageReceiver.java
index 4514906a8c..a46430f11e 100644
--- 
a/modules/json/src/org/apache/axis2/json/moshi/rpc/JsonInOnlyRPCMessageReceiver.java
+++ 
b/modules/json/src/org/apache/axis2/json/moshi/rpc/JsonInOnlyRPCMessageReceiver.java
@@ -35,7 +35,7 @@ import java.lang.reflect.Method;
 
 public class JsonInOnlyRPCMessageReceiver extends RPCInOnlyMessageReceiver {
     private static final Log log = 
LogFactory.getLog(JsonInOnlyRPCMessageReceiver.class);
-    
+
     @Override
     public void invokeBusinessLogic(MessageContext inMessage) throws AxisFault 
{
         Object tempObj = inMessage.getProperty(JsonConstant.IS_JSON_STREAM);
@@ -70,7 +70,6 @@ public class JsonInOnlyRPCMessageReceiver extends 
RPCInOnlyMessageReceiver {
     }
 
     public void invokeService(JsonReader jsonReader, Object serviceObj, String 
operation_name, String enableJSONOnly) throws AxisFault {
-        String msg;
         Class implClass = serviceObj.getClass();
         Method[] allMethods = implClass.getDeclaredMethods();
         Method method = JsonUtils.getOpMethod(operation_name, allMethods);
@@ -78,22 +77,22 @@ public class JsonInOnlyRPCMessageReceiver extends 
RPCInOnlyMessageReceiver {
         try {
             int paramCount = paramClasses.length;
             JsonUtils.invokeServiceClass(jsonReader, serviceObj, method, 
paramClasses, paramCount, enableJSONOnly);
-        } catch (IllegalAccessException e) {
-            msg = "Does not have access to " +
-                    "the definition of the specified class, field, method or 
constructor";
-            log.error(msg, e);
-            throw AxisFault.makeFault(e);
-
         } catch (InvocationTargetException e) {
-            msg = "Exception occurred while trying to invoke service method " +
-                    (method != null ? method.getName() : "null");
-            log.error(msg, e);
-            throw AxisFault.makeFault(e);
+            // Unwrap the ITE to log the real cause, not the reflection 
wrapper.
+            Throwable cause = e.getCause();
+            Exception rootCause;
+            if (cause instanceof Exception) {
+                rootCause = (Exception) cause;
+            } else if (cause != null) {
+                rootCause = new RuntimeException("Service threw non-Exception 
Throwable", cause);
+            } else {
+                rootCause = e;
+            }
+            throw JsonUtils.createSecureFault(rootCause, operation_name, 
false);
+        } catch (IllegalAccessException e) {
+            throw JsonUtils.createSecureFault(e, operation_name, false);
         } catch (IOException e) {
-            msg = "Exception occur while encording or " +
-                    "access to the input string at the JsonRpcMessageReceiver";
-            log.error(msg, e);
-            throw AxisFault.makeFault(e);
+            throw JsonUtils.createSecureFault(e, operation_name, true);
         }
     }
 }
diff --git 
a/modules/json/src/org/apache/axis2/json/moshi/rpc/JsonRpcMessageReceiver.java 
b/modules/json/src/org/apache/axis2/json/moshi/rpc/JsonRpcMessageReceiver.java
index 0c74df2010..2e6fed4a04 100644
--- 
a/modules/json/src/org/apache/axis2/json/moshi/rpc/JsonRpcMessageReceiver.java
+++ 
b/modules/json/src/org/apache/axis2/json/moshi/rpc/JsonRpcMessageReceiver.java
@@ -20,8 +20,11 @@ package org.apache.axis2.json.moshi.rpc;
 
 import com.squareup.moshi.JsonReader;
 import org.apache.axis2.AxisFault;
+import org.apache.axis2.Constants;
 import org.apache.axis2.context.MessageContext;
 import org.apache.axis2.description.AxisOperation;
+import org.apache.axis2.json.gson.rpc.Axis2JsonErrorResponse;
+import org.apache.axis2.json.gson.rpc.JsonRpcFaultException;
 import org.apache.axis2.json.moshi.MoshiXMLStreamReader;
 import org.apache.axis2.json.factory.JsonConstant;
 import org.apache.axis2.rpc.receivers.RPCMessageReceiver;
@@ -35,7 +38,7 @@ import java.lang.reflect.Method;
 
 public class JsonRpcMessageReceiver extends RPCMessageReceiver {
     private static final Log log = LogFactory.getLog(RPCMessageReceiver.class);
-    
+
     @Override
     public void invokeBusinessLogic(MessageContext inMessage, MessageContext 
outMessage) throws AxisFault {
         Object tempObj = inMessage.getProperty(JsonConstant.IS_JSON_STREAM);
@@ -69,7 +72,6 @@ public class JsonRpcMessageReceiver extends 
RPCMessageReceiver {
     }
 
     public void invokeService(JsonReader jsonReader, Object serviceObj, String 
operation_name, MessageContext outMes, String enableJSONOnly) throws AxisFault {
-        String msg;
         Class implClass = serviceObj.getClass();
         Method[] allMethods = implClass.getDeclaredMethods();
         Method method = JsonUtils.getOpMethod(operation_name, allMethods);
@@ -82,22 +84,43 @@ public class JsonRpcMessageReceiver extends 
RPCMessageReceiver {
             outMes.setProperty(JsonConstant.RETURN_OBJECT, retObj);
             outMes.setProperty(JsonConstant.RETURN_TYPE, 
method.getReturnType());
 
-        } catch (IllegalAccessException e) {
-            msg = "Does not have access to " +
-                    "the definition of the specified class, field, method or 
constructor";
-            log.error(msg, e);
-            throw AxisFault.makeFault(e);
-
         } catch (InvocationTargetException e) {
-            msg = "Exception occurred while trying to invoke service method " +
-                    (method != null ? method.getName() : "null");
-            log.error(msg, e);
-            throw AxisFault.makeFault(e);
+            // Method.invoke() wraps any exception thrown by the service method
+            // in InvocationTargetException.  Unwrap to inspect the real cause.
+            Throwable cause = e.getCause();
+
+            if (cause instanceof JsonRpcFaultException) {
+                // ── Structured error path 
────────────────────────────────────
+                // Service explicitly signaled a typed error (e.g. validation 
422).
+                // Set the HTTP status code and put the error response as 
RETURN_OBJECT
+                // so the Moshi formatter serializes it as a normal JSON body 
— NOT
+                // through the SOAP fault path.
+                JsonRpcFaultException fault = (JsonRpcFaultException) cause;
+                Axis2JsonErrorResponse errorResponse = 
fault.getErrorResponse();
+                log.warn("[errorRef=" + errorResponse.getErrorRef() + "] " +
+                        errorResponse.getError() + " in operation '" + 
operation_name +
+                        "': " + errorResponse.getMessage());
+                outMes.setProperty(Constants.HTTP_RESPONSE_STATE,
+                        String.valueOf(fault.getHttpStatusCode()));
+                outMes.setProperty(JsonConstant.RETURN_OBJECT, errorResponse);
+                outMes.setProperty(JsonConstant.RETURN_TYPE, 
Axis2JsonErrorResponse.class);
+            } else {
+                // ── Opaque error path 
────────────────────────────────────────
+                // Unexpected exception — create a CWE-209-safe AxisFault.
+                Exception rootCause;
+                if (cause instanceof Exception) {
+                    rootCause = (Exception) cause;
+                } else if (cause != null) {
+                    rootCause = new RuntimeException("Service threw 
non-Exception Throwable", cause);
+                } else {
+                    rootCause = e;
+                }
+                throw JsonUtils.createSecureFault(rootCause, operation_name, 
false, outMes);
+            }
+        } catch (IllegalAccessException e) {
+            throw JsonUtils.createSecureFault(e, operation_name, false, 
outMes);
         } catch (IOException e) {
-            msg = "Exception occur while encording or " +
-                    "access to the input string at the JsonRpcMessageReceiver";
-            log.error(msg, e);
-            throw AxisFault.makeFault(e);
+            throw JsonUtils.createSecureFault(e, operation_name, true, outMes);
         }
     }
 }
diff --git a/modules/json/src/org/apache/axis2/json/moshi/rpc/JsonUtils.java 
b/modules/json/src/org/apache/axis2/json/moshi/rpc/JsonUtils.java
index ca3d94c9c8..9db4673ece 100644
--- a/modules/json/src/org/apache/axis2/json/moshi/rpc/JsonUtils.java
+++ b/modules/json/src/org/apache/axis2/json/moshi/rpc/JsonUtils.java
@@ -28,6 +28,12 @@ import com.squareup.moshi.JsonWriter;
 import com.squareup.moshi.Moshi;
 import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter;
 
+import org.apache.axis2.AxisFault;
+import org.apache.axis2.Constants;
+import org.apache.axis2.context.MessageContext;
+import org.apache.axis2.json.factory.JsonConstant;
+import org.apache.axis2.json.gson.rpc.Axis2JsonErrorResponse;
+
 import java.io.IOException;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationTargetException;
@@ -36,6 +42,7 @@ import java.lang.reflect.Type;
 import java.util.Date;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 import jakarta.annotation.Nullable;
 
 
@@ -147,4 +154,48 @@ public class JsonUtils {
         return null;
     }
 
+    /**
+     * Build a secure {@link AxisFault} for any unexpected failure in the 
Moshi JSON-RPC
+     * message receivers.  Mirrors the Gson equivalent in
+     * {@link org.apache.axis2.json.gson.rpc.JsonUtils#createSecureFault}.
+     *
+     * <p>The full context is logged server-side under an opaque correlation 
ID;
+     * only {@code "Bad Request [errorRef=<uuid>]"} or
+     * {@code "Internal Server Error [errorRef=<uuid>]"} is returned to the 
caller.
+     * This prevents information disclosure (CWE-209).
+     */
+    static AxisFault createSecureFault(Exception e, String operationName, 
boolean isParsingError) {
+        return createSecureFault(e, operationName, isParsingError, null);
+    }
+
+    /**
+     * Build a secure {@link AxisFault} and, when an outgoing {@link 
MessageContext}
+     * is available, also set the structured {@link Axis2JsonErrorResponse} as 
the
+     * return object with the appropriate HTTP status code.
+     */
+    static AxisFault createSecureFault(Exception e, String operationName, 
boolean isParsingError,
+                                        MessageContext outMessage) {
+        String errorRef = UUID.randomUUID().toString();
+        String opDisplay = operationName != null ? operationName : "<unknown>";
+        Axis2JsonErrorResponse errorResponse;
+        int httpStatus;
+        if (isParsingError) {
+            log.error("[errorRef=" + errorRef + "] Bad Request parsing 
JSON-RPC body " +
+                    "for operation '" + opDisplay + "': " + e.getMessage(), e);
+            errorResponse = Axis2JsonErrorResponse.badRequest(errorRef);
+            httpStatus = 400;
+        } else {
+            log.error("[errorRef=" + errorRef + "] Internal error invoking 
operation '" +
+                    opDisplay + "': " + e.getMessage(), e);
+            errorResponse = Axis2JsonErrorResponse.internalError(errorRef);
+            httpStatus = 500;
+        }
+        if (outMessage != null) {
+            outMessage.setProperty(Constants.HTTP_RESPONSE_STATE, 
String.valueOf(httpStatus));
+            outMessage.setProperty(JsonConstant.RETURN_OBJECT, errorResponse);
+            outMessage.setProperty(JsonConstant.RETURN_TYPE, 
Axis2JsonErrorResponse.class);
+        }
+        return new AxisFault(errorResponse.getMessage());
+    }
+
 }

Reply via email to