This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new b121f58 [CAMEL-17341, CAMEL-17342] BacklogDebugger API enhancements (#6535) b121f58 is described below commit b121f58a7b93287c8ce38f24a0508392667702a0 Author: javaduke <eugene.ber...@modusbox.com> AuthorDate: Wed Dec 15 22:41:51 2021 -0700 [CAMEL-17341, CAMEL-17342] BacklogDebugger API enhancements (#6535) * Exposing properties and expression evaluator * Removed unnecessary dependency * Unit test and docs added * Improved evaluateExpression, added tests and docs * Use camel class resolver * Got rid of the Apache utils and implemented isSerializable() method * Need initialization of languages other than core * CAMEL-17342 - exchange properties are optional part of the dumpTracedMessages * CAMEL-17341 - removed DataSonnet tests to avoid cyclic reference --- core/camel-management-api/pom.xml | 1 - .../mbean/ManagedBacklogDebuggerMBean.java | 8 ++ core/camel-management/pom.xml | 1 - .../management/mbean/ManagedBacklogDebugger.java | 106 ++++++++++++++- .../camel/management/BacklogDebuggerTest.java | 145 +++++++++++++++++++++ .../modules/ROOT/pages/backlog-debugger.adoc | 3 + 6 files changed, 261 insertions(+), 3 deletions(-) diff --git a/core/camel-management-api/pom.xml b/core/camel-management-api/pom.xml index d971dfb..64bf001 100644 --- a/core/camel-management-api/pom.xml +++ b/core/camel-management-api/pom.xml @@ -43,7 +43,6 @@ <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> - </dependencies> <reporting> diff --git a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedBacklogDebuggerMBean.java b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedBacklogDebuggerMBean.java index 75c4df1..2a6626a 100644 --- a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedBacklogDebuggerMBean.java +++ b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedBacklogDebuggerMBean.java @@ -142,6 +142,9 @@ public interface ManagedBacklogDebuggerMBean { @ManagedOperation(description = "Dumps the messages in xml format from the suspended breakpoint at the given node") String dumpTracedMessagesAsXml(String nodeId); + @ManagedOperation(description = "Dumps the messages in xml format from the suspended breakpoint at the given node, optionally including the exchange properties") + String dumpTracedMessagesAsXml(String nodeId, boolean includeExchangeProperties); + @ManagedAttribute(description = "Number of total debugged messages") long getDebugCounter(); @@ -151,4 +154,9 @@ public interface ManagedBacklogDebuggerMBean { @ManagedOperation(description = "Used for validating if a given predicate is valid or not") String validateConditionalBreakpoint(String language, String predicate); + @ManagedOperation(description = "Evaluates the expression at a given breakpoint Id") + Object evaluateExpressionAtBreakpoint(String id, String language, String expression, String resultType); + + @ManagedOperation(description = "Evaluates the expression at a given breakpoint Id and returns the result as String") + String evaluateExpressionAtBreakpoint(String id, String language, String expression); } diff --git a/core/camel-management/pom.xml b/core/camel-management/pom.xml index 8950f05..c806e27 100644 --- a/core/camel-management/pom.xml +++ b/core/camel-management/pom.xml @@ -85,7 +85,6 @@ <artifactId>log4j-slf4j-impl</artifactId> <scope>test</scope> </dependency> - </dependencies> <build> diff --git a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedBacklogDebugger.java b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedBacklogDebugger.java index 9554cd8..22184e0 100644 --- a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedBacklogDebugger.java +++ b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedBacklogDebugger.java @@ -16,16 +16,26 @@ */ package org.apache.camel.management.mbean; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.Map; import java.util.Set; import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.Expression; import org.apache.camel.NoTypeConversionAvailableException; +import org.apache.camel.Predicate; import org.apache.camel.RuntimeCamelException; import org.apache.camel.api.management.ManagedResource; +import org.apache.camel.api.management.mbean.BacklogTracerEventMessage; import org.apache.camel.api.management.mbean.ManagedBacklogDebuggerMBean; import org.apache.camel.impl.debugger.BacklogDebugger; import org.apache.camel.spi.Language; import org.apache.camel.spi.ManagementStrategy; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.StringHelper; @ManagedResource(description = "Managed BacklogDebugger") public class ManagedBacklogDebugger implements ManagedBacklogDebuggerMBean { @@ -236,7 +246,18 @@ public class ManagedBacklogDebugger implements ManagedBacklogDebuggerMBean { @Override public String dumpTracedMessagesAsXml(String nodeId) { - return backlogDebugger.dumpTracedMessagesAsXml(nodeId); + return dumpTracedMessagesAsXml(nodeId, false); + } + + @Override + public String dumpTracedMessagesAsXml(String nodeId, boolean includeExchangeProperties) { + String messageAsXml = backlogDebugger.dumpTracedMessagesAsXml(nodeId); + if (messageAsXml != null && includeExchangeProperties) { + String closingTag = "</" + BacklogTracerEventMessage.ROOT_TAG + ">"; + String exchangePropertiesAsXml = dumpExchangePropertiesAsXml(nodeId); + messageAsXml = messageAsXml.replace(closingTag, exchangePropertiesAsXml) + "\n" + closingTag; + } + return messageAsXml; } @Override @@ -274,4 +295,87 @@ public class ManagedBacklogDebugger implements ManagedBacklogDebuggerMBean { public void setFallbackTimeout(long fallbackTimeout) { backlogDebugger.setFallbackTimeout(fallbackTimeout); } + + @Override + public String evaluateExpressionAtBreakpoint(String id, String language, String expression) { + return evaluateExpressionAtBreakpoint(id, language, expression, "java.lang.String").toString(); + } + + @Override + public Object evaluateExpressionAtBreakpoint(String id, String language, String expression, String resultType) { + Exchange suspendedExchange = null; + try { + Language lan = camelContext.resolveLanguage(language); + suspendedExchange = backlogDebugger.getSuspendedExchange(id); + if (suspendedExchange != null) { + Object result = null; + Class resultClass = camelContext.getClassResolver().resolveClass(resultType); + if (!Boolean.class.isAssignableFrom(resultClass)) { + Expression expr = lan.createExpression(expression); + expr.init(camelContext); + result = expr.evaluate(suspendedExchange, resultClass); + } else { + Predicate pred = lan.createPredicate(expression); + pred.init(camelContext); + result = pred.matches(suspendedExchange); + } + //Test if result is serializable + if (!isSerializable(result)) { + String resultStr = suspendedExchange.getContext().getTypeConverter().tryConvertTo(String.class, result); + if (resultStr != null) { + result = resultStr; + } + } + return result; + } + } catch (Exception e) { + return e; + } + return null; + } + + private boolean isSerializable(Object obj) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(512); + try (ObjectOutputStream out = new ObjectOutputStream(baos)) { + out.writeObject(obj); + return true; + } catch (final IOException ex) { + return false; + } + } + + private String dumpExchangePropertiesAsXml(String id) { + StringBuilder sb = new StringBuilder(); + sb.append(" <exchangeProperties>\n"); + Exchange suspendedExchange = backlogDebugger.getSuspendedExchange(id); + if (suspendedExchange != null) { + Map<String, Object> properties = suspendedExchange.getAllProperties(); + properties.forEach((propertyName, propertyValue) -> { + String type = ObjectHelper.classCanonicalName(propertyValue); + sb.append(" <exchangeProperty name=\"").append(propertyName).append("\""); + if (type != null) { + sb.append(" type=\"").append(type).append("\""); + } + sb.append(">"); + // dump property value as XML, use Camel type converter to convert + // to String + if (propertyValue != null) { + try { + String xml = suspendedExchange.getContext().getTypeConverter().tryConvertTo(String.class, + suspendedExchange, propertyValue); + if (xml != null) { + // must always xml encode + sb.append(StringHelper.xmlEncode(xml)); + } + } catch (Throwable e) { + // ignore as the body is for logging purpose + } + } + sb.append("</exchangeProperty>\n"); + }); + } + sb.append(" </exchangeProperties>"); + return sb.toString(); + } + } diff --git a/core/camel-management/src/test/java/org/apache/camel/management/BacklogDebuggerTest.java b/core/camel-management/src/test/java/org/apache/camel/management/BacklogDebuggerTest.java index 63d1167..401c36d 100644 --- a/core/camel-management/src/test/java/org/apache/camel/management/BacklogDebuggerTest.java +++ b/core/camel-management/src/test/java/org/apache/camel/management/BacklogDebuggerTest.java @@ -670,6 +670,150 @@ public class BacklogDebuggerTest extends ManagementTestSupport { assertEquals(Boolean.FALSE, stepMode, "Should not be in step mode"); } + @SuppressWarnings("unchecked") + @Test + public void testBacklogDebuggerExchangeProperties() throws Exception { + MBeanServer mbeanServer = getMBeanServer(); + ObjectName on = new ObjectName( + "org.apache.camel:context=" + context.getManagementName() + ",type=tracer,name=BacklogDebugger"); + assertNotNull(on); + mbeanServer.isRegistered(on); + + Boolean enabled = (Boolean) mbeanServer.getAttribute(on, "Enabled"); + assertEquals(Boolean.FALSE, enabled, "Should not be enabled"); + + // enable debugger + mbeanServer.invoke(on, "enableDebugger", null, null); + + enabled = (Boolean) mbeanServer.getAttribute(on, "Enabled"); + assertEquals(Boolean.TRUE, enabled, "Should be enabled"); + + // add breakpoint at bar + mbeanServer.invoke(on, "addBreakpoint", new Object[] { "bar" }, new String[] { "java.lang.String" }); + + MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMessageCount(0); + mock.setSleepForEmptyTest(100); + + template.sendBody("seda:start", "Hello World"); + + assertMockEndpointsSatisfied(); + + // wait for breakpoint at bar + await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { + Set<String> suspended = (Set<String>) mbeanServer.invoke(on, "getSuspendedBreakpointNodeIds", null, null); + assertNotNull(suspended); + assertEquals(1, suspended.size()); + assertEquals("bar", suspended.iterator().next()); + }); + + // there should be an exchange property 'myProperty' + String xml = (String) mbeanServer.invoke(on, "dumpTracedMessagesAsXml", new Object[] { "bar", true }, + new String[] { "java.lang.String", "boolean" }); + assertNotNull(xml); + log.info(xml); + + assertTrue(xml.contains("<exchangeProperty name=\"myProperty\" type=\"java.lang.String\">myValue</exchangeProperty>"), + "Should contain myProperty"); + + resetMocks(); + mock.expectedMessageCount(1); + + // resume breakpoint + mbeanServer.invoke(on, "resumeBreakpoint", new Object[] { "bar" }, new String[] { "java.lang.String" }); + + assertMockEndpointsSatisfied(); + + // and no suspended anymore + Set<String> nodes = (Set<String>) mbeanServer.invoke(on, "getSuspendedBreakpointNodeIds", null, null); + assertNotNull(nodes); + assertEquals(0, nodes.size()); + } + + @SuppressWarnings("unchecked") + @Test + public void testBacklogDebuggerEvaluateExpression() throws Exception { + MBeanServer mbeanServer = getMBeanServer(); + ObjectName on = new ObjectName( + "org.apache.camel:context=" + context.getManagementName() + ",type=tracer,name=BacklogDebugger"); + assertNotNull(on); + mbeanServer.isRegistered(on); + + Boolean enabled = (Boolean) mbeanServer.getAttribute(on, "Enabled"); + assertEquals(Boolean.FALSE, enabled, "Should not be enabled"); + + // enable debugger + mbeanServer.invoke(on, "enableDebugger", null, null); + + enabled = (Boolean) mbeanServer.getAttribute(on, "Enabled"); + assertEquals(Boolean.TRUE, enabled, "Should be enabled"); + + // add breakpoint at bar + mbeanServer.invoke(on, "addBreakpoint", new Object[] { "bar" }, new String[] { "java.lang.String" }); + + MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMessageCount(0); + mock.setSleepForEmptyTest(100); + + template.sendBody("seda:start", "Hello World"); + + assertMockEndpointsSatisfied(); + + // wait for breakpoint at bar + await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { + Set<String> suspended = (Set<String>) mbeanServer.invoke(on, "getSuspendedBreakpointNodeIds", null, null); + assertNotNull(suspended); + assertEquals(1, suspended.size()); + assertEquals("bar", suspended.iterator().next()); + }); + + // evaluate expression, should return true + Object response = mbeanServer.invoke(on, "evaluateExpressionAtBreakpoint", + new Object[] { "bar", "simple", "${body} contains 'Hello'", "java.lang.Boolean" }, + new String[] { "java.lang.String", "java.lang.String", "java.lang.String", "java.lang.String" }); + + assertNotNull(response); + log.info(response.toString()); + + assertTrue(response.getClass().isAssignableFrom(Boolean.class)); + assertTrue((Boolean) response); + + // evaluate another expression, should return value + response = mbeanServer.invoke(on, "evaluateExpressionAtBreakpoint", + new Object[] { "bar", "simple", "${exchangeProperty.myProperty}", "java.lang.String" }, + new String[] { "java.lang.String", "java.lang.String", "java.lang.String", "java.lang.String" }); + + assertNotNull(response); + log.info(response.toString()); + + assertTrue(response.getClass().isAssignableFrom(String.class)); + assertEquals("myValue", response); + + // same as before but assume string by default + response = mbeanServer.invoke(on, "evaluateExpressionAtBreakpoint", + new Object[] { "bar", "simple", "${exchangeProperty.myProperty}" }, + new String[] { "java.lang.String", "java.lang.String", "java.lang.String" }); + + assertNotNull(response); + log.info(response.toString()); + + assertTrue(response.getClass().isAssignableFrom(String.class)); + assertEquals("myValue", response); + + resetMocks(); + mock.expectedMessageCount(1); + + // resume breakpoint + mbeanServer.invoke(on, "resumeBreakpoint", new Object[] { "bar" }, new String[] { "java.lang.String" }); + + assertMockEndpointsSatisfied(); + + // and no suspended anymore + Set<String> nodes = (Set<String>) mbeanServer.invoke(on, "getSuspendedBreakpointNodeIds", null, null); + assertNotNull(nodes); + assertEquals(0, nodes.size()); + } + @Override protected RouteBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { @@ -679,6 +823,7 @@ public class BacklogDebuggerTest extends ManagementTestSupport { context.setDebugging(true); from("seda:start?concurrentConsumers=2") + .setProperty("myProperty", constant("myValue")).id("setProp") .to("log:foo").id("foo") .to("log:bar").id("bar") .transform().constant("Bye World").id("transform") diff --git a/docs/user-manual/modules/ROOT/pages/backlog-debugger.adoc b/docs/user-manual/modules/ROOT/pages/backlog-debugger.adoc index 9a4fd97..f26a9f5 100644 --- a/docs/user-manual/modules/ROOT/pages/backlog-debugger.adoc +++ b/docs/user-manual/modules/ROOT/pages/backlog-debugger.adoc @@ -36,8 +36,11 @@ NOTE: This requires to enabled JMX by including `camel-management` JAR in the cl |`disableBreakpoint(nodeId)` |`void` |To disable a breakpoint. |`disableDebugger` |`void` |To disable the debugger |`dumpTracedMessagesAsXml(nodeId)` |`String` |To dump the debugged messages from the give node id in XML format. +|`dumpTracedMessagesAsXml(nodeId, boolean)` |`String` |To dump the debugged messages from the give node id in XML format, optionally including the exchange properties. |`enableBreakpoint(nodeId)` |`void` |To activate a breakpoint which has been disabled. |`enableDebugger` |`void` |To enable the debugger +|`evaluateExpressionAtBreakpoint(nodeId, language, expression)` | `String`|To evaluate an expression in any supported language (e.g. Simple, CSimple, etc.) and convert the result to String +|`evaluateExpressionAtBreakpoint(nodeId, language, expression, resultType)` | `Object`|To evaluate an expression in any supported language (e.g. Simple, CSimple, etc.) and return the result as a given result type |`getBreakpoints` |`Set<String>` |To get a set of all the nodes which has a breakpoint added. |`getDebuggerCounter` |`long` |Gets the total number of debugged messages. |`getSuspendedBreakpointNodeIds` |`Set<String>` |To get a set of all the nodes which has suspended breakpoints (eg an Exchange at the breakpoint which is suspended).