This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch camel-3.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit 4c29f2058d400ee431d34f72384f8ef0a9fd0eee Author: Claus Ibsen <[email protected]> AuthorDate: Fri May 5 10:25:17 2023 +0200 CAMEL-19320: camel-jbang - Reload on demand --- .../camel/impl/engine/DefaultRoutesLoader.java | 6 +- .../camel/impl/console/ContextDevConsole.java | 25 ++--- .../apache/camel/main/MainPropertiesReload.java | 5 +- .../support/DefaultContextReloadStrategy.java | 5 + .../support/FileWatcherResourceReloadStrategy.java | 25 +++-- .../camel/support/RouteOnDemandReloadStrategy.java | 112 +++++++++++++++++++++ .../camel/support/RouteWatcherReloadStrategy.java | 54 +++++++--- .../camel/cli/connector/LocalCliConnector.java | 26 ++++- .../apache/camel/dsl/jbang/core/commands/Run.java | 1 + 9 files changed, 217 insertions(+), 42 deletions(-) diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java index 5407c04f4ac..4ae4e65ea92 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java @@ -192,8 +192,11 @@ public class DefaultRoutesLoader extends ServiceSupport implements RoutesLoader, @Override public Set<String> updateRoutes(Collection<Resource> resources) throws Exception { Set<String> answer = new LinkedHashSet<>(); - Collection<RoutesBuilder> builders = findRoutesBuilders(resources); + if (resources == null || resources.isEmpty()) { + return answer; + } + Collection<RoutesBuilder> builders = findRoutesBuilders(resources); for (RoutesBuilder builder : builders) { // update any existing route configurations first if (builder instanceof RouteConfigurationsBuilder) { @@ -201,7 +204,6 @@ public class DefaultRoutesLoader extends ServiceSupport implements RoutesLoader, rcb.updateRouteConfigurationsToCamelContext(getCamelContext()); } } - for (RoutesBuilder builder : builders) { // update any existing routes Set<String> ids = builder.updateRoutesToCamelContext(getCamelContext()); diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/ContextDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/ContextDevConsole.java index 86c722336b6..514fbc20958 100644 --- a/core/camel-console/src/main/java/org/apache/camel/impl/console/ContextDevConsole.java +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/ContextDevConsole.java @@ -19,6 +19,7 @@ package org.apache.camel.impl.console; import java.util.Date; import java.util.Locale; import java.util.Map; +import java.util.Set; import org.apache.camel.ExtendedCamelContext; import org.apache.camel.api.management.ManagedCamelContext; @@ -53,13 +54,13 @@ public class ContextDevConsole extends AbstractDevConsole { ManagedCamelContextMBean mb = mcc.getManagedCamelContext(); if (mb != null) { int reloaded = 0; - ResourceReloadStrategy rrs = getCamelContext().hasService(ResourceReloadStrategy.class); - if (rrs != null) { - reloaded += rrs.getReloadCounter(); + Set<ResourceReloadStrategy> rrs = getCamelContext().hasServices(ResourceReloadStrategy.class); + for (ResourceReloadStrategy r : rrs) { + reloaded += r.getReloadCounter(); } - ContextReloadStrategy crs = getCamelContext().hasService(ContextReloadStrategy.class); - if (crs != null) { - reloaded += crs.getReloadCounter(); + Set<ContextReloadStrategy> crs = getCamelContext().hasServices(ContextReloadStrategy.class); + for (ContextReloadStrategy r : crs) { + reloaded += r.getReloadCounter(); } String load1 = getLoad1(mb); String load5 = getLoad5(mb); @@ -123,13 +124,13 @@ public class ContextDevConsole extends AbstractDevConsole { JsonObject stats = new JsonObject(); int reloaded = 0; - ResourceReloadStrategy rrs = getCamelContext().hasService(ResourceReloadStrategy.class); - if (rrs != null) { - reloaded += rrs.getReloadCounter(); + Set<ResourceReloadStrategy> rrs = getCamelContext().hasServices(ResourceReloadStrategy.class); + for (ResourceReloadStrategy r : rrs) { + reloaded += r.getReloadCounter(); } - ContextReloadStrategy crs = getCamelContext().hasService(ContextReloadStrategy.class); - if (crs != null) { - reloaded += crs.getReloadCounter(); + Set<ContextReloadStrategy> crs = getCamelContext().hasServices(ContextReloadStrategy.class); + for (ContextReloadStrategy r : crs) { + reloaded += r.getReloadCounter(); } String load1 = getLoad1(mb); String load5 = getLoad5(mb); diff --git a/core/camel-main/src/main/java/org/apache/camel/main/MainPropertiesReload.java b/core/camel-main/src/main/java/org/apache/camel/main/MainPropertiesReload.java index 34ddef39139..ce2855d31a1 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/MainPropertiesReload.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/MainPropertiesReload.java @@ -20,6 +20,8 @@ import java.util.Properties; import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; +import org.apache.camel.NonManagedService; +import org.apache.camel.StaticService; import org.apache.camel.spi.PropertiesReload; import org.apache.camel.support.service.ServiceSupport; import org.apache.camel.util.OrderedLocationProperties; @@ -27,7 +29,8 @@ import org.apache.camel.util.OrderedLocationProperties; /** * Reloading of application.properties when using Camel Main in reload mode, such as when using camel-jbang. */ -public class MainPropertiesReload extends ServiceSupport implements PropertiesReload, CamelContextAware { +public class MainPropertiesReload extends ServiceSupport + implements StaticService, NonManagedService, PropertiesReload, CamelContextAware { private final BaseMainSupport main; private CamelContext camelContext; diff --git a/core/camel-support/src/main/java/org/apache/camel/support/DefaultContextReloadStrategy.java b/core/camel-support/src/main/java/org/apache/camel/support/DefaultContextReloadStrategy.java index 4df77bc0ed2..83bf9147a35 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/DefaultContextReloadStrategy.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/DefaultContextReloadStrategy.java @@ -48,6 +48,11 @@ public class DefaultContextReloadStrategy extends ServiceSupport implements Cont this.camelContext = camelContext; } + @ManagedOperation(description = "Trigger on-demand reloading") + public void onReload() { + onReload("JMX Management"); + } + @Override public void onReload(Object source) { LOG.info("Reloading CamelContext ({}) triggered by: {}", camelContext.getName(), source); diff --git a/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java b/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java index 1e62cd72720..54a865c84b7 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java @@ -61,14 +61,15 @@ public class FileWatcherResourceReloadStrategy extends ResourceReloadStrategySup private static final Logger LOG = LoggerFactory.getLogger(FileWatcherResourceReloadStrategy.class); - private String folder; - private boolean isRecursive; - private WatchService watcher; - private ExecutorService executorService; - private WatchFileChangesTask task; - private Map<WatchKey, Path> folderKeys; - private long pollTimeout = 2000; - private FileFilter fileFilter; + WatchService watcher; + ExecutorService executorService; + WatchFileChangesTask task; + Map<WatchKey, Path> folderKeys; + FileFilter fileFilter; + String folder; + boolean isRecursive; + boolean scheduler = true; + long pollTimeout = 2000; public FileWatcherResourceReloadStrategy() { setRecursive(false); @@ -96,6 +97,10 @@ public class FileWatcherResourceReloadStrategy extends ResourceReloadStrategySup this.isRecursive = isRecursive; } + public void setScheduler(boolean scheduler) { + this.scheduler = scheduler; + } + /** * Sets the poll timeout in millis. The default value is 2000. */ @@ -137,6 +142,10 @@ public class FileWatcherResourceReloadStrategy extends ResourceReloadStrategySup // no folder configured return; } + if (!scheduler) { + // do not start scheduler so exit start phase + return; + } File dir = new File(folder); if (dir.exists() && dir.isDirectory()) { diff --git a/core/camel-support/src/main/java/org/apache/camel/support/RouteOnDemandReloadStrategy.java b/core/camel-support/src/main/java/org/apache/camel/support/RouteOnDemandReloadStrategy.java new file mode 100644 index 00000000000..6012c9e564a --- /dev/null +++ b/core/camel-support/src/main/java/org/apache/camel/support/RouteOnDemandReloadStrategy.java @@ -0,0 +1,112 @@ +/* + * 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.support; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.apache.camel.api.management.ManagedOperation; +import org.apache.camel.api.management.ManagedResource; +import org.apache.camel.spi.Resource; +import org.apache.camel.util.FileUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Strategy for triggering on-demand reloading of Camel routes in a running Camel application. The strategy is triggered + * on-demand and reload all files from a directory (and subdirectories). + */ +@ManagedResource(description = "Managed RouteOnDemandReloadStrategy") +public class RouteOnDemandReloadStrategy extends RouteWatcherReloadStrategy { + + private static final Logger LOG = LoggerFactory.getLogger(RouteOnDemandReloadStrategy.class); + + public RouteOnDemandReloadStrategy() { + setScheduler(false); + } + + public RouteOnDemandReloadStrategy(String directory) { + super(directory); + setScheduler(false); + } + + public RouteOnDemandReloadStrategy(String directory, boolean recursive) { + super(directory, recursive); + setScheduler(false); + } + + /** + * Triggers on-demand reloading + */ + @ManagedOperation(description = "Trigger on-demand reloading") + public void onReload() { + onReload("JMX Management"); + } + + /** + * Triggers on-demand reloading + */ + public void onReload(Object source) { + try { + doOnReload(source); + incSucceededCounter(); + } catch (Exception e) { + incFailedCounter(); + LOG.warn("Error reloading routes due " + e.getMessage() + + ". This exception is ignored.", + e); + } + } + + protected void doOnReload(Object source) throws Exception { + List<Resource> properties = new ArrayList<>(); + List<Resource> routes = new ArrayList<>(); + + File dir = new File(getFolder()); + for (Path path : ResourceHelper.findInFileSystem(dir.toPath(), getPattern())) { + Resource res = ResourceHelper.resolveResource(getCamelContext(), "file:" + path.toString()); + String ext = FileUtil.onlyExt(path.getFileName().toString()); + if ("properties".equals(ext)) { + properties.add(res); + } else { + routes.add(res); + } + } + + if (LOG.isDebugEnabled()) { + LOG.debug("On-demand reload scanned {} files (properties: {}, routes: {})", + properties.size() + routes.size(), properties.size(), routes.size()); + } + + // reload properties first + boolean reloaded = false; + for (Resource res : properties) { + reloaded |= onPropertiesReload(res, false); + } + boolean removeEverything = routes.isEmpty(); + if (reloaded || !routes.isEmpty()) { + // trigger routes to also reload if properties was reloaded + onRouteReload(routes, removeEverything); + } else { + // rare situation where all routes are deleted + onRouteReload(null, removeEverything); + } + } + +} diff --git a/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java b/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java index a3ac3215fae..f63af35d3ec 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java @@ -147,9 +147,9 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg // attach listener that triggers the route update setResourceReload((name, resource) -> { if (name.endsWith(".properties")) { - onPropertiesReload(resource); + onPropertiesReload(resource, true); } else { - onRouteReload(resource); + onRouteReload(List.of(resource), true); } }); } @@ -162,7 +162,7 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg return "Live route reloading enabled (directory: " + dir + ")"; } - protected void onPropertiesReload(Resource resource) throws Exception { + protected boolean onPropertiesReload(Resource resource, boolean reloadRoutes) throws Exception { LOG.info("Reloading properties: {}. (Only Camel routes and components can be updated with changes)", resource.getLocation()); @@ -183,19 +183,21 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg pc.keepOnlyChangeProperties(changed); } - if (changed == null || !changed.isEmpty()) { - boolean reloaded = pc.reloadProperties(resource.getLocation()); + boolean reloaded = false; + if (changed != null && !changed.isEmpty()) { + reloaded = pc.reloadProperties(resource.getLocation()); if (reloaded) { - if (pr != null) { - pr.onReload(resource.getLocation(), changed); - } + pr.onReload(resource.getLocation(), changed); // trigger all routes to be reloaded - onRouteReload(null); + if (reloadRoutes) { + onRouteReload(null, true); + } } } + return reloaded; } - protected void onRouteReload(Resource resource) { + protected void onRouteReload(Collection<Resource> resources, boolean removeEverything) { // remember all existing resources List<Resource> sources = new ArrayList<>(); @@ -204,7 +206,7 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg // to the last working set previousSources.forEach(rs -> { // remember all the sources of the current routes (except the updated) - if (rs != null && !equalResourceLocation(resource, rs)) { + if (rs != null && !equalResourceLocation(resources, rs)) { sources.add(rs); } }); @@ -216,7 +218,7 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg // remember all the sources of the current routes (except the updated) getCamelContext().getRoutes().forEach(r -> { Resource rs = r.getSourceResource(); - if (rs != null && !equalResourceLocation(resource, rs)) { + if (rs != null && !equalResourceLocation(resources, rs)) { sources.add(rs); } }); @@ -227,8 +229,12 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg getCamelContext().getEndpointRegistry().clear(); } - if (resource != null && Files.exists(Paths.get(resource.getURI()))) { - sources.add(resource); + if (resources != null) { + for (Resource resource : resources) { + if (Files.exists(Paths.get(resource.getURI()))) { + sources.add(resource); + } + } } Collection<Resource> extras @@ -246,6 +252,11 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg previousSources.clear(); previousSources.addAll(sources); + // special situation where we remove all routes + if (removeEverything) { + sources.clear(); + } + // reload those other routes that was stopped and removed as we want to keep running those Set<String> ids = getCamelContext().adapt(ExtendedCamelContext.class).getRoutesLoader().updateRoutes(sources); @@ -312,6 +323,21 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg } } + /** + * Whether the target is loading any of the given sources + */ + private static boolean equalResourceLocation(Collection<Resource> sources, Resource target) { + if (sources == null || target == null || sources.isEmpty()) { + return false; + } + for (Resource source : sources) { + if (equalResourceLocation(source, target)) { + return true; + } + } + return false; + } + /** * Whether the two resources are loading the same resource */ diff --git a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java index 6d7dab139fc..5bda4c0790d 100644 --- a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java +++ b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java @@ -46,8 +46,11 @@ import org.apache.camel.console.DevConsoleRegistry; import org.apache.camel.spi.CliConnector; import org.apache.camel.spi.CliConnectorFactory; import org.apache.camel.spi.ContextReloadStrategy; +import org.apache.camel.spi.ResourceReloadStrategy; import org.apache.camel.support.DefaultContextReloadStrategy; import org.apache.camel.support.PatternHelper; +import org.apache.camel.support.RouteOnDemandReloadStrategy; +import org.apache.camel.support.RouteWatcherReloadStrategy; import org.apache.camel.support.service.ServiceHelper; import org.apache.camel.support.service.ServiceSupport; import org.apache.camel.util.FileUtil; @@ -249,12 +252,25 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C } else if ("gc".equals(action)) { System.gc(); } else if ("reload".equals(action)) { - ContextReloadStrategy reloader = camelContext.hasService(ContextReloadStrategy.class); - if (reloader == null) { - reloader = new DefaultContextReloadStrategy(); - camelContext.addService(reloader); + Optional<String> dir = camelContext.getPropertiesComponent().resolveProperty("camel.jbang.sourceDir"); + if (dir.isPresent()) { + // if using source-dir then reload this specific directory + RouteOnDemandReloadStrategy reloader = camelContext.hasService(RouteOnDemandReloadStrategy.class); + if (reloader == null) { + reloader = new RouteOnDemandReloadStrategy(dir.get(), true); + reloader.setPattern("*"); + camelContext.addService(reloader); + } + reloader.onReload("Camel CLI"); + } else { + // general camel reloading + ContextReloadStrategy reloader = camelContext.hasService(ContextReloadStrategy.class); + if (reloader == null) { + reloader = new DefaultContextReloadStrategy(); + camelContext.addService(reloader); + } + reloader.onReload("Camel CLI"); } - reloader.onReload("Camel CLI"); } else if ("reset-stats".equals(action)) { ManagedCamelContext mcc = camelContext.getExtension(ManagedCamelContext.class); if (mcc != null) { diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java index d444c3a0c66..1019a170aa6 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java @@ -538,6 +538,7 @@ public class Run extends CamelCommand { if (dev && (sourceDir != null || sjReload.length() > 0)) { main.addInitialProperty("camel.main.routesReloadEnabled", "true"); if (sourceDir != null) { + main.addInitialProperty("camel.jbang.sourceDir", sourceDir); main.addInitialProperty("camel.main.routesReloadDirectory", sourceDir); main.addInitialProperty("camel.main.routesReloadPattern", "*"); main.addInitialProperty("camel.main.routesReloadDirectoryRecursive", "true");
