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 91ecb8cebc30 CAMEL-26426: camel-core - AbstractExchange: a field for
TO_ENDPOINT and a lazily created internal properties map (#28761)
91ecb8cebc30 is described below
commit 91ecb8cebc309a5e6a64f33812feaa02dbf4d2bb
Author: Adriano Machado <[email protected]>
AuthorDate: Sat Oct 31 05:29:28 2026 -0400
CAMEL-26426: camel-core - AbstractExchange: a field for TO_ENDPOINT and a
lazily created internal properties map (#28761)
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
.../apache/camel/support/AbstractExchangeTest.java | 213 +++++++++++++++++++++
.../org/apache/camel/support/AbstractExchange.java | 73 +++++--
.../camel/support/DefaultPooledExchange.java | 2 +-
.../camel/support/ExtendedExchangeExtension.java | 4 +-
4 files changed, 270 insertions(+), 22 deletions(-)
diff --git
a/core/camel-core/src/test/java/org/apache/camel/support/AbstractExchangeTest.java
b/core/camel-core/src/test/java/org/apache/camel/support/AbstractExchangeTest.java
index 6e9179a909c8..788f705d5aa9 100644
---
a/core/camel-core/src/test/java/org/apache/camel/support/AbstractExchangeTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/support/AbstractExchangeTest.java
@@ -16,22 +16,34 @@
*/
package org.apache.camel.support;
+import java.util.Map;
+
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
+import org.apache.camel.ExchangePropertyKey;
import org.apache.camel.clock.Clock;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.spi.DataType;
import org.apache.camel.spi.DataTypeAware;
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.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* The unit test for the class {@link AbstractExchange}.
*/
public class AbstractExchangeTest {
+ private static final String URI = "mock://foo";
+
+ private final CamelContext context = new DefaultCamelContext();
+
static class CustomAbstractExchange extends AbstractExchange {
private final Clock clock = new MonotonicClock();
@@ -77,4 +89,205 @@ public class AbstractExchangeTest {
assertSame(type2, ((DataTypeAware) e2.getMessage()).getDataType());
}
+ @Test
+ void shouldNotCreateInternalPropertiesForToEndpoint() {
+ // every send sets this key, so it must not cost every exchange the
map sized for all the keys
+ DefaultExchange exchange = new DefaultExchange(context);
+ assertNull(exchange.internalProperties);
+
+ exchange.setProperty(ExchangePropertyKey.TO_ENDPOINT, URI);
+ assertEquals(URI,
exchange.getProperty(ExchangePropertyKey.TO_ENDPOINT));
+ assertNull(exchange.internalProperties);
+
+ AbstractExchange copy = (AbstractExchange) exchange.copy();
+ assertNull(copy.internalProperties);
+ }
+
+ @Test
+ void shouldKeepToEndpointBesideOtherInternalProperties() {
+ DefaultExchange exchange = new DefaultExchange(context);
+ exchange.setProperty(ExchangePropertyKey.TO_ENDPOINT, URI);
+ exchange.setProperty(ExchangePropertyKey.CHARSET_NAME, "UTF-8");
+
+ assertNotNull(exchange.internalProperties);
+ assertEquals(URI,
exchange.getProperty(ExchangePropertyKey.TO_ENDPOINT));
+ assertEquals("UTF-8",
exchange.getProperty(ExchangePropertyKey.CHARSET_NAME));
+
+ assertEquals("UTF-8",
exchange.removeProperty(ExchangePropertyKey.CHARSET_NAME));
+ assertEquals(URI,
exchange.getProperty(ExchangePropertyKey.TO_ENDPOINT));
+ }
+
+ @Test
+ void shouldReadOtherInternalPropertiesWithoutTheMap() {
+ DefaultExchange exchange = new DefaultExchange(context);
+
+ assertNull(exchange.getProperty(ExchangePropertyKey.CHARSET_NAME));
+ assertNull(exchange.getProperty(Exchange.CHARSET_NAME));
+ assertNull(exchange.removeProperty(ExchangePropertyKey.CHARSET_NAME));
+ assertTrue(exchange.getInternalProperties().isEmpty());
+ assertTrue(exchange.getAllProperties().isEmpty());
+ }
+
+ @Test
+ void shouldAccessToEndpointByKeyAndByName() {
+ // the name is the public constant that routes and components use
+ DefaultExchange exchange = new DefaultExchange(context);
+
+ exchange.setProperty(Exchange.TO_ENDPOINT, URI);
+ assertEquals(URI,
exchange.getProperty(ExchangePropertyKey.TO_ENDPOINT));
+ assertEquals(URI, exchange.getProperty(Exchange.TO_ENDPOINT));
+ assertEquals(URI, exchange.getProperty(Exchange.TO_ENDPOINT,
String.class));
+
+ assertEquals(URI, exchange.removeProperty(Exchange.TO_ENDPOINT));
+ assertNull(exchange.getProperty(ExchangePropertyKey.TO_ENDPOINT));
+
+ exchange.setProperty(ExchangePropertyKey.TO_ENDPOINT, URI);
+ assertEquals(URI,
exchange.removeProperty(ExchangePropertyKey.TO_ENDPOINT));
+ assertNull(exchange.getProperty(Exchange.TO_ENDPOINT));
+ }
+
+ @Test
+ void shouldListToEndpointWithTheInternalProperties() {
+ // the dev consoles and tracers dump the exchange with these
+ DefaultExchange exchange = new DefaultExchange(context);
+ exchange.setProperty(ExchangePropertyKey.TO_ENDPOINT, URI);
+ exchange.setProperty("foo", "bar");
+
+ assertEquals(Map.of("CamelToEndpoint", URI),
exchange.getInternalProperties());
+ assertEquals(Map.of("CamelToEndpoint", URI, "foo", "bar"),
exchange.getAllProperties());
+ }
+
+ @Test
+ void shouldCopyToEndpointIndependently() {
+ DefaultExchange exchange = new DefaultExchange(context);
+ exchange.setProperty(ExchangePropertyKey.TO_ENDPOINT, URI);
+
+ Exchange copy = exchange.copy();
+ assertEquals(URI, copy.getProperty(ExchangePropertyKey.TO_ENDPOINT));
+
+ copy.setProperty(ExchangePropertyKey.TO_ENDPOINT, "mock://bar");
+ assertEquals(URI,
exchange.getProperty(ExchangePropertyKey.TO_ENDPOINT));
+ }
+
+ @Test
+ void shouldCopyInternalPropertiesIndependently() {
+ DefaultExchange exchange = new DefaultExchange(context);
+ exchange.setProperty(ExchangePropertyKey.CHARSET_NAME, "UTF-8");
+
+ Exchange copy = exchange.copy();
+ assertEquals("UTF-8",
copy.getProperty(ExchangePropertyKey.CHARSET_NAME));
+
+ copy.setProperty(ExchangePropertyKey.CHARSET_NAME, "ISO-8859-1");
+ assertEquals("UTF-8",
exchange.getProperty(ExchangePropertyKey.CHARSET_NAME));
+ }
+
+ @Test
+ void shouldCreateCopyWithToEndpoint() {
+ DefaultExchange exchange = new DefaultExchange(context);
+ exchange.setProperty(ExchangePropertyKey.TO_ENDPOINT, URI);
+ exchange.setProperty("foo", "bar");
+
+ Exchange copy =
exchange.getExchangeExtension().createCopyWithProperties(context);
+
+ assertEquals(URI, copy.getProperty(ExchangePropertyKey.TO_ENDPOINT));
+ assertEquals("bar", copy.getProperty("foo"));
+ assertNull(((AbstractExchange) copy).internalProperties);
+ }
+
+ @Test
+ void shouldCreateCopyWithInternalProperties() {
+ DefaultExchange exchange = new DefaultExchange(context);
+ exchange.setProperty(ExchangePropertyKey.CHARSET_NAME, "UTF-8");
+
+ Exchange copy =
exchange.getExchangeExtension().createCopyWithProperties(context);
+
+ assertEquals("UTF-8",
copy.getProperty(ExchangePropertyKey.CHARSET_NAME));
+ }
+
+ @Test
+ void shouldCopyInternalPropertiesToTarget() {
+ DefaultExchange source = new DefaultExchange(context);
+ source.setProperty(ExchangePropertyKey.TO_ENDPOINT, URI);
+ source.setProperty(ExchangePropertyKey.CHARSET_NAME, "UTF-8");
+
+ DefaultExchange target = new DefaultExchange(context);
+ source.getExchangeExtension().copyInternalProperties(target);
+
+ assertEquals(URI, target.getProperty(ExchangePropertyKey.TO_ENDPOINT));
+ assertEquals("UTF-8",
target.getProperty(ExchangePropertyKey.CHARSET_NAME));
+ // the target has its own map, so it can change without changing the
source
+ target.setProperty(ExchangePropertyKey.CHARSET_NAME, "ISO-8859-1");
+ assertEquals("UTF-8",
source.getProperty(ExchangePropertyKey.CHARSET_NAME));
+ }
+
+ @Test
+ void shouldKeepPropertiesOfTargetThatTheSourceDoesNotHave() {
+ DefaultExchange target = new DefaultExchange(context);
+ target.setProperty(ExchangePropertyKey.TO_ENDPOINT, URI);
+ target.setProperty(ExchangePropertyKey.CHARSET_NAME, "UTF-8");
+
+ new
DefaultExchange(context).getExchangeExtension().copyInternalProperties(target);
+
+ assertEquals(URI, target.getProperty(ExchangePropertyKey.TO_ENDPOINT));
+ assertEquals("UTF-8",
target.getProperty(ExchangePropertyKey.CHARSET_NAME));
+ }
+
+ @Test
+ void shouldKeepToEndpointOfTargetWhenTheSourceRemovedIt() {
+ // a removed property is not in the source any more, so it must not
clear the value of the target
+ DefaultExchange source = new DefaultExchange(context);
+ source.setProperty(ExchangePropertyKey.TO_ENDPOINT, "mock://bar");
+ source.removeProperty(ExchangePropertyKey.TO_ENDPOINT);
+
+ DefaultExchange target = new DefaultExchange(context);
+ target.setProperty(ExchangePropertyKey.TO_ENDPOINT, URI);
+
+ source.getExchangeExtension().copyInternalProperties(target);
+
+ assertEquals(URI, target.getProperty(ExchangePropertyKey.TO_ENDPOINT));
+ }
+
+ @Test
+ void shouldRemoveToEndpointWithAllProperties() {
+ DefaultExchange exchange = new DefaultExchange(context);
+ exchange.setProperty(ExchangePropertyKey.TO_ENDPOINT, URI);
+
+ assertTrue(exchange.removeProperties("*"));
+ assertNull(exchange.getProperty(ExchangePropertyKey.TO_ENDPOINT));
+ }
+
+ @Test
+ void shouldRemoveToEndpointByPattern() {
+ DefaultExchange exchange = new DefaultExchange(context);
+ exchange.setProperty(ExchangePropertyKey.TO_ENDPOINT, URI);
+
+ assertTrue(exchange.removeProperties("CamelTo*"));
+ assertNull(exchange.getProperty(ExchangePropertyKey.TO_ENDPOINT));
+ }
+
+ @Test
+ void shouldKeepToEndpointExcludedFromPattern() {
+ DefaultExchange exchange = new DefaultExchange(context);
+ exchange.setProperty(ExchangePropertyKey.TO_ENDPOINT, URI);
+ exchange.setProperty(ExchangePropertyKey.CHARSET_NAME, "UTF-8");
+
+ exchange.removeProperties("Camel*", "CamelToEndpoint");
+
+ assertEquals(URI,
exchange.getProperty(ExchangePropertyKey.TO_ENDPOINT));
+ assertNull(exchange.getProperty(ExchangePropertyKey.CHARSET_NAME));
+ }
+
+ @Test
+ void shouldClearInternalPropertiesOfPooledExchangeWhenDone() {
+ // a reused exchange must not see the endpoint of its previous use
+ DefaultPooledExchange exchange = new DefaultPooledExchange(context);
+ exchange.setProperty(ExchangePropertyKey.TO_ENDPOINT, URI);
+ exchange.setProperty(ExchangePropertyKey.CHARSET_NAME, "UTF-8");
+ assertFalse(exchange.getInternalProperties().isEmpty());
+
+ exchange.done();
+
+ assertTrue(exchange.getInternalProperties().isEmpty());
+ }
+
}
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 102bee085166..5680f5945e6d 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
@@ -58,7 +58,9 @@ abstract class AbstractExchange implements Exchange,
ExchangeExtension {
private final CamelContext context;
- protected final EnumMap<ExchangePropertyKey, Object> internalProperties;
+ // created when needed, as the map is sized for all the keys. TO_ENDPOINT
is set on every send, so it has its own field
+ protected EnumMap<ExchangePropertyKey, Object> internalProperties;
+ private Object toEndpoint;
protected Map<String, Object> properties;
protected Map<String, SafeCopyProperty> safeCopyProperties;
protected ExchangeVariableRepository variableRepository;
@@ -96,7 +98,7 @@ abstract class AbstractExchange implements Exchange,
ExchangeExtension {
protected AbstractExchange(CamelContext context,
EnumMap<ExchangePropertyKey, Object> internalProperties,
Map<String, Object> properties) {
this.context = context;
- this.internalProperties = new EnumMap<>(internalProperties);
+ this.internalProperties = internalProperties != null ? new
EnumMap<>(internalProperties) : null;
this.properties = safeCopyProperties(properties);
}
@@ -107,13 +109,11 @@ abstract class AbstractExchange implements Exchange,
ExchangeExtension {
protected AbstractExchange(CamelContext context, ExchangePattern pattern) {
this.context = context;
this.pattern = pattern;
- this.internalProperties = new EnumMap<>(ExchangePropertyKey.class);
}
protected AbstractExchange(Exchange parent) {
this.context = parent.getContext();
this.pattern = parent.getPattern();
- this.internalProperties = new EnumMap<>(ExchangePropertyKey.class);
this.fromEndpoint = parent.getFromEndpoint();
this.fromRouteId = parent.getFromRouteId();
this.unitOfWork = parent.getUnitOfWork();
@@ -123,7 +123,8 @@ abstract class AbstractExchange implements Exchange,
ExchangeExtension {
protected AbstractExchange(AbstractExchange parent) {
this.context = parent.getContext();
this.pattern = parent.getPattern();
- this.internalProperties = new EnumMap<>(parent.internalProperties);
+ this.internalProperties = parent.internalProperties != null ? new
EnumMap<>(parent.internalProperties) : null;
+ this.toEndpoint = parent.toEndpoint;
this.fromEndpoint = parent.getFromEndpoint();
this.fromRouteId = parent.getFromRouteId();
this.unitOfWork = parent.getUnitOfWork();
@@ -171,13 +172,15 @@ abstract class AbstractExchange implements Exchange,
ExchangeExtension {
public Exchange copy() {
AbstractExchange exchange = newCopy();
- if (getContext().isMessageHistory()) {
-
exchange.internalProperties.computeIfPresent(ExchangePropertyKey.MESSAGE_HISTORY,
- (k, v) -> new
CopyOnWriteArrayList<>((List<MessageHistory>) v));
+ if (exchange.internalProperties != null) {
+ if (getContext().isMessageHistory()) {
+
exchange.internalProperties.computeIfPresent(ExchangePropertyKey.MESSAGE_HISTORY,
+ (k, v) -> new
CopyOnWriteArrayList<>((List<MessageHistory>) v));
+ }
+ // the claim check repository is scoped per exchange, so the copy
must not share it
+
exchange.internalProperties.computeIfPresent(ExchangePropertyKey.CLAIM_CHECK_REPOSITORY,
+ (k, v) -> v instanceof SafeCopyProperty scp ?
scp.safeCopy() : v);
}
- // the claim check repository is scoped per exchange, so the copy must
not share it
-
exchange.internalProperties.computeIfPresent(ExchangePropertyKey.CLAIM_CHECK_REPOSITORY,
- (k, v) -> v instanceof SafeCopyProperty scp ? scp.safeCopy() :
v);
return exchange;
}
@@ -189,7 +192,10 @@ abstract class AbstractExchange implements Exchange,
ExchangeExtension {
@Override
public Object getProperty(ExchangePropertyKey key) {
- return internalProperties.get(key);
+ if (key == ExchangePropertyKey.TO_ENDPOINT) {
+ return toEndpoint;
+ }
+ return internalProperties != null ? internalProperties.get(key) : null;
}
@Override
@@ -251,12 +257,24 @@ abstract class AbstractExchange implements Exchange,
ExchangeExtension {
@Override
public void setProperty(ExchangePropertyKey key, Object value) {
+ if (key == ExchangePropertyKey.TO_ENDPOINT) {
+ toEndpoint = value;
+ return;
+ }
+ if (internalProperties == null) {
+ internalProperties = new EnumMap<>(ExchangePropertyKey.class);
+ }
internalProperties.put(key, value);
}
@Override
public Object removeProperty(ExchangePropertyKey key) {
- return internalProperties.remove(key);
+ if (key == ExchangePropertyKey.TO_ENDPOINT) {
+ Object answer = toEndpoint;
+ toEndpoint = null;
+ return answer;
+ }
+ return internalProperties != null ? internalProperties.remove(key) :
null;
}
@Override
@@ -264,7 +282,7 @@ abstract class AbstractExchange implements Exchange,
ExchangeExtension {
Object answer = null;
ExchangePropertyKey key =
ExchangePropertyKey.asExchangePropertyKey(name);
if (key != null) {
- answer = internalProperties.get(key);
+ answer = getProperty(key);
// if the property is not an internal then fallback to lookup in
the properties map
}
if (answer == null && properties != null) {
@@ -326,7 +344,7 @@ abstract class AbstractExchange implements Exchange,
ExchangeExtension {
if (properties != null) {
properties.clear();
}
- internalProperties.clear();
+ clearInternalProperties();
return true;
}
@@ -338,7 +356,7 @@ abstract class AbstractExchange implements Exchange,
ExchangeExtension {
continue;
}
matches = true;
- internalProperties.remove(epk);
+ removeProperty(epk);
}
}
@@ -1012,7 +1030,18 @@ abstract class AbstractExchange implements Exchange,
ExchangeExtension {
@Override
public void copyInternalProperties(Exchange target) {
- ((AbstractExchange)
target).internalProperties.putAll(internalProperties);
+ AbstractExchange exchange = (AbstractExchange) target;
+ // null means the source has no value, so the target keeps its own, as
for a key that is not in the map
+ if (toEndpoint != null) {
+ exchange.toEndpoint = toEndpoint;
+ }
+ if (internalProperties != null) {
+ if (exchange.internalProperties == null) {
+ exchange.internalProperties = new
EnumMap<>(internalProperties);
+ } else {
+ exchange.internalProperties.putAll(internalProperties);
+ }
+ }
}
@Override
@@ -1029,7 +1058,7 @@ abstract class AbstractExchange implements Exchange,
ExchangeExtension {
public Map<String, Object> getInternalProperties() {
Map<String, Object> map = new HashMap<>();
for (ExchangePropertyKey key : ExchangePropertyKey.values()) {
- Object value = internalProperties.get(key);
+ Object value = getProperty(key);
if (value != null) {
map.put(key.getName(), value);
}
@@ -1040,10 +1069,18 @@ abstract class AbstractExchange implements Exchange,
ExchangeExtension {
@Override
public Exchange createCopyWithProperties(CamelContext context) {
DefaultExchange answer = new DefaultExchange(context,
internalProperties, properties);
+ answer.setProperty(ExchangePropertyKey.TO_ENDPOINT, toEndpoint);
answer.setPattern(pattern);
return answer;
}
+ void clearInternalProperties() {
+ toEndpoint = null;
+ if (internalProperties != null) {
+ internalProperties.clear();
+ }
+ }
+
public void resetExtension() {
if (unitOfWork != null) {
unitOfWork.reset();
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/DefaultPooledExchange.java
b/core/camel-support/src/main/java/org/apache/camel/support/DefaultPooledExchange.java
index 37e783558ae9..cfdda01fa15d 100644
---
a/core/camel-support/src/main/java/org/apache/camel/support/DefaultPooledExchange.java
+++
b/core/camel-support/src/main/java/org/apache/camel/support/DefaultPooledExchange.java
@@ -93,7 +93,7 @@ public final class DefaultPooledExchange extends
AbstractExchange implements Poo
this.properties.clear();
}
}
- internalProperties.clear();
+ clearInternalProperties();
if (this.safeCopyProperties != null) {
this.safeCopyProperties.clear();
}
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/ExtendedExchangeExtension.java
b/core/camel-support/src/main/java/org/apache/camel/support/ExtendedExchangeExtension.java
index 16dad05f146b..0b3442289386 100644
---
a/core/camel-support/src/main/java/org/apache/camel/support/ExtendedExchangeExtension.java
+++
b/core/camel-support/src/main/java/org/apache/camel/support/ExtendedExchangeExtension.java
@@ -422,8 +422,6 @@ public class ExtendedExchangeExtension implements
ExchangeExtension {
@Override
public Exchange createCopyWithProperties(CamelContext context) {
- DefaultExchange answer = new DefaultExchange(context,
exchange.internalProperties, exchange.properties);
- answer.setPattern(exchange.pattern);
- return answer;
+ return exchange.createCopyWithProperties(context);
}
}