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"); + } + }; + } + +}