This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.22.x by this push:
new 23db9a25ed31 CAMEL-24729: camel-infinispan - report an expiry that is
missing its time unit (backport camel-4.22.x) (#26490)
23db9a25ed31 is described below
commit 23db9a25ed3135c4dca4fe201172a134493ff875
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Sep 16 10:11:30 2026 +0200
CAMEL-24729: camel-infinispan - report an expiry that is missing its time
unit (backport camel-4.22.x) (#26490)
Backport to camel-4.22.x. camel-infinispan now reports an expiry that is
missing its time unit instead of
silently proceeding. Cherry-picked from #26427 (main), adapted to the
4.22.x imports.
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
.../component/infinispan/InfinispanProducer.java | 36 +++++-
.../InfinispanEmbeddedProducerExpiryTest.java | 131 +++++++++++++++++++++
2 files changed, 163 insertions(+), 4 deletions(-)
diff --git
a/components/camel-infinispan/camel-infinispan-common/src/main/java/org/apache/camel/component/infinispan/InfinispanProducer.java
b/components/camel-infinispan/camel-infinispan-common/src/main/java/org/apache/camel/component/infinispan/InfinispanProducer.java
index 1dd15ecc741e..bf6a93d41166 100644
---
a/components/camel-infinispan/camel-infinispan-common/src/main/java/org/apache/camel/component/infinispan/InfinispanProducer.java
+++
b/components/camel-infinispan/camel-infinispan-common/src/main/java/org/apache/camel/component/infinispan/InfinispanProducer.java
@@ -19,16 +19,24 @@ package org.apache.camel.component.infinispan;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.camel.Message;
import org.apache.camel.spi.InvokeOnHeader;
import org.apache.camel.support.HeaderSelectorProducer;
import org.apache.camel.util.ObjectHelper;
import org.infinispan.commons.api.BasicCache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public abstract class InfinispanProducer<M extends InfinispanManager, C
extends InfinispanConfiguration>
extends HeaderSelectorProducer {
+ private static final Logger LOG =
LoggerFactory.getLogger(InfinispanProducer.class);
+
+ private final AtomicBoolean lifespanReported = new AtomicBoolean();
+ private final AtomicBoolean maxIdleTimeReported = new AtomicBoolean();
+
private final String cacheName;
private final C configuration;
private final M manager;
@@ -413,13 +421,33 @@ public abstract class InfinispanProducer<M extends
InfinispanManager, C extends
}
protected boolean hasLifespan(Message message) {
- return !InfinispanUtil.isHeaderEmpty(message,
InfinispanConstants.LIFESPAN_TIME)
- && !InfinispanUtil.isHeaderEmpty(message,
InfinispanConstants.LIFESPAN_TIME_UNIT);
+ return hasExpiry(message, InfinispanConstants.LIFESPAN_TIME,
InfinispanConstants.LIFESPAN_TIME_UNIT,
+ lifespanReported);
}
protected boolean hasMaxIdleTime(Message message) {
- return !InfinispanUtil.isHeaderEmpty(message,
InfinispanConstants.MAX_IDLE_TIME)
- && !InfinispanUtil.isHeaderEmpty(message,
InfinispanConstants.MAX_IDLE_TIME_UNIT);
+ return hasExpiry(message, InfinispanConstants.MAX_IDLE_TIME,
InfinispanConstants.MAX_IDLE_TIME_UNIT,
+ maxIdleTimeReported);
+ }
+
+ /**
+ * An expiry needs both an amount and the time unit it is expressed in.
When only one of the two is on the message
+ * the expiry cannot be applied, and the entry is stored without it, so
report that instead of dropping it quietly.
+ * <p>
+ * A route that gets this wrong gets it wrong for every message it sends,
so each pair is reported once per producer
+ * rather than on every exchange.
+ */
+ private boolean hasExpiry(Message message, String timeHeader, String
timeUnitHeader, AtomicBoolean reported) {
+ boolean hasTime = !InfinispanUtil.isHeaderEmpty(message, timeHeader);
+ boolean hasTimeUnit = !InfinispanUtil.isHeaderEmpty(message,
timeUnitHeader);
+
+ if (hasTime != hasTimeUnit && reported.compareAndSet(false, true)) {
+ LOG.warn("Both {} and {} are needed to set an expiry on cache {},
but only {} is set on the message,"
+ + " so the entry is stored without one. Reported once per
producer.",
+ timeHeader, timeUnitHeader, getCacheName(), hasTime ?
timeHeader : timeUnitHeader);
+ }
+
+ return hasTime && hasTimeUnit;
}
protected void setResult(Message message, Object result) {
diff --git
a/components/camel-infinispan/camel-infinispan-embedded/src/test/java/org/apache/camel/component/infinispan/embedded/InfinispanEmbeddedProducerExpiryTest.java
b/components/camel-infinispan/camel-infinispan-embedded/src/test/java/org/apache/camel/component/infinispan/embedded/InfinispanEmbeddedProducerExpiryTest.java
new file mode 100644
index 000000000000..9d4ba49bc1c3
--- /dev/null
+++
b/components/camel-infinispan/camel-infinispan-embedded/src/test/java/org/apache/camel/component/infinispan/embedded/InfinispanEmbeddedProducerExpiryTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.infinispan.embedded;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.Message;
+import org.apache.camel.component.infinispan.InfinispanConstants;
+import org.apache.camel.component.infinispan.InfinispanEndpoint;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Verifies when the producer considers an expiry to be configured on a
message. An expiry needs both its amount and its
+ * time unit, and half of a pair used to be dropped without a word.
+ */
+class InfinispanEmbeddedProducerExpiryTest {
+
+ private DefaultCamelContext context;
+ private ExpiryProbe producer;
+
+ @BeforeEach
+ void setUp() {
+ context = new DefaultCamelContext();
+
+ InfinispanEmbeddedConfiguration configuration = new
InfinispanEmbeddedConfiguration();
+ InfinispanEmbeddedComponent component = new
InfinispanEmbeddedComponent(context);
+ InfinispanEmbeddedEndpoint endpoint
+ = new InfinispanEmbeddedEndpoint("infinispan-embedded:misc",
"misc", component, configuration);
+
+ // the manager is only needed to reach a cache, which these assertions
never do
+ producer = new ExpiryProbe(endpoint, configuration);
+ }
+
+ @AfterEach
+ void tearDown() {
+ if (context != null) {
+ context.stop();
+ }
+ }
+
+ private Message message() {
+ return new DefaultExchange(context).getMessage();
+ }
+
+ @Test
+ void noExpiryHeadersMeansNoExpiry() {
+ Message message = message();
+
+ assertThat(producer.lifespan(message)).isFalse();
+ assertThat(producer.maxIdleTime(message)).isFalse();
+ }
+
+ @Test
+ void anAmountWithItsTimeUnitIsAnExpiry() {
+ Message lifespan = message();
+ lifespan.setHeader(InfinispanConstants.LIFESPAN_TIME, 100L);
+ lifespan.setHeader(InfinispanConstants.LIFESPAN_TIME_UNIT,
TimeUnit.MILLISECONDS);
+ assertThat(producer.lifespan(lifespan)).isTrue();
+
+ Message maxIdle = message();
+ maxIdle.setHeader(InfinispanConstants.MAX_IDLE_TIME, 100L);
+ maxIdle.setHeader(InfinispanConstants.MAX_IDLE_TIME_UNIT,
TimeUnit.MILLISECONDS);
+ assertThat(producer.maxIdleTime(maxIdle)).isTrue();
+ }
+
+ @Test
+ void anAmountWithoutItsTimeUnitIsNotAnExpiry() {
+ // the entry is still stored, without an expiry - but the producer now
reports it at WARN
+ Message lifespan = message();
+ lifespan.setHeader(InfinispanConstants.LIFESPAN_TIME, 100L);
+ assertThat(producer.lifespan(lifespan)).isFalse();
+
+ Message maxIdle = message();
+ maxIdle.setHeader(InfinispanConstants.MAX_IDLE_TIME, 100L);
+ assertThat(producer.maxIdleTime(maxIdle)).isFalse();
+
+ // a route that gets this wrong gets it wrong for every message, so
the verdict must not change
+ // when the pair is reported only the first time
+ assertThat(producer.lifespan(lifespan)).isFalse();
+ assertThat(producer.maxIdleTime(maxIdle)).isFalse();
+ }
+
+ @Test
+ void aTimeUnitWithoutItsAmountIsNotAnExpiry() {
+ Message lifespan = message();
+ lifespan.setHeader(InfinispanConstants.LIFESPAN_TIME_UNIT,
TimeUnit.MILLISECONDS);
+ assertThat(producer.lifespan(lifespan)).isFalse();
+
+ Message maxIdle = message();
+ maxIdle.setHeader(InfinispanConstants.MAX_IDLE_TIME_UNIT,
TimeUnit.MILLISECONDS);
+ assertThat(producer.maxIdleTime(maxIdle)).isFalse();
+ }
+
+ /**
+ * Opens up the two protected decisions of the producer, which are
otherwise only reachable from a subclass.
+ */
+ private static final class ExpiryProbe extends InfinispanEmbeddedProducer {
+
+ private ExpiryProbe(InfinispanEndpoint endpoint,
InfinispanEmbeddedConfiguration configuration) {
+ super((InfinispanEmbeddedEndpoint) endpoint, "misc", null,
configuration);
+ }
+
+ private boolean lifespan(Message message) {
+ return hasLifespan(message);
+ }
+
+ private boolean maxIdleTime(Message message) {
+ return hasMaxIdleTime(message);
+ }
+ }
+}