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 <claus.ib...@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"); + } + }; + } + +}