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

davsclaus pushed a commit to branch trace
in repository https://gitbox.apache.org/repos/asf/camel.git

commit bccd990728253442497b146258ca9255e013adb9
Author: Claus Ibsen <claus.ib...@gmail.com>
AuthorDate: Thu Oct 24 09:53:32 2024 +0200

    CAMEL-21382: camel-core - Tracing with headers that are Map type can cause 
ClassCastException when dumping as XML
---
 .../impl/debugger/DefaultBacklogDebugger.java      |   4 +
 .../debugger/DefaultBacklogTracerEventMessage.java |  29 +++--
 .../camel/impl/engine/CamelInternalProcessor.java  |   3 +
 .../management/BacklogTracerHeaderMapTest.java     | 131 +++++++++++++++++++++
 4 files changed, 156 insertions(+), 11 deletions(-)

diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogDebugger.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogDebugger.java
index 57bad2b4e6b..51c9025ed05 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogDebugger.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogDebugger.java
@@ -830,6 +830,7 @@ public final class DefaultBacklogDebugger extends 
ServiceSupport implements Back
         suspendedBreakpointMessages.computeIfPresent(
                 nodeId,
                 (nId, message) -> new DefaultBacklogTracerEventMessage(
+                        camelContext,
                         false, false, message.getUid(), 
message.getTimestamp(), message.getLocation(), message.getRouteId(),
                         message.getToNode(),
                         message.getExchangeId(),
@@ -876,6 +877,7 @@ public final class DefaultBacklogDebugger extends 
ServiceSupport implements Back
             JsonObject data = dumpAsJSonObject(exchange);
             BacklogTracerEventMessage msg
                     = new DefaultBacklogTracerEventMessage(
+                            camelContext,
                             first, false, uid, timestamp, source, routeId, 
toNode, exchangeId, false, false, data);
             suspendedBreakpointMessages.put(nodeId, msg);
 
@@ -948,6 +950,7 @@ public final class DefaultBacklogDebugger extends 
ServiceSupport implements Back
             JsonObject data = dumpAsJSonObject(exchange);
             BacklogTracerEventMessage msg
                     = new DefaultBacklogTracerEventMessage(
+                            camelContext,
                             false, false, uid, timestamp, source, routeId, 
toNode, exchangeId, false, false, data);
             suspendedBreakpointMessages.put(toNode, msg);
 
@@ -1037,6 +1040,7 @@ public final class DefaultBacklogDebugger extends 
ServiceSupport implements Back
             JsonObject data = dumpAsJSonObject(exchange);
             BacklogTracerEventMessage msg
                     = new DefaultBacklogTracerEventMessage(
+                            camelContext,
                             false, true, uid, timestamp, source, routeId, 
toNode, exchangeId, false, false, data);
             // we want to capture if there was an exception
             if (cause != null) {
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogTracerEventMessage.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogTracerEventMessage.java
index b7a6e862c50..157c55dbf83 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogTracerEventMessage.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogTracerEventMessage.java
@@ -19,6 +19,7 @@ package org.apache.camel.impl.debugger;
 import java.text.SimpleDateFormat;
 import java.util.Map;
 
+import org.apache.camel.CamelContext;
 import org.apache.camel.spi.BacklogTracerEventMessage;
 import org.apache.camel.support.MessageHelper;
 import org.apache.camel.util.StopWatch;
@@ -35,6 +36,7 @@ import static 
org.apache.camel.support.MessageHelper.dumpExceptionAsJSonObject;
  */
 public final class DefaultBacklogTracerEventMessage implements 
BacklogTracerEventMessage {
 
+    private final CamelContext camelContext;
     private final StopWatch watch;
     private final boolean first;
     private final boolean last;
@@ -62,9 +64,10 @@ public final class DefaultBacklogTracerEventMessage 
implements BacklogTracerEven
     private long duration;
     private boolean done;
 
-    public DefaultBacklogTracerEventMessage(boolean first, boolean last, long 
uid, long timestamp,
+    public DefaultBacklogTracerEventMessage(CamelContext camelContext, boolean 
first, boolean last, long uid, long timestamp,
                                             String location, String routeId, 
String toNode, String exchangeId,
                                             boolean rest, boolean template, 
JsonObject data) {
+        this.camelContext = camelContext;
         this.watch = new StopWatch();
         this.first = first;
         this.last = last;
@@ -353,11 +356,12 @@ public final class DefaultBacklogTracerEventMessage 
implements BacklogTracerEven
                     sb.append(" type=\"").append(type).append("\"");
                 }
                 sb.append(">");
-                String value = jo.getString("value");
+                Object value = jo.get("value");
                 if (value != null) {
                     try {
+                        String text = 
camelContext.getTypeConverter().tryConvertTo(String.class, value);
                         // must always xml encode
-                        sb.append(StringHelper.xmlEncode(value));
+                        sb.append(StringHelper.xmlEncode(text));
                     } catch (Exception e) {
                         // ignore
                     }
@@ -381,13 +385,14 @@ public final class DefaultBacklogTracerEventMessage 
implements BacklogTracerEven
                     sb.append(" type=\"").append(type).append("\"");
                 }
                 sb.append(">");
-                String value = jo.getString("value");
+                Object value = jo.get("value");
                 if (value != null) {
                     try {
+                        String text = 
camelContext.getTypeConverter().tryConvertTo(String.class, value);
                         // must always xml encode
-                        sb.append(StringHelper.xmlEncode(value));
+                        sb.append(StringHelper.xmlEncode(text));
                     } catch (Exception e) {
-                        // ignore as the body is for logging purpose
+                        // ignore
                     }
                 }
                 sb.append("</exchangeProperty>\n");
@@ -409,11 +414,12 @@ public final class DefaultBacklogTracerEventMessage 
implements BacklogTracerEven
                     sb.append(" type=\"").append(type).append("\"");
                 }
                 sb.append(">");
-                String value = jo.getString("value");
+                Object value = jo.get("value");
                 if (value != null) {
                     try {
+                        String text = 
camelContext.getTypeConverter().tryConvertTo(String.class, value);
                         // must always xml encode
-                        sb.append(StringHelper.xmlEncode(value));
+                        sb.append(StringHelper.xmlEncode(text));
                     } catch (Exception e) {
                         // ignore
                     }
@@ -440,13 +446,14 @@ public final class DefaultBacklogTracerEventMessage 
implements BacklogTracerEven
                 sb.append(" position=\"").append(position).append("\"");
             }
             sb.append(">");
-            String value = jo.getString("value");
+            Object value = jo.get("value");
             if (value != null) {
                 try {
+                    String text = 
camelContext.getTypeConverter().tryConvertTo(String.class, value);
                     // must always xml encode
-                    sb.append(StringHelper.xmlEncode(value));
+                    sb.append(StringHelper.xmlEncode(text));
                 } catch (Exception e) {
-                    // ignore as the body is for logging purpose
+                    // ignore
                 }
             }
             sb.append("</body>\n");
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java
index 870431dd5da..37f0f8b536a 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java
@@ -642,6 +642,7 @@ public class CamelInternalProcessor extends 
DelegateAsyncProcessor implements In
                     String source = 
LoggerHelper.getLineNumberLoggerName(routeDefinition);
                     final long created = exchange.getClock().getCreated();
                     DefaultBacklogTracerEventMessage pseudoFirst = new 
DefaultBacklogTracerEventMessage(
+                            camelContext,
                             true, false, 
backlogTracer.incrementTraceCounter(), created, source, routeId, null, 
exchangeId,
                             rest, template, data);
                     if (exchange.getFromEndpoint() instanceof 
EndpointServiceLocation esl) {
@@ -654,6 +655,7 @@ public class CamelInternalProcessor extends 
DelegateAsyncProcessor implements In
                 }
                 String source = 
LoggerHelper.getLineNumberLoggerName(processorDefinition);
                 DefaultBacklogTracerEventMessage event = new 
DefaultBacklogTracerEventMessage(
+                        camelContext,
                         false, false, backlogTracer.incrementTraceCounter(), 
timestamp, source, routeId, toNode, exchangeId,
                         rest, template, data);
                 backlogTracer.traceEvent(event);
@@ -679,6 +681,7 @@ public class CamelInternalProcessor extends 
DelegateAsyncProcessor implements In
                             true, backlogTracer.isBodyIncludeStreams(), 
backlogTracer.isBodyIncludeFiles(),
                             backlogTracer.getBodyMaxChars());
                     DefaultBacklogTracerEventMessage pseudoLast = new 
DefaultBacklogTracerEventMessage(
+                            camelContext,
                             false, true, 
backlogTracer.incrementTraceCounter(), created, source, routeId, null,
                             exchangeId, rest, template, data);
                     backlogTracer.traceEvent(pseudoLast);
diff --git 
a/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerHeaderMapTest.java
 
b/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerHeaderMapTest.java
new file mode 100644
index 00000000000..0da7e7c2396
--- /dev/null
+++ 
b/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerHeaderMapTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.camel.management;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.BacklogTracerEventMessage;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@DisabledOnOs(OS.AIX)
+public class BacklogTracerHeaderMapTest extends ManagementTestSupport {
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testBacklogTracerEventMessage() throws Exception {
+        MBeanServer mbeanServer = getMBeanServer();
+        ObjectName on
+                = new ObjectName("org.apache.camel:context=" + 
context.getManagementName() + ",type=tracer,name=BacklogTracer");
+        assertNotNull(on);
+        assertTrue(mbeanServer.isRegistered(on));
+
+        Boolean enabled = (Boolean) mbeanServer.getAttribute(on, "Enabled");
+        assertEquals(Boolean.TRUE, enabled, "Should be enabled");
+
+        Integer size = (Integer) mbeanServer.getAttribute(on, "BacklogSize");
+        assertEquals(100, size.intValue(), "Should be 100");
+
+        Boolean removeOnDump = (Boolean) mbeanServer.getAttribute(on, 
"RemoveOnDump");
+        assertEquals(Boolean.TRUE, removeOnDump);
+
+        getMockEndpoint("mock:foo").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        List<Exchange> exchanges = 
getMockEndpoint("mock:foo").getReceivedExchanges();
+
+        List<BacklogTracerEventMessage> events = 
(List<BacklogTracerEventMessage>) mbeanServer.invoke(on, "dumpTracedMessages",
+                new Object[] { "foo" }, new String[] { "java.lang.String" });
+
+        assertNotNull(events);
+        assertEquals(2, events.size());
+
+        BacklogTracerEventMessage event1 = events.get(0);
+        assertEquals("foo", event1.getToNode());
+        assertEquals("    <message exchangeId=\"" + 
exchanges.get(0).getExchangeId()
+                     + "\" exchangePattern=\"InOnly\" 
exchangeType=\"org.apache.camel.support.DefaultExchange\" 
messageType=\"org.apache.camel.support.DefaultMessage\">\n"
+                     + "      <exchangeProperties>\n"
+                     + "        <exchangeProperty key=\"CamelToEndpoint\" 
type=\"java.lang.String\">direct://start</exchangeProperty>\n"
+                     + "      </exchangeProperties>\n"
+                     + "      <headers>\n"
+                     + "        <header key=\"foo\" 
type=\"java.util.LinkedHashMap\">{one=1, two=2}</header>\n"
+                     + "      </headers>\n"
+                     + "      <body type=\"java.lang.String\">Hello 
World</body>\n"
+                     + "    </message>",
+                event1.getMessageAsXml());
+
+        assertEquals(String.format(
+                
"{\"message\":{\"exchangeId\":\"%s\",\"exchangePattern\":\"InOnly\",\"exchangeType\":\"org.apache.camel.support.DefaultExchange\",\"messageType\":\"org.apache.camel.support.DefaultMessage\",\"exchangeProperties\":[{\"key\":\"CamelToEndpoint\",\"type\":\"java.lang.String\",\"value\":\"direct:\\/\\/start\"}],\"headers\":[{\"key\":\"foo\",\"type\":\"java.util.LinkedHashMap\",\"value\":{\"one\":1,\"two\":2}}],\"body\":{\"type\":\"java.lang.String\",\"value\":\"Hello
 World\"}}}",
+                exchanges.get(0).getExchangeId()),
+                event1.getMessageAsJSon());
+
+        BacklogTracerEventMessage event2 = events.get(1);
+        assertEquals("foo", event2.getToNode());
+        assertEquals("    <message exchangeId=\"" + 
exchanges.get(1).getExchangeId()
+                     + "\" exchangePattern=\"InOnly\" 
exchangeType=\"org.apache.camel.support.DefaultExchange\" 
messageType=\"org.apache.camel.support.DefaultMessage\">\n"
+                     + "      <exchangeProperties>\n"
+                     + "        <exchangeProperty key=\"CamelToEndpoint\" 
type=\"java.lang.String\">direct://start</exchangeProperty>\n"
+                     + "      </exchangeProperties>\n"
+                     + "      <headers>\n"
+                     + "        <header key=\"foo\" 
type=\"java.util.LinkedHashMap\">{one=1, two=2}</header>\n"
+                     + "      </headers>\n"
+                     + "      <body type=\"java.lang.String\">Bye 
World</body>\n"
+                     + "    </message>",
+                event2.getMessageAsXml());
+
+        assertEquals(String.format(
+                
"{\"message\":{\"exchangeId\":\"%s\",\"exchangePattern\":\"InOnly\",\"exchangeType\":\"org.apache.camel.support.DefaultExchange\",\"messageType\":\"org.apache.camel.support.DefaultMessage\",\"exchangeProperties\":[{\"key\":\"CamelToEndpoint\",\"type\":\"java.lang.String\",\"value\":\"direct:\\/\\/start\"}],\"headers\":[{\"key\":\"foo\",\"type\":\"java.util.LinkedHashMap\",\"value\":{\"one\":1,\"two\":2}}],\"body\":{\"type\":\"java.lang.String\",\"value\":\"Bye
 World\"}}}",
+                exchanges.get(1).getExchangeId()),
+                event2.getMessageAsJSon());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                context.setUseBreadcrumb(false);
+                context.setBacklogTracing(true);
+
+                Map<String, Object> map = new LinkedHashMap<>();
+                map.put("one", 1);
+                map.put("two", 2);
+
+                from("direct:start")
+                        .setHeader("foo", constant(map)).id("setHeader1")
+                        .to("mock:foo").id("foo");
+            }
+        };
+    }
+
+}

Reply via email to