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 <claus.ib...@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) {