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 ffaf973 CAMEL-17274: camel-core - Properties component should allow reloading properties ffaf973 is described below commit ffaf97347962ff523bf52060cc87b36a2a1ecf54 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu Dec 2 10:23:43 2021 +0100 CAMEL-17274: camel-core - Properties component should allow reloading properties --- .../apache/camel/spi/LoadablePropertiesSource.java | 9 +- .../org/apache/camel/spi/PropertiesComponent.java | 8 ++ .../org/apache/camel/spi/PropertiesSource.java | 2 +- .../AbstractLocationPropertiesSource.java | 22 +++- .../component/properties/PropertiesComponent.java | 28 +++++ .../PropertiesComponentPropertiesSourceTest.java | 5 + .../PropertiesComponentReloadPropertiesTest.java | 137 +++++++++++++++++++++ .../camel/support/RouteWatcherReloadStrategy.java | 29 +++-- .../main/java/org/apache/camel/util/IOHelper.java | 24 ++++ .../apache/camel/dsl/jbang/core/commands/Run.java | 7 ++ 10 files changed, 258 insertions(+), 13 deletions(-) diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/LoadablePropertiesSource.java b/core/camel-api/src/main/java/org/apache/camel/spi/LoadablePropertiesSource.java index dceafd8..950fdc1 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/LoadablePropertiesSource.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/LoadablePropertiesSource.java @@ -25,7 +25,7 @@ import org.apache.camel.Ordered; * A source for properties that can be loaded all at once during initialization, such as loading .properties files. * <p/> * A source can implement {@link Ordered} to control the ordering of which sources are used by the Camel properties - * component. The source with the highest precedence (lowest number) will be used first. + * component. The source with the highest precedence (the lowest number) will be used first. */ public interface LoadablePropertiesSource extends PropertiesSource { @@ -43,4 +43,11 @@ public interface LoadablePropertiesSource extends PropertiesSource { * @return the properties loaded. */ Properties loadProperties(Predicate<String> filter); + + /** + * Re-loads the properties from the file location + * + * @param location the location of the properties + */ + void reloadProperties(String location); } diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesComponent.java b/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesComponent.java index f540ea5..d7cef9c 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesComponent.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesComponent.java @@ -212,4 +212,12 @@ public interface PropertiesComponent extends StaticService { */ void setEncoding(String encoding); + /** + * Reload properties from the given location patterns. + * + * @param pattern patterns, or null to reload from all known locations + * @return true if some properties was reloaded + */ + boolean reloadProperties(String pattern); + } diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesSource.java b/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesSource.java index 493afae..7daa12a 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesSource.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesSource.java @@ -22,7 +22,7 @@ import org.apache.camel.Ordered; * A source for properties. * <p/> * A source can implement {@link Ordered} to control the ordering of which sources are used by the Camel properties - * component. The source with the highest precedence (lowest number) will be used first. + * component. The source with the highest precedence (the lowest number) will be used first. */ public interface PropertiesSource { diff --git a/core/camel-base/src/main/java/org/apache/camel/component/properties/AbstractLocationPropertiesSource.java b/core/camel-base/src/main/java/org/apache/camel/component/properties/AbstractLocationPropertiesSource.java index 4b67049..62d8918 100644 --- a/core/camel-base/src/main/java/org/apache/camel/component/properties/AbstractLocationPropertiesSource.java +++ b/core/camel-base/src/main/java/org/apache/camel/component/properties/AbstractLocationPropertiesSource.java @@ -21,6 +21,7 @@ import java.util.Properties; import java.util.function.Predicate; import org.apache.camel.spi.LoadablePropertiesSource; +import org.apache.camel.support.ResourceHelper; import org.apache.camel.support.service.ServiceSupport; import org.apache.camel.util.OrderedProperties; @@ -66,6 +67,23 @@ public abstract class AbstractLocationPropertiesSource extends ServiceSupport } @Override + public void reloadProperties(String location) { + String resolver = null; + if (ResourceHelper.hasScheme(location)) { + resolver = ResourceHelper.getScheme(location); + location = location.substring(resolver.length()); + } + PropertiesLocation loc = new PropertiesLocation(resolver, location); + Properties prop = loadPropertiesFromLocation(propertiesComponent, loc); + if (prop != null) { + prop = prepareLoadedProperties(prop); + // need to clear in case some properties was removed + properties.clear(); + properties.putAll(prop); + } + } + + @Override public String getProperty(String name) { return properties.getProperty(name); } @@ -85,7 +103,7 @@ public abstract class AbstractLocationPropertiesSource extends ServiceSupport * Strategy to prepare loaded properties before being used by Camel. * <p/> * This implementation will ensure values are trimmed, as loading properties from a file with values having trailing - * spaces is not automatic trimmed by the Properties API from the JDK. + * spaces is not automatically trimmed by the Properties API from the JDK. * * @param properties the properties * @return the prepared properties @@ -99,7 +117,7 @@ public abstract class AbstractLocationPropertiesSource extends ServiceSupport String s = (String) value; // trim any trailing spaces which can be a problem when loading from - // a properties file, note that java.util.Properties does already this + // a properties file, note that java.util.Properties do already this // for any potential leading spaces so there's nothing to do there value = trimTrailingWhitespaces(s); } diff --git a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java index 4e32fe2..5115750 100644 --- a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java +++ b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java @@ -32,6 +32,7 @@ import org.apache.camel.CamelContextAware; import org.apache.camel.ExtendedCamelContext; import org.apache.camel.StaticService; import org.apache.camel.api.management.ManagedAttribute; +import org.apache.camel.api.management.ManagedOperation; import org.apache.camel.api.management.ManagedResource; import org.apache.camel.spi.FactoryFinder; import org.apache.camel.spi.LoadablePropertiesSource; @@ -39,6 +40,7 @@ import org.apache.camel.spi.PropertiesFunction; import org.apache.camel.spi.PropertiesSource; import org.apache.camel.spi.annotations.JdkService; import org.apache.camel.support.OrderedComparator; +import org.apache.camel.support.PatternHelper; import org.apache.camel.support.service.ServiceHelper; import org.apache.camel.support.service.ServiceSupport; import org.apache.camel.util.FilePathResolver; @@ -576,6 +578,32 @@ public class PropertiesComponent extends ServiceSupport return sources; } + @ManagedOperation(description = "Reload properties from the given location patterns") + @Override + public boolean reloadProperties(String pattern) { + if (ObjectHelper.isEmpty(pattern)) { + pattern = "*"; + } + LOG.debug("Reloading properties (pattern: {})", pattern); + boolean answer = false; + + // find sources with this location to reload + for (PropertiesSource source : sources) { + if (source instanceof LocationPropertiesSource && source instanceof LoadablePropertiesSource) { + LocationPropertiesSource loc = (LocationPropertiesSource) source; + LoadablePropertiesSource loadable = (LoadablePropertiesSource) source; + String schemeAndPath = loc.getLocation().getResolver() + ":" + loc.getLocation().getPath(); + String path = loc.getLocation().getPath(); + if (PatternHelper.matchPattern(schemeAndPath, pattern) || PatternHelper.matchPattern(path, pattern)) { + loadable.reloadProperties(schemeAndPath); + LOG.trace("Reloaded properties: {}", schemeAndPath); + answer = true; + } + } + } + return answer; + } + @Override protected void doInit() throws Exception { super.doInit(); diff --git a/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentPropertiesSourceTest.java b/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentPropertiesSourceTest.java index 6229849..ebc10a0 100644 --- a/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentPropertiesSourceTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentPropertiesSourceTest.java @@ -160,5 +160,10 @@ public class PropertiesComponentPropertiesSourceTest { return props; } + + @Override + public void reloadProperties(String location) { + // noop + } } } diff --git a/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentReloadPropertiesTest.java b/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentReloadPropertiesTest.java new file mode 100644 index 0000000..ccbadb2 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentReloadPropertiesTest.java @@ -0,0 +1,137 @@ +/* + * 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.component.properties; + +import java.io.File; +import java.util.Properties; + +import org.apache.camel.CamelContext; +import org.apache.camel.ContextTestSupport; +import org.apache.camel.util.IOHelper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class PropertiesComponentReloadPropertiesTest extends ContextTestSupport { + + private String name; + private String name2; + + @Override + public boolean isUseRouteBuilder() { + return false; + } + + @Test + public void testReloadProperties() throws Exception { + context.start(); + + org.apache.camel.spi.PropertiesComponent pc = context.getPropertiesComponent(); + Properties prop = pc.loadProperties(); + + assertNotNull(prop); + assertEquals(2, prop.size()); + assertEquals("10", prop.getProperty("myQueueSize")); + assertEquals("Moes", prop.getProperty("bar")); + + IOHelper.writeText("myQueueSize = 20\nsay=cheese", new File(name)); + IOHelper.writeText("bar = Jacks", new File(name2)); + + // reload all + boolean reloaded = pc.reloadProperties(null); + assertTrue(reloaded); + prop = pc.loadProperties(); + + assertNotNull(prop); + assertEquals(3, prop.size()); + assertEquals("20", prop.getProperty("myQueueSize")); + assertEquals("cheese", prop.getProperty("say")); + assertEquals("Jacks", prop.getProperty("bar")); + } + + @Test + public void testReloadPropertiesPattern() throws Exception { + context.start(); + + org.apache.camel.spi.PropertiesComponent pc = context.getPropertiesComponent(); + Properties prop = pc.loadProperties(); + + assertNotNull(prop); + assertEquals(2, prop.size()); + assertEquals("10", prop.getProperty("myQueueSize")); + assertEquals("Moes", prop.getProperty("bar")); + + IOHelper.writeText("myQueueSize = 20\nsay=cheese", new File(name)); + IOHelper.writeText("bar = Jacks", new File(name2)); + + // reload only one file + boolean reloaded = pc.reloadProperties("file:" + name); + assertTrue(reloaded); + prop = pc.loadProperties(); + + assertNotNull(prop); + assertEquals(3, prop.size()); + assertEquals("20", prop.getProperty("myQueueSize")); + assertEquals("cheese", prop.getProperty("say")); + // should use old value as not reloaded + assertEquals("Moes", prop.getProperty("bar")); + } + + @Test + public void testReloadNotMatch() throws Exception { + context.start(); + + org.apache.camel.spi.PropertiesComponent pc = context.getPropertiesComponent(); + Properties prop = pc.loadProperties(); + + assertNotNull(prop); + assertEquals(2, prop.size()); + assertEquals("10", prop.getProperty("myQueueSize")); + assertEquals("Moes", prop.getProperty("bar")); + + // this files does not exist + boolean reloaded = pc.reloadProperties("file:foo.properties"); + assertFalse(reloaded); + prop = pc.loadProperties(); + + // properties unchanged + assertNotNull(prop); + assertEquals(2, prop.size()); + assertEquals("10", prop.getProperty("myQueueSize")); + assertEquals("Moes", prop.getProperty("bar")); + } + + @Override + protected CamelContext createCamelContext() throws Exception { + CamelContext context = super.createCamelContext(); + + name = fileUri() + "/myreload.properties"; + name = name.substring(5); + IOHelper.writeText("myQueueSize = 10", new File(name)); + + name2 = fileUri() + "/myreload2.properties"; + name2 = name2.substring(5); + IOHelper.writeText("bar = Moes", new File(name2)); + + context.getPropertiesComponent().setLocation("file:" + name + ",file:" + name2); + return context; + } + +} 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 bf0db4c..e765e01 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 @@ -27,6 +27,7 @@ import org.apache.camel.Route; import org.apache.camel.RuntimeCamelException; import org.apache.camel.ServiceStatus; import org.apache.camel.StartupSummaryLevel; +import org.apache.camel.spi.PropertiesComponent; import org.apache.camel.spi.Resource; import org.apache.camel.util.AntPathMatcher; import org.apache.camel.util.FileUtil; @@ -121,9 +122,9 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg // attach listener that triggers the route update setResourceReload((name, resource) -> { if (name.endsWith(".properties")) { - onPropertiesReload(name, resource); + onPropertiesReload(resource); } else { - onRouteReload(name, resource); + onRouteReload(resource); } }); } @@ -131,7 +132,19 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg super.doStart(); } - protected void onRouteReload(String name, Resource resource) { + protected void onPropertiesReload(Resource resource) { + LOG.info("Reloading properties: {}. (Only Camel routes can be updated with changes)", + resource.getLocation()); + + PropertiesComponent pc = getCamelContext().getPropertiesComponent(); + boolean reloaded = pc.reloadProperties(resource.getLocation()); + if (reloaded) { + // trigger all routes to be reloaded + onRouteReload(null); + } + } + + protected void onRouteReload(Resource resource) { // remember all existing resources List<Resource> sources = new ArrayList<>(); @@ -141,7 +154,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 && !rs.getLocation().equals(resource.getLocation())) { + if (rs != null && (resource == null || !rs.getLocation().equals(resource.getLocation()))) { sources.add(rs); } }); @@ -152,7 +165,9 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg getCamelContext().getEndpointRegistry().clear(); } - sources.add(resource); + if (resource != null) { + sources.add(resource); + } // reload those other routes that was stopped and removed as we want to keep running those Set<String> ids @@ -211,8 +226,4 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg } } - protected void onPropertiesReload(String name, Resource resource) { - // TODO: implement me - } - } diff --git a/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java b/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java index abfa1e4..300065d 100644 --- a/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java +++ b/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java @@ -473,6 +473,30 @@ public final class IOHelper { } /** + * Writes the text to the file. + */ + public static void writeText(String text, File file) throws IOException { + if (!file.exists()) { + String path = FileUtil.onlyPath(file.getPath()); + if (path != null) { + new File(path).mkdirs(); + } + } + writeText(text, new FileOutputStream(file, false)); + } + + /** + * Writes the text to the stream. + */ + public static void writeText(String text, OutputStream os) throws IOException { + try { + os.write(text.getBytes()); + } finally { + close(os); + } + } + + /** * Get the charset name from the content type string * * @param contentType the content type 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 c324f7d..b5d1403 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 @@ -157,11 +157,18 @@ class Run implements Callable<Integer> { for (String file : files) { // check for properties files if (file.endsWith(".properties")) { + if (!ResourceHelper.hasScheme(file) && !file.startsWith("github:")) { + file = "file:" + file; + } if (ObjectHelper.isEmpty(propertiesFiles)) { propertiesFiles = file; } else { propertiesFiles = propertiesFiles + "," + file; } + if (reload && file.startsWith("file:")) { + // we can only reload if file based + sjReload.add(file.substring(5)); + } continue; }