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