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;
             }
 

Reply via email to