You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2021/12/16 05:42:34 UTC

[camel] branch main updated: [CAMEL-17341, CAMEL-17342] BacklogDebugger API enhancements (#6535)

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 <Eu...@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).