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/30 15:14:58 UTC

(camel) 01/25: CAMEL-19749: Add variables as concept to Camel

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch var
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 8e81b17b175544e5f7d10ba4c0449ddca06764fc
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu Dec 28 10:40:30 2023 +0100

    CAMEL-19749: Add variables as concept to Camel
---
 .../src/main/java/org/apache/camel/Exchange.java   |  77 ++++++++++++
 .../java/org/apache/camel/ExchangeTestSupport.java |   1 +
 .../org/apache/camel/impl/DefaultExchangeTest.java | 136 +++++++++++++++++++++
 .../org/apache/camel/support/AbstractExchange.java | 109 ++++++++++++++++-
 4 files changed, 322 insertions(+), 1 deletion(-)

diff --git a/core/camel-api/src/main/java/org/apache/camel/Exchange.java b/core/camel-api/src/main/java/org/apache/camel/Exchange.java
index 9e7abd83ad0..a91f9865b6c 100644
--- a/core/camel-api/src/main/java/org/apache/camel/Exchange.java
+++ b/core/camel-api/src/main/java/org/apache/camel/Exchange.java
@@ -419,6 +419,83 @@ public interface Exchange {
      */
     boolean hasProperties();
 
+    /**
+     * Returns a variable by name
+     *
+     * @param  name the name of the variable
+     * @return      the value of the given variable or <tt>null</tt> if there is no variable for the given name
+     */
+    Object getVariable(String name);
+
+    /**
+     * Returns a variable by name and specifying the type required
+     *
+     * @param  name the name of the variable
+     * @param  type the type of the variable
+     * @return      the value of the given variable or <tt>null</tt> if there is no variable for the given name or
+     *              <tt>null</tt> if it cannot be converted to the given type
+     */
+    <T> T getVariable(String name, Class<T> type);
+
+    /**
+     * Returns a variable by name and specifying the type required
+     *
+     * @param  name         the name of the variable
+     * @param  defaultValue the default value to return if variable was absent
+     * @param  type         the type of the variable
+     * @return              the value of the given variable or <tt>defaultValue</tt> if there is no variable for the
+     *                      given name or <tt>null</tt> if it cannot be converted to the given type
+     */
+    <T> T getVariable(String name, Object defaultValue, Class<T> type);
+
+    /**
+     * Sets a varialbe on the exchange
+     *
+     * @param name  of the variable
+     * @param value the value of the variable
+     */
+    void setVariable(String name, Object value);
+
+    /**
+     * Removes the given variable
+     *
+     * @param  name of the variable
+     * @return      the old value of the variable
+     */
+    Object removeVariable(String name);
+
+    /**
+     * Remove all the variables matching a specific pattern
+     *
+     * @param  pattern pattern of names
+     * @return         boolean whether any variables matched
+     */
+    boolean removeVariables(String pattern);
+
+    /**
+     * Removes the variables that match the given <tt>pattern</tt>, except for the ones matching one or more
+     * <tt>excludePatterns</tt>
+     *
+     * @param  pattern         pattern of names that should be removed
+     * @param  excludePatterns one or more pattern of variable names that should be excluded (= preserved)
+     * @return                 boolean whether any variables matched
+     */
+    boolean removeVariables(String pattern, String... excludePatterns);
+
+    /**
+     * Returns the variables
+     *
+     * @return the variables in a Map
+     */
+    Map<String, Object> getVariables();
+
+    /**
+     * Returns whether any variables have been set
+     *
+     * @return <tt>true</tt> if any variables has been set
+     */
+    boolean hasVariables();
+
     /**
      * Returns the inbound request message
      *
diff --git a/core/camel-core/src/test/java/org/apache/camel/ExchangeTestSupport.java b/core/camel-core/src/test/java/org/apache/camel/ExchangeTestSupport.java
index 422d82d141f..872c588d6e3 100644
--- a/core/camel-core/src/test/java/org/apache/camel/ExchangeTestSupport.java
+++ b/core/camel-core/src/test/java/org/apache/camel/ExchangeTestSupport.java
@@ -44,6 +44,7 @@ public abstract class ExchangeTestSupport extends ContextTestSupport {
         in.setBody("<hello id='m123'>world!</hello>");
 
         exchange.setProperty("foobar", "cba");
+        exchange.setVariable("cheese", "gauda");
     }
 
     @Override
diff --git a/core/camel-core/src/test/java/org/apache/camel/impl/DefaultExchangeTest.java b/core/camel-core/src/test/java/org/apache/camel/impl/DefaultExchangeTest.java
index 6147d9877ec..b8aa1d8af06 100644
--- a/core/camel-core/src/test/java/org/apache/camel/impl/DefaultExchangeTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/impl/DefaultExchangeTest.java
@@ -142,6 +142,30 @@ public class DefaultExchangeTest extends ExchangeTestSupport {
         assertEquals("banana", exchange.getProperty("beer", "banana", String.class));
     }
 
+    @Test
+    public void testVariable() throws Exception {
+        exchange.removeVariable("cheese");
+        assertFalse(exchange.hasVariables());
+
+        exchange.setVariable("fruit", "apple");
+        assertTrue(exchange.hasVariables());
+
+        assertEquals("apple", exchange.getVariable("fruit"));
+        assertNull(exchange.getVariable("beer"));
+        assertNull(exchange.getVariable("beer", String.class));
+
+        // Current TypeConverter support to turn the null value to false of
+        // boolean,
+        // as assertEquals needs the Object as the parameter, we have to use
+        // Boolean.FALSE value in this case
+        assertEquals(Boolean.FALSE, exchange.getVariable("beer", boolean.class));
+        assertNull(exchange.getVariable("beer", Boolean.class));
+
+        assertEquals("apple", exchange.getVariable("fruit", String.class));
+        assertEquals("apple", exchange.getVariable("fruit", "banana", String.class));
+        assertEquals("banana", exchange.getVariable("beer", "banana", String.class));
+    }
+
     @Test
     public void testRemoveProperties() throws Exception {
         exchange.removeProperty("foobar");
@@ -164,6 +188,28 @@ public class DefaultExchangeTest extends ExchangeTestSupport {
         assertEquals("Africa", exchange.getProperty("zone", String.class));
     }
 
+    @Test
+    public void testRemoveVariables() throws Exception {
+        exchange.removeVariable("cheese");
+        assertFalse(exchange.hasVariables());
+
+        exchange.setVariable("fruit", "apple");
+        exchange.setVariable("fruit1", "banana");
+        exchange.setVariable("zone", "Africa");
+        assertTrue(exchange.hasVariables());
+
+        assertEquals("apple", exchange.getVariable("fruit"));
+        assertEquals("banana", exchange.getVariable("fruit1"));
+        assertEquals("Africa", exchange.getVariable("zone"));
+
+        exchange.removeVariables("fr*");
+        assertTrue(exchange.hasVariables());
+        assertEquals(1, exchange.getVariables().size());
+        assertNull(exchange.getVariable("fruit", String.class));
+        assertNull(exchange.getVariable("fruit1", String.class));
+        assertEquals("Africa", exchange.getVariable("zone", String.class));
+    }
+
     @Test
     public void testRemoveAllProperties() throws Exception {
         exchange.removeProperty("foobar");
@@ -179,6 +225,21 @@ public class DefaultExchangeTest extends ExchangeTestSupport {
         assertEquals(0, exchange.getProperties().size());
     }
 
+    @Test
+    public void testRemoveAllVariables() throws Exception {
+        exchange.removeVariable("cheese");
+        assertFalse(exchange.hasVariables());
+
+        exchange.setVariable("fruit", "apple");
+        exchange.setVariable("fruit1", "banana");
+        exchange.setVariable("zone", "Africa");
+        assertTrue(exchange.hasVariables());
+
+        exchange.removeVariables("*");
+        assertFalse(exchange.hasVariables());
+        assertEquals(0, exchange.getVariables().size());
+    }
+
     @Test
     public void testRemovePropertiesWithExclusion() throws Exception {
         exchange.removeProperty("foobar");
@@ -204,6 +265,31 @@ public class DefaultExchangeTest extends ExchangeTestSupport {
         assertEquals("Africa", exchange.getProperty("zone", String.class));
     }
 
+    @Test
+    public void testRemoveVariablesWithExclusion() throws Exception {
+        exchange.removeVariable("cheese");
+        assertFalse(exchange.hasVariables());
+
+        exchange.setVariable("fruit", "apple");
+        exchange.setVariable("fruit1", "banana");
+        exchange.setVariable("fruit2", "peach");
+        exchange.setVariable("zone", "Africa");
+        assertTrue(exchange.hasVariables());
+
+        assertEquals("apple", exchange.getVariable("fruit"));
+        assertEquals("banana", exchange.getVariable("fruit1"));
+        assertEquals("peach", exchange.getVariable("fruit2"));
+        assertEquals("Africa", exchange.getVariable("zone"));
+
+        exchange.removeVariables("fr*", "fruit1", "fruit2");
+        assertTrue(exchange.hasVariables());
+        assertEquals(3, exchange.getVariables().size());
+        assertNull(exchange.getVariable("fruit", String.class));
+        assertEquals("banana", exchange.getVariable("fruit1", String.class));
+        assertEquals("peach", exchange.getVariable("fruit2", String.class));
+        assertEquals("Africa", exchange.getVariable("zone", String.class));
+    }
+
     @Test
     public void testRemovePropertiesPatternWithAllExcluded() throws Exception {
         exchange.removeProperty("foobar");
@@ -229,6 +315,31 @@ public class DefaultExchangeTest extends ExchangeTestSupport {
         assertEquals("Africa", exchange.getProperty("zone", String.class));
     }
 
+    @Test
+    public void testRemoveVariablesPatternWithAllExcluded() throws Exception {
+        exchange.removeVariable("cheese");
+        assertFalse(exchange.hasVariables());
+
+        exchange.setVariable("fruit", "apple");
+        exchange.setVariable("fruit1", "banana");
+        exchange.setVariable("fruit2", "peach");
+        exchange.setVariable("zone", "Africa");
+        assertTrue(exchange.hasVariables());
+
+        assertEquals("apple", exchange.getVariable("fruit"));
+        assertEquals("banana", exchange.getVariable("fruit1"));
+        assertEquals("peach", exchange.getVariable("fruit2"));
+        assertEquals("Africa", exchange.getVariable("zone"));
+
+        exchange.removeVariables("fr*", "fruit", "fruit1", "fruit2", "zone");
+        assertTrue(exchange.hasVariables());
+        assertEquals(4, exchange.getVariables().size());
+        assertEquals("apple", exchange.getVariable("fruit", String.class));
+        assertEquals("banana", exchange.getVariable("fruit1", String.class));
+        assertEquals("peach", exchange.getVariable("fruit2", String.class));
+        assertEquals("Africa", exchange.getVariable("zone", String.class));
+    }
+
     @Test
     public void testRemoveInternalProperties() throws Exception {
         exchange.setProperty(ExchangePropertyKey.CHARSET_NAME, "iso-8859-1");
@@ -265,6 +376,31 @@ public class DefaultExchangeTest extends ExchangeTestSupport {
         assertEquals(3, exchange.getAllProperties().size());
     }
 
+    @Test
+    public void testCopyExchangeWithVariables() throws Exception {
+        exchange.setVariable("beer", "Carlsberg");
+        assertEquals(2, exchange.getVariables().size());
+
+        Exchange copy = exchange.copy();
+        assertTrue(copy.hasVariables());
+        assertEquals(2, copy.getVariables().size());
+        assertEquals("gauda", copy.getVariable("cheese"));
+        assertEquals("Carlsberg", copy.getVariable("beer"));
+
+        exchange.setVariable("beer", "Heineken");
+        assertEquals("Carlsberg", copy.getVariable("beer"));
+
+        exchange.removeVariable("beer");
+        assertEquals(1, exchange.getVariables().size());
+        assertEquals("Carlsberg", copy.getVariable("beer"));
+        assertEquals(2, copy.getVariables().size());
+
+        exchange.removeVariables("*");
+        assertFalse(exchange.hasVariables());
+        assertTrue(copy.hasVariables());
+        assertEquals(2, copy.getVariables().size());
+    }
+
     @Test
     public void testInType() throws Exception {
         exchange.setIn(new MyMessage(context));
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java b/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java
index ce3acd09507..b0e4ddbcffc 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java
@@ -53,6 +53,7 @@ abstract class AbstractExchange implements Exchange {
 
     protected final CamelContext context;
     protected Map<String, Object> properties; // create properties on-demand as we use internal properties mostly
+    protected Map<String, Object> variables;  // create variables on-demand
     protected Message in;
     protected Message out;
     protected Exception exception;
@@ -65,6 +66,7 @@ abstract class AbstractExchange implements Exchange {
     private final ExtendedExchangeExtension privateExtension;
     private RedeliveryTraitPayload externalRedelivered = RedeliveryTraitPayload.UNDEFINED_REDELIVERY;
 
+    // TODO: variables ?
     protected AbstractExchange(CamelContext context, EnumMap<ExchangePropertyKey, Object> internalProperties,
                                Map<String, Object> properties) {
         this.context = context;
@@ -124,10 +126,12 @@ abstract class AbstractExchange implements Exchange {
         privateExtension.setErrorHandlerHandled(parent.getExchangeExtension().getErrorHandlerHandled());
         privateExtension.setStreamCacheDisabled(parent.getExchangeExtension().isStreamCacheDisabled());
 
+        if (parent.hasVariables()) {
+            this.variables = safeCopyProperties(parent.variables);
+        }
         if (parent.hasProperties()) {
             this.properties = safeCopyProperties(parent.properties);
         }
-
         if (parent.hasSafeCopyProperties()) {
             this.safeCopyProperties = parent.copySafeCopyProperties();
         }
@@ -385,6 +389,109 @@ abstract class AbstractExchange implements Exchange {
         return safeCopyProperties != null && !safeCopyProperties.isEmpty();
     }
 
+    @Override
+    public Object getVariable(String name) {
+        if (variables != null) {
+            return variables.get(name);
+        }
+        return null;
+    }
+
+    @Override
+    public <T> T getVariable(String name, Class<T> type) {
+        Object value = getVariable(name);
+        return evalPropertyValue(type, value);
+    }
+
+    @Override
+    public <T> T getVariable(String name, Object defaultValue, Class<T> type) {
+        Object value = getVariable(name);
+        return evalPropertyValue(defaultValue, type, value);
+    }
+
+    @Override
+    public void setVariable(String name, Object value) {
+        if (value != null) {
+            // avoid the NullPointException
+            if (variables == null) {
+                this.variables = new ConcurrentHashMap<>(8);
+            }
+            variables.put(name, value);
+        } else if (variables != null) {
+            // if the value is null, we just remove the key from the map
+            variables.remove(name);
+        }
+    }
+
+    @Override
+    public Object removeVariable(String name) {
+        if (!hasVariables()) {
+            return null;
+        }
+        return variables.remove(name);
+    }
+
+    @Override
+    public boolean removeVariables(String pattern) {
+        return removeVariables(pattern, (String[]) null);
+    }
+
+    @Override
+    public boolean removeVariables(String pattern, String... excludePatterns) {
+        // special optimized
+        if (excludePatterns == null && "*".equals(pattern)) {
+            if (variables != null) {
+                variables.clear();
+            }
+            return true;
+        }
+
+        boolean matches = false;
+
+        // store keys to be removed as we cannot loop and remove at the same time in implementations such as HashMap
+        if (variables != null) {
+            Set<String> toBeRemoved = null;
+            for (String key : variables.keySet()) {
+                if (PatternHelper.matchPattern(key, pattern)) {
+                    if (excludePatterns != null && PatternHelper.isExcludePatternMatch(key, excludePatterns)) {
+                        continue;
+                    }
+                    matches = true;
+                    if (toBeRemoved == null) {
+                        toBeRemoved = new HashSet<>();
+                    }
+                    toBeRemoved.add(key);
+                }
+            }
+
+            if (matches) {
+                if (toBeRemoved.size() == variables.size()) {
+                    // special optimization when all should be removed
+                    variables.clear();
+                } else {
+                    for (String key : toBeRemoved) {
+                        variables.remove(key);
+                    }
+                }
+            }
+        }
+
+        return matches;
+    }
+
+    @Override
+    public Map<String, Object> getVariables() {
+        if (variables == null) {
+            this.variables = new ConcurrentHashMap<>(8);
+        }
+        return variables;
+    }
+
+    @Override
+    public boolean hasVariables() {
+        return variables != null && !variables.isEmpty();
+    }
+
     @Override
     public Message getIn() {
         if (in == null) {