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

gnodet 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 c01f4c648e30 CAMEL-24463: Improve KeyValueRepository SPI
c01f4c648e30 is described below

commit c01f4c648e30b38dd94e81ea10bb3334f117a110
Author: Guillaume Nodet <[email protected]>
AuthorDate: Wed Sep 2 06:48:29 2026 +0200

    CAMEL-24463: Improve KeyValueRepository SPI
    
    - Add put(key, value) and putIfAbsent(key, value) convenience defaults
      that delegate to their Duration-based variants with null TTL
    - Add KeyValueRepositoryHelper with shared serialization utilities
      (serialize/deserialize objects to byte arrays) for persistent backends
    - Update KeyValueAggregationRepository and KeyValueIdempotentRepository
      to use the new convenience methods
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../org/apache/camel/spi/KeyValueRepository.java   |  28 +++++
 .../support/KeyValueAggregationRepository.java     |   4 +-
 .../support/KeyValueIdempotentRepository.java      |   2 +-
 .../camel/support/KeyValueRepositoryHelper.java    | 125 +++++++++++++++++++++
 .../support/KeyValueIdempotentRepositoryTest.java  |   4 +-
 5 files changed, 158 insertions(+), 5 deletions(-)

diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/KeyValueRepository.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/KeyValueRepository.java
index fef877edda4c..7e92d11818ca 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/KeyValueRepository.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/KeyValueRepository.java
@@ -51,6 +51,20 @@ public interface KeyValueRepository extends Service {
     @Nullable
     Object get(String key);
 
+    /**
+     * Stores a value under the given key with no expiration.
+     * <p/>
+     * Equivalent to {@code put(key, value, null)}.
+     *
+     * @param  key   the key
+     * @param  value the value to store
+     * @return       the previous value associated with the key, or {@code 
null} if there was no mapping
+     */
+    @Nullable
+    default Object put(String key, Object value) {
+        return put(key, value, null);
+    }
+
     /**
      * Stores a value under the given key with an optional time-to-live.
      *
@@ -91,6 +105,20 @@ public interface KeyValueRepository extends Service {
      */
     void clear();
 
+    /**
+     * Stores the value under the given key only if no non-expired mapping 
already exists, with no expiration.
+     * <p/>
+     * Equivalent to {@code putIfAbsent(key, value, null)}.
+     *
+     * @param  key   the key
+     * @param  value the value to store
+     * @return       the existing value if the key was already present, or 
{@code null} if the put succeeded
+     */
+    @Nullable
+    default Object putIfAbsent(String key, Object value) {
+        return putIfAbsent(key, value, null);
+    }
+
     /**
      * Stores the value under the given key only if no non-expired mapping 
already exists.
      * <p/>
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/KeyValueAggregationRepository.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/KeyValueAggregationRepository.java
index 95fb22141b5b..38a4aaf24301 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/KeyValueAggregationRepository.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/KeyValueAggregationRepository.java
@@ -110,7 +110,7 @@ public class KeyValueAggregationRepository extends 
ServiceSupport
     public Exchange add(CamelContext camelContext, String key, Exchange 
exchange) {
         LOG.trace("Adding an Exchange with ID {} for key {}", 
exchange.getExchangeId(), key);
         DefaultExchangeHolder newHolder = 
DefaultExchangeHolder.marshal(exchange, true, allowSerializedHeaders);
-        DefaultExchangeHolder oldHolder = (DefaultExchangeHolder) 
repository.put(AGGREGATE_PREFIX + key, newHolder, null);
+        DefaultExchangeHolder oldHolder = (DefaultExchangeHolder) 
repository.put(AGGREGATE_PREFIX + key, newHolder);
         return unmarshallExchange(camelContext, oldHolder);
     }
 
@@ -126,7 +126,7 @@ public class KeyValueAggregationRepository extends 
ServiceSupport
         if (useRecovery && holder != null) {
             // Store under the exchangeId for potential recovery
             LOG.trace("Moving Exchange with ID {} to completed (pending 
confirmation)", exchange.getExchangeId());
-            repository.put(COMPLETED_PREFIX + exchange.getExchangeId(), 
holder, null);
+            repository.put(COMPLETED_PREFIX + exchange.getExchangeId(), 
holder);
         }
     }
 
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/KeyValueIdempotentRepository.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/KeyValueIdempotentRepository.java
index d11b656641e1..02d87434d725 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/KeyValueIdempotentRepository.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/KeyValueIdempotentRepository.java
@@ -84,7 +84,7 @@ public class KeyValueIdempotentRepository extends 
ServiceSupport implements Idem
     @Override
     public boolean add(String key) {
         // putIfAbsent returns null if the key was successfully added (not 
already present)
-        return repository.putIfAbsent(IDEMPOTENT_PREFIX + key, Boolean.TRUE, 
null) == null;
+        return repository.putIfAbsent(IDEMPOTENT_PREFIX + key, Boolean.TRUE) 
== null;
     }
 
     @Override
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/KeyValueRepositoryHelper.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/KeyValueRepositoryHelper.java
new file mode 100644
index 000000000000..13d3ff696456
--- /dev/null
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/KeyValueRepositoryHelper.java
@@ -0,0 +1,125 @@
+/*
+ * 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.support;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.nio.ByteBuffer;
+
+import org.apache.camel.RuntimeCamelException;
+
+/**
+ * Shared serialization utilities for {@link 
org.apache.camel.spi.KeyValueRepository} implementations.
+ * <p/>
+ * All persistent {@code KeyValueRepository} implementations need to serialize 
arbitrary Java objects to bytes (for BLOB
+ * columns, Kafka messages, etc.) and deserialize them back. This helper 
centralises that logic to avoid the same
+ * try/catch boilerplate in every implementation.
+ * <p/>
+ * <b>Security note:</b> These methods use plain Java serialization
+ * ({@link ObjectOutputStream}/{@link ObjectInputStream}). The stored data is 
trusted — it was written by the same
+ * application instance or cluster. Do not expose a repository's raw byte 
store to untrusted input.
+ *
+ * @since 4.23
+ */
+public final class KeyValueRepositoryHelper {
+
+    private KeyValueRepositoryHelper() {
+        // utility class
+    }
+
+    /**
+     * Serializes an object to a byte array using Java object serialization.
+     *
+     * @param  value                 the object to serialize (must be {@link 
java.io.Serializable})
+     * @return                       the serialized bytes
+     * @throws RuntimeCamelException if serialization fails
+     */
+    public static byte[] serialize(Object value) {
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
+             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
+            oos.writeObject(value);
+            oos.flush();
+            return bos.toByteArray();
+        } catch (IOException e) {
+            throw new RuntimeCamelException("Failed to serialize value", e);
+        }
+    }
+
+    /**
+     * Serializes an object to a {@link ByteBuffer} using Java object 
serialization. Useful for drivers that work with
+     * {@code ByteBuffer} (e.g. Cassandra).
+     *
+     * @param  value                 the object to serialize (must be {@link 
java.io.Serializable})
+     * @return                       a ByteBuffer wrapping the serialized bytes
+     * @throws RuntimeCamelException if serialization fails
+     */
+    public static ByteBuffer serializeToByteBuffer(Object value) {
+        return ByteBuffer.wrap(serialize(value));
+    }
+
+    /**
+     * Deserializes a byte array back into an object using Java object 
serialization.
+     *
+     * @param  bytes                 the bytes to deserialize
+     * @return                       the deserialized object
+     * @throws RuntimeCamelException if deserialization fails
+     */
+    public static Object deserialize(byte[] bytes) {
+        try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+             ObjectInputStream ois = new ObjectInputStream(bis)) {
+            return ois.readObject();
+        } catch (IOException | ClassNotFoundException e) {
+            throw new RuntimeCamelException("Failed to deserialize value", e);
+        }
+    }
+
+    /**
+     * Deserializes an object from a portion of a byte array using Java object 
serialization. Useful when the serialized
+     * data starts at an offset (e.g. after a protocol header).
+     *
+     * @param  bytes                 the byte array containing the serialized 
data
+     * @param  offset                the start offset within the array
+     * @param  length                the number of bytes to read
+     * @return                       the deserialized object
+     * @throws RuntimeCamelException if deserialization fails
+     */
+    public static Object deserialize(byte[] bytes, int offset, int length) {
+        try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes, 
offset, length);
+             ObjectInputStream ois = new ObjectInputStream(bis)) {
+            return ois.readObject();
+        } catch (IOException | ClassNotFoundException e) {
+            throw new RuntimeCamelException("Failed to deserialize value", e);
+        }
+    }
+
+    /**
+     * Deserializes an object from a {@link ByteBuffer} using Java object 
serialization. The buffer's remaining bytes
+     * are consumed.
+     *
+     * @param  buffer                the ByteBuffer containing the serialized 
bytes
+     * @return                       the deserialized object
+     * @throws RuntimeCamelException if deserialization fails
+     */
+    public static Object deserialize(ByteBuffer buffer) {
+        byte[] bytes = new byte[buffer.remaining()];
+        buffer.get(bytes);
+        return deserialize(bytes);
+    }
+}
diff --git 
a/core/camel-support/src/test/java/org/apache/camel/support/KeyValueIdempotentRepositoryTest.java
 
b/core/camel-support/src/test/java/org/apache/camel/support/KeyValueIdempotentRepositoryTest.java
index c1853e4c172a..3b78af19a580 100644
--- 
a/core/camel-support/src/test/java/org/apache/camel/support/KeyValueIdempotentRepositoryTest.java
+++ 
b/core/camel-support/src/test/java/org/apache/camel/support/KeyValueIdempotentRepositoryTest.java
@@ -145,7 +145,7 @@ class KeyValueIdempotentRepositoryTest {
     @Test
     void testClearDoesNotAffectOtherPrefixes() {
         // Simulate another adapter storing entries under a different prefix
-        kvRepository.put("aggregate:order-1", "exchange-holder", null);
+        kvRepository.put("aggregate:order-1", "exchange-holder");
 
         // Add idempotent entries and clear them
         idempotentRepository.add("msg-001");
@@ -166,7 +166,7 @@ class KeyValueIdempotentRepositoryTest {
         idempotentRepository.add("order-1");
 
         // A different adapter storing under its own prefix should not collide
-        kvRepository.put("aggregate:order-1", "exchange-data", null);
+        kvRepository.put("aggregate:order-1", "exchange-data");
 
         // The idempotent entry should still resolve correctly
         assertThat(idempotentRepository.contains("order-1")).isTrue();

Reply via email to