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

kpvdr pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-interop-test.git

commit c4b1f314a0642d2b2c59bfc4196f2c720d950c70
Author: QIT Development Team <[email protected]>
AuthorDate: Fri Jul 17 23:34:35 2026 -0400

    Phase 2b.5: Add Java ProtonJ2 JMS emulation
    
    Complete full 6×6 unified JMS interoperability test matrix (180 tests).
    
    Changes:
    - Java ProtonJ2 shim: Add --jms-mode flag support
    - Java sender: Add JMS message type annotation via message.annotation()
    - Java receiver: Detect and decode JMS TextMessage via 
hasAnnotation()/annotation()
    - Enable java-protonj2 in ENABLED_CLIENTS list
    
    Technical details:
    - Used message.annotation("x-opt-jms-msg-type", byte) for annotation
    - Used message.hasAnnotation() and annotation() for detection
    - Map AMQP string → JMS TextMessage (type 5)
    - Decode JMS TextMessage as type 'text'
    
    Test results: 180/180 tests passing locally (6×6 matrix)
    - All 6 AMQP clients now support JMS TextMessage emulation
    - Full interoperability achieved across Python, JMS, JavaScript, C++, .NET, 
Java ProtonJ2
    
    Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
---
 .../main/java/org/apache/qpid/qit/Receiver.java    | 62 +++++++++++++++++++++-
 .../src/main/java/org/apache/qpid/qit/Sender.java  | 49 ++++++++++++++++-
 tests/test_jms_unified.py                          | 30 +++++++++--
 3 files changed, 133 insertions(+), 8 deletions(-)

diff --git 
a/shims/java-protonj2/src/main/java/org/apache/qpid/qit/Receiver.java 
b/shims/java-protonj2/src/main/java/org/apache/qpid/qit/Receiver.java
index ea48940..2454dd2 100644
--- a/shims/java-protonj2/src/main/java/org/apache/qpid/qit/Receiver.java
+++ b/shims/java-protonj2/src/main/java/org/apache/qpid/qit/Receiver.java
@@ -73,7 +73,24 @@ public class Receiver {
                 }
 
                 Message<?> message = delivery.message();
-                TypeCodec.DecodedMessage decoded = 
TypeCodec.decode(message.body());
+
+                // Check for JMS message type annotation
+                byte jmsType = -1;
+                if (message.hasAnnotation("x-opt-jms-msg-type")) {
+                    Object annotation = 
message.annotation("x-opt-jms-msg-type");
+                    if (annotation instanceof Byte) {
+                        jmsType = (Byte) annotation;
+                    }
+                }
+
+                TypeCodec.DecodedMessage decoded;
+                if (jmsType >= 0) {
+                    // Decode as JMS message
+                    decoded = decodeJmsMessage(message.body(), jmsType);
+                } else {
+                    // Decode as regular AMQP message
+                    decoded = TypeCodec.decode(message.body());
+                }
 
                 JsonObject msgResult = new JsonObject();
                 msgResult.addProperty("index", i);
@@ -113,4 +130,47 @@ public class Receiver {
         URI uri = new URI(broker);
         return uri;
     }
+
+    private static TypeCodec.DecodedMessage decodeJmsMessage(Object body, byte 
jmsType) {
+        // JMS message type constants
+        final byte JMS_MESSAGE = 0;
+        final byte JMS_TEXT_MESSAGE = 5;
+        final byte JMS_BYTES_MESSAGE = 3;
+
+        if (jmsType == JMS_TEXT_MESSAGE) {
+            // TextMessage: body is string in AmqpValue section
+            TypeCodec.DecodedMessage result = new TypeCodec.DecodedMessage();
+            result.type = "text";  // Use 'text' to match JMS shim output
+            if (body instanceof String) {
+                result.value = new com.google.gson.JsonPrimitive((String) 
body);
+            } else {
+                result.value = com.google.gson.JsonNull.INSTANCE;
+            }
+            return result;
+        } else if (jmsType == JMS_BYTES_MESSAGE) {
+            // BytesMessage: body is binary in Data section
+            TypeCodec.DecodedMessage result = new TypeCodec.DecodedMessage();
+            result.type = "bytes";
+            if (body instanceof byte[]) {
+                byte[] bytes = (byte[]) body;
+                StringBuilder hex = new StringBuilder();
+                for (byte b : bytes) {
+                    hex.append(String.format("%02x", b));
+                }
+                result.value = new 
com.google.gson.JsonPrimitive(hex.toString());
+            } else {
+                result.value = com.google.gson.JsonNull.INSTANCE;
+            }
+            return result;
+        } else if (jmsType == JMS_MESSAGE) {
+            // Empty message
+            TypeCodec.DecodedMessage result = new TypeCodec.DecodedMessage();
+            result.type = "null";
+            result.value = com.google.gson.JsonNull.INSTANCE;
+            return result;
+        }
+
+        // Unknown JMS type, fall back to regular AMQP decoding
+        return TypeCodec.decode(body);
+    }
 }
diff --git a/shims/java-protonj2/src/main/java/org/apache/qpid/qit/Sender.java 
b/shims/java-protonj2/src/main/java/org/apache/qpid/qit/Sender.java
index 84f2e32..ba07cab 100644
--- a/shims/java-protonj2/src/main/java/org/apache/qpid/qit/Sender.java
+++ b/shims/java-protonj2/src/main/java/org/apache/qpid/qit/Sender.java
@@ -24,9 +24,24 @@ public class Sender {
         String queue = null;
         String type = null;
         String data = null;
+        boolean jmsMode = false;
 
-        for (int i = 1; i < args.length; i += 2) {
-            String key = args[i].replace("--", "");
+        for (int i = 1; i < args.length; i++) {
+            String arg = args[i];
+
+            // Check for flags (no value)
+            if ("--jms-mode".equals(arg)) {
+                jmsMode = true;
+                continue;
+            }
+
+            // Regular options (key-value pairs)
+            if (i + 1 >= args.length) {
+                System.err.println("Missing value for option: " + arg);
+                System.exit(1);
+            }
+
+            String key = arg.replace("--", "");
             String value = args[i + 1];
 
             switch (key) {
@@ -43,6 +58,7 @@ public class Sender {
                     data = value;
                     break;
             }
+            i++;  // Skip the value in next iteration
         }
 
         if (broker == null || queue == null || type == null || data == null) {
@@ -78,6 +94,16 @@ public class Sender {
                 message.messageId(String.valueOf(index));
                 message.body(TypeCodec.encode(type, value));
 
+                // Add JMS annotations if in JMS mode
+                if (jmsMode) {
+                    byte jmsType = getJmsMessageType(type);
+                    if (jmsType >= 0) {
+                        // NOTE: Key MUST be Symbol, value MUST be signed byte
+                        // This matches Qpid JMS Client wire format
+                        message.annotation("x-opt-jms-msg-type", jmsType);
+                    }
+                }
+
                 sender.send(message);
 
                 // Record sent message
@@ -122,4 +148,23 @@ public class Sender {
         URI uri = new URI(broker);
         return uri;
     }
+
+    private static byte getJmsMessageType(String amqpType) {
+        // JMS message type constants (from Qpid JMS Client)
+        final byte JMS_MESSAGE = 0;        // Empty message
+        final byte JMS_TEXT_MESSAGE = 5;   // String/text
+        final byte JMS_BYTES_MESSAGE = 3;  // Binary data
+
+        // Map AMQP types to JMS message types
+        switch (amqpType) {
+            case "string":
+                return JMS_TEXT_MESSAGE;
+            case "binary":
+                return JMS_BYTES_MESSAGE;
+            case "null":
+                return JMS_MESSAGE;
+            default:
+                return -1;  // Invalid
+        }
+    }
 }
diff --git a/tests/test_jms_unified.py b/tests/test_jms_unified.py
index fcf0ac5..1f00859 100644
--- a/tests/test_jms_unified.py
+++ b/tests/test_jms_unified.py
@@ -55,7 +55,7 @@ ENABLED_CLIENTS = [
     "javascript-rhea",  # Phase 2b.2 ✅
     "cpp-proton",       # Phase 2b.3 ✅
     "dotnet-proton",    # Phase 2b.4 ✅
-    # "java-protonj2",    # Phase 2b.5 (future)
+    "java-protonj2",    # Phase 2b.5 ✅
 ]
 
 # Client metadata
@@ -82,8 +82,8 @@ CLIENT_INFO = {
     },
     "java-protonj2": {
         "name": "Java ProtonJ2",
-        "shim_path": "shims/java-protonj2/sender.sh",
-        "jms_mode": True,  # Will support JMS emulation (Phase 2b.5)
+        "shim_path": "shims/java-protonj2/shim.sh",
+        "jms_mode": True,  # Phase 2b.5 ✅
     },
     "jms": {
         "name": "Qpid JMS Client",
@@ -195,8 +195,19 @@ def run_sender(
         ]
         if client_info["jms_mode"]:
             cmd.append("--jms-mode")
+    elif client == "java-protonj2":
+        # Java ProtonJ2 sender with JMS emulation
+        cmd = [
+            str(shim_path),
+            "send",
+            "--broker", f"amqp://{broker_url}",
+            "--queue", queue,
+            "--type", "string",
+            "--data", json.dumps(messages),
+        ]
+        if client_info["jms_mode"]:
+            cmd.append("--jms-mode")
     else:
-        # Future: Java ProtonJ2
         pytest.skip(f"Sender for {client} not yet implemented")
 
     result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
@@ -267,8 +278,17 @@ def run_receiver(
             "--count", str(count),
             "--timeout", str(timeout),
         ]
+    elif client == "java-protonj2":
+        # Java ProtonJ2 receiver (automatically detects JMS annotation)
+        cmd = [
+            str(shim_path),
+            "receive",
+            "--broker", f"amqp://{broker_url}",
+            "--queue", queue,
+            "--count", str(count),
+            "--timeout", str(timeout),
+        ]
     else:
-        # Future: Java ProtonJ2
         pytest.skip(f"Receiver for {client} not yet implemented")
 
     result = subprocess.run(cmd, capture_output=True, text=True, 
timeout=timeout + 10)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to