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 2023/12/21 14:38:57 UTC

(camel) 01/04: CAMEL-20274: camel-management - Add JMX operation to update route from XML

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

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

commit acd9a0b5f37d60c9467c3a3138e2004e8495a310
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu Dec 21 15:07:36 2023 +0100

    CAMEL-20274: camel-management - Add JMX operation to update route from XML
---
 .../main/java/org/apache/camel/CamelContext.java   |   1 +
 .../java/org/apache/camel/clock/ContextClock.java  |  15 +-
 .../java/org/apache/camel/clock/EventClock.java    |   7 +-
 .../camel/impl/engine/AbstractCamelContext.java    |   1 -
 .../api/management/mbean/ManagedRouteMBean.java    |   4 +
 core/camel-management/pom.xml                      |   4 +
 .../camel/management/mbean/ManagedRoute.java       |  38 +++++
 .../camel/management/ManagedFromRestGetTest.java   |  10 +-
 .../management/ManagedFromRestPlaceholderTest.java |  10 +-
 .../ManagedRouteUpdateRouteFromXmlTest.java        | 153 +++++++++++++++++++++
 10 files changed, 224 insertions(+), 19 deletions(-)

diff --git a/core/camel-api/src/main/java/org/apache/camel/CamelContext.java b/core/camel-api/src/main/java/org/apache/camel/CamelContext.java
index 1035141046d..22cfc3ed793 100644
--- a/core/camel-api/src/main/java/org/apache/camel/CamelContext.java
+++ b/core/camel-api/src/main/java/org/apache/camel/CamelContext.java
@@ -185,6 +185,7 @@ public interface CamelContext extends CamelContextLifecycle, RuntimeConfiguratio
 
     /**
      * Gets a clock instance that keeps track of time for relevant CamelContext events
+     *
      * @return A clock instance
      */
     EventClock<ContextEvents> getClock();
diff --git a/core/camel-api/src/main/java/org/apache/camel/clock/ContextClock.java b/core/camel-api/src/main/java/org/apache/camel/clock/ContextClock.java
index 2bc575a0abf..edc6ae65a19 100644
--- a/core/camel-api/src/main/java/org/apache/camel/clock/ContextClock.java
+++ b/core/camel-api/src/main/java/org/apache/camel/clock/ContextClock.java
@@ -50,9 +50,10 @@ public final class ContextClock implements EventClock<ContextEvents> {
 
     /**
      * Get the elapsed time for the event
-     * @param event the event to get the elapsed time
-     * @param defaultValue the default value to provide if the event is not being tracked
-     * @return The elapsed time or the default value if the event is not being tracked
+     *
+     * @param  event        the event to get the elapsed time
+     * @param  defaultValue the default value to provide if the event is not being tracked
+     * @return              The elapsed time or the default value if the event is not being tracked
      */
     public long elapsed(ContextEvents event, long defaultValue) {
         Clock clock = events.get(event);
@@ -65,9 +66,11 @@ public final class ContextClock implements EventClock<ContextEvents> {
 
     /**
      * Get the time for the event as a Date object
-     * @param event the event to get the elapsed time
-     * @param defaultValue the default value to provide if the event is not being tracked
-     * @return The Date object representing the creation date or the default value if the event is not being tracked
+     *
+     * @param  event        the event to get the elapsed time
+     * @param  defaultValue the default value to provide if the event is not being tracked
+     * @return              The Date object representing the creation date or the default value if the event is not
+     *                      being tracked
      */
     public Date asDate(ContextEvents event, Date defaultValue) {
         Clock clock = events.get(event);
diff --git a/core/camel-api/src/main/java/org/apache/camel/clock/EventClock.java b/core/camel-api/src/main/java/org/apache/camel/clock/EventClock.java
index 924640a5814..18136904229 100644
--- a/core/camel-api/src/main/java/org/apache/camel/clock/EventClock.java
+++ b/core/camel-api/src/main/java/org/apache/camel/clock/EventClock.java
@@ -19,12 +19,14 @@ package org.apache.camel.clock;
 
 /**
  * A specialized clock that tracks the pass of time for one or more types of events
+ *
  * @param <T> The event type as an Enum
  */
 public interface EventClock<T extends Enum<T>> extends Clock {
 
     /**
      * Add the event to be tracked
+     *
      * @param event the event to track
      * @param clock the clock associated with the event
      */
@@ -32,8 +34,9 @@ public interface EventClock<T extends Enum<T>> extends Clock {
 
     /**
      * Get the clock for the event
-     * @param event the event to get the clock for
-     * @return the clock instance or null if not set
+     *
+     * @param  event the event to get the clock for
+     * @return       the clock instance or null if not set
      */
     Clock get(T event);
 }
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
index 49b5d5bb0d5..5f2060182d7 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
@@ -72,7 +72,6 @@ import org.apache.camel.TypeConverter;
 import org.apache.camel.VetoCamelContextStartException;
 import org.apache.camel.api.management.JmxSystemPropertyKeys;
 import org.apache.camel.catalog.RuntimeCamelCatalog;
-import org.apache.camel.clock.Clock;
 import org.apache.camel.clock.ContextClock;
 import org.apache.camel.clock.EventClock;
 import org.apache.camel.console.DevConsoleRegistry;
diff --git a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
index 9687add7998..4adb688971d 100644
--- a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
+++ b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
@@ -165,4 +165,8 @@ public interface ManagedRouteMBean extends ManagedPerformanceCounterMBean {
 
     @ManagedOperation(description = "IDs for the processors that are part of this route")
     Collection<String> processorIds() throws Exception;
+
+    @ManagedOperation(description = "Updates the route from XML")
+    void updateRouteFromXml(String xml) throws Exception;
+
 }
diff --git a/core/camel-management/pom.xml b/core/camel-management/pom.xml
index 1a9adec3710..72e2dbc7ee3 100644
--- a/core/camel-management/pom.xml
+++ b/core/camel-management/pom.xml
@@ -47,6 +47,10 @@
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-core-engine</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-xml-jaxb</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-bean</artifactId>
diff --git a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
index d3c559e5198..0bd96f85748 100644
--- a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
+++ b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.management.mbean;
 
+import java.io.InputStream;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -41,6 +42,7 @@ import javax.management.openmbean.TabularData;
 import javax.management.openmbean.TabularDataSupport;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.ExtendedCamelContext;
 import org.apache.camel.ManagementStatisticsLevel;
 import org.apache.camel.Route;
 import org.apache.camel.RuntimeCamelException;
@@ -55,11 +57,13 @@ import org.apache.camel.api.management.mbean.RouteError;
 import org.apache.camel.model.Model;
 import org.apache.camel.model.ModelCamelContext;
 import org.apache.camel.model.RouteDefinition;
+import org.apache.camel.model.RoutesDefinition;
 import org.apache.camel.spi.InflightRepository;
 import org.apache.camel.spi.ManagementStrategy;
 import org.apache.camel.spi.RoutePolicy;
 import org.apache.camel.support.PluginHelper;
 import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.xml.jaxb.JaxbHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -672,6 +676,40 @@ public class ManagedRoute extends ManagedPerformanceCounter implements TimerList
         }
     }
 
+    @Override
+    public void updateRouteFromXml(String xml) throws Exception {
+        // convert to model from xml
+        ExtendedCamelContext ecc = context.getCamelContextExtension();
+        InputStream is = context.getTypeConverter().convertTo(InputStream.class, xml);
+        RoutesDefinition routes = JaxbHelper.loadRoutesDefinition(context, is);
+        if (routes == null || routes.getRoutes().isEmpty()) {
+            return;
+        }
+        RouteDefinition def = routes.getRoutes().get(0);
+
+        // if the xml does not contain the route-id then we fix this by adding the actual route id
+        // this may be needed if the route-id was auto-generated, as the intend is to update this route
+        // and not add a new route, adding a new route, use the MBean operation on ManagedCamelContext instead.
+        if (ObjectHelper.isEmpty(def.getId())) {
+            def.setId(getRouteId());
+        } else if (!def.getId().equals(getRouteId())) {
+            throw new IllegalArgumentException(
+                    "Cannot update route from XML as routeIds does not match. routeId: "
+                                               + getRouteId() + ", routeId from XML: " + def.getId());
+        }
+
+        LOG.debug("Updating route: {} from xml: {}", def.getId(), xml);
+        try {
+            // add will remove existing route first
+            ecc.getContextPlugin(Model.class).addRouteDefinition(def);
+        } catch (Exception e) {
+            // log the error as warn as the management api may be invoked remotely over JMX which does not propagate such exception
+            String msg = "Error updating route: " + def.getId() + " from xml: " + xml + " due: " + e.getMessage();
+            LOG.warn(msg, e);
+            throw e;
+        }
+    }
+
     @Override
     public boolean equals(Object o) {
         return this == o || o != null && getClass() == o.getClass() && route.equals(((ManagedRoute) o).route);
diff --git a/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestGetTest.java b/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestGetTest.java
index 086719e7c02..c4e1df5bd7c 100644
--- a/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestGetTest.java
+++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestGetTest.java
@@ -64,14 +64,14 @@ public class ManagedFromRestGetTest extends ManagementTestSupport {
         assertTrue(xml.contains("application/json"));
         assertTrue(xml.contains("</rests>"));
 
-        assertTrue(xml.contains("<param defaultValue=\"1\" dataType=\"integer\" name=\"header_count\""
-                                + " description=\"header param description1\" type=\"header\" required=\"true\">"));
-        assertTrue(xml.contains("<param defaultValue=\"b\" dataType=\"string\" name=\"header_letter\""
-                                + " description=\"header param description2\" type=\"query\" collectionFormat=\"multi\" required=\"false\">"));
+        assertTrue(xml.contains(
+                "<param dataType=\"integer\" defaultValue=\"1\" description=\"header param description1\" name=\"header_count\" required=\"true\" type=\"header\">"));
+        assertTrue(xml.contains(
+                "<param collectionFormat=\"multi\" dataType=\"string\" defaultValue=\"b\" description=\"header param description2\" name=\"header_letter\" required=\"false\" type=\"query\">"));
         assertTrue(xml.contains("<value>1</value>"));
         assertTrue(xml.contains("<value>a</value>"));
 
-        assertTrue(xml.contains("<responseMessage code=\"300\" responseModel=\"java.lang.Integer\" message=\"test msg\"/>"));
+        assertTrue(xml.contains("<responseMessage code=\"300\" message=\"test msg\" responseModel=\"java.lang.Integer\"/>"));
 
         String xml2 = (String) mbeanServer.invoke(on, "dumpRoutesAsXml", null, null);
         log.info(xml2);
diff --git a/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestPlaceholderTest.java b/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestPlaceholderTest.java
index 7984b258693..c09629efc86 100644
--- a/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestPlaceholderTest.java
+++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestPlaceholderTest.java
@@ -65,14 +65,14 @@ public class ManagedFromRestPlaceholderTest extends ManagementTestSupport {
         assertTrue(xml.contains("application/json"));
         assertTrue(xml.contains("</rests>"));
 
-        assertTrue(xml.contains("<param defaultValue=\"1\" dataType=\"integer\" name=\"header_count\""
-                                + " description=\"header param description1\" type=\"header\" required=\"true\">"));
-        assertTrue(xml.contains("<param defaultValue=\"b\" dataType=\"string\" name=\"header_letter\""
-                                + " description=\"header param description2\" type=\"query\" collectionFormat=\"multi\" required=\"false\">"));
+        assertTrue(xml.contains(
+                "<param dataType=\"integer\" defaultValue=\"1\" description=\"header param description1\" name=\"header_count\" required=\"true\" type=\"header\">"));
+        assertTrue(xml.contains(
+                "<param collectionFormat=\"multi\" dataType=\"string\" defaultValue=\"b\" description=\"header param description2\" name=\"header_letter\" required=\"false\" type=\"query\">"));
         assertTrue(xml.contains("<value>1</value>"));
         assertTrue(xml.contains("<value>a</value>"));
 
-        assertTrue(xml.contains("<responseMessage code=\"300\" responseModel=\"java.lang.Integer\" message=\"test msg\"/>"));
+        assertTrue(xml.contains("<responseMessage code=\"300\" message=\"test msg\" responseModel=\"java.lang.Integer\"/>"));
 
         String xml2 = (String) mbeanServer.invoke(on, "dumpRoutesAsXml", null, null);
         log.info(xml2);
diff --git a/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteUpdateRouteFromXmlTest.java b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteUpdateRouteFromXmlTest.java
new file mode 100644
index 00000000000..7ba41bffef7
--- /dev/null
+++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteUpdateRouteFromXmlTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.Set;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+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.fail;
+
+@DisabledOnOs(OS.AIX)
+public class ManagedRouteUpdateRouteFromXmlTest extends ManagementTestSupport {
+
+    @Test
+    public void testUpdateRouteFromXml() throws Exception {
+        MBeanServer mbeanServer = getMBeanServer();
+        ObjectName on = getRouteObjectName(mbeanServer);
+
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedBodiesReceived("Hello World");
+
+        template.sendBody("direct:start", "Hello World");
+
+        assertMockEndpointsSatisfied();
+
+        // should be started
+        String routeId = (String) mbeanServer.getAttribute(on, "RouteId");
+        assertEquals("myRoute", routeId);
+
+        String xml = "<route id=\"myRoute\" xmlns=\"http://camel.apache.org/schema/spring\">"
+                     + "  <from uri=\"direct:start\"/>"
+                     + "  <log message=\"This is a changed route saying ${body}\"/>"
+                     + "  <to uri=\"mock:changed\"/>"
+                     + "</route>";
+
+        mbeanServer.invoke(on, "updateRouteFromXml", new Object[] { xml }, new String[] { "java.lang.String" });
+
+        assertEquals(1, context.getRoutes().size());
+
+        getMockEndpoint("mock:changed").expectedMessageCount(1);
+
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testUpdateRouteFromXmlWithoutRouteId() throws Exception {
+        MBeanServer mbeanServer = getMBeanServer();
+        ObjectName on = getRouteObjectName(mbeanServer);
+
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedBodiesReceived("Hello World");
+
+        template.sendBody("direct:start", "Hello World");
+
+        assertMockEndpointsSatisfied();
+
+        // should be started
+        String routeId = (String) mbeanServer.getAttribute(on, "RouteId");
+        assertEquals("myRoute", routeId);
+
+        String xml = "<route xmlns=\"http://camel.apache.org/schema/spring\">"
+                     + "  <from uri=\"direct:start\"/>"
+                     + "  <log message=\"This is a changed route saying ${body}\"/>"
+                     + "  <to uri=\"mock:changed\"/>"
+                     + "</route>";
+
+        mbeanServer.invoke(on, "updateRouteFromXml", new Object[] { xml }, new String[] { "java.lang.String" });
+
+        assertEquals(1, context.getRoutes().size());
+
+        getMockEndpoint("mock:changed").expectedMessageCount(1);
+
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testUpdateRouteFromXmlMismatchRouteId() throws Exception {
+        MBeanServer mbeanServer = getMBeanServer();
+        ObjectName on = getRouteObjectName(mbeanServer);
+
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedBodiesReceived("Hello World");
+
+        template.sendBody("direct:start", "Hello World");
+
+        assertMockEndpointsSatisfied();
+
+        // should be started
+        String routeId = (String) mbeanServer.getAttribute(on, "RouteId");
+        assertEquals("myRoute", routeId);
+
+        String xml = "<route id=\"foo\" xmlns=\"http://camel.apache.org/schema/spring\">"
+                     + "  <from uri=\"direct:start\"/>"
+                     + "  <log message=\"This is a changed route saying ${body}\"/>"
+                     + "  <to uri=\"mock:changed\"/>"
+                     + "</route>";
+
+        try {
+            mbeanServer.invoke(on, "updateRouteFromXml", new Object[] { xml }, new String[] { "java.lang.String" });
+            fail("Should have thrown exception");
+        } catch (Exception e) {
+            assertIsInstanceOf(IllegalArgumentException.class, e.getCause());
+            assertEquals("Cannot update route from XML as routeIds does not match. routeId: myRoute, routeId from XML: foo",
+                    e.getCause().getMessage());
+        }
+    }
+
+    static ObjectName getRouteObjectName(MBeanServer mbeanServer) throws Exception {
+        Set<ObjectName> set = mbeanServer.queryNames(new ObjectName("*:type=routes,*"), null);
+        assertEquals(1, set.size());
+
+        return set.iterator().next();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start").routeId("myRoute")
+                        .log("Got ${body}")
+                        .to("mock:result");
+            }
+        };
+    }
+
+}