From 74c2ac4500418819ebf4e8d8d1ad40f3f71e732d Mon Sep 17 00:00:00 2001 From: Andrey G Date: Thu, 2 Jul 2026 01:32:52 +0200 Subject: [PATCH] Support object mapper --- .../com/aerospike/client/sdk/Cluster.java | 21 +++ .../com/aerospike/client/sdk/Session.java | 17 ++ .../client/sdk/SessionExtension.java | 42 +++++ .../client/sdk/SessionExtensionTest.java | 145 ++++++++++++++++++ .../aerospike/client/sdk/SuiteCluster.java | 1 + 5 files changed, 226 insertions(+) create mode 100644 client/src/main/java/com/aerospike/client/sdk/SessionExtension.java create mode 100644 client/src/test/java/com/aerospike/client/sdk/SessionExtensionTest.java diff --git a/client/src/main/java/com/aerospike/client/sdk/Cluster.java b/client/src/main/java/com/aerospike/client/sdk/Cluster.java index 48f883b0..cb0193af 100644 --- a/client/src/main/java/com/aerospike/client/sdk/Cluster.java +++ b/client/src/main/java/com/aerospike/client/sdk/Cluster.java @@ -182,6 +182,27 @@ public Session createSession(Behavior behavior) { return new Session(this, behavior); } + /** + * Creates a new session of a custom type using the provided {@link SessionExtension}. + * + *

The extension is responsible for constructing the session subtype. The return type + * is inferred from the extension's type parameter, providing compile-time safety.

+ * + *

Example:

+ *
{@code
+     * MappingSession session = cluster.createSession(behavior, mappingExtension);
+     * }
+ * + * @param the concrete session type + * @param behavior the behavior configuration for the session + * @param extension the session extension that creates the session subtype + * @return a new session instance of type {@code S} + * @see SessionExtension + */ + public S createSession(Behavior behavior, SessionExtension extension) { + return extension.create(this, behavior); + } + /** * Gets the current record mapping factory. * diff --git a/client/src/main/java/com/aerospike/client/sdk/Session.java b/client/src/main/java/com/aerospike/client/sdk/Session.java index c4cc4d21..05a5a5c7 100644 --- a/client/src/main/java/com/aerospike/client/sdk/Session.java +++ b/client/src/main/java/com/aerospike/client/sdk/Session.java @@ -141,6 +141,23 @@ public Cluster getCluster() { return cluster; } + /** + * Returns a new session backed by the same cluster but with a different behavior. + * + *

This is a convenience for {@code getCluster().createSession(behavior)}. + * The receiver is not modified.

+ * + *

Subclasses (e.g. session types created via {@link SessionExtension}) should + * override this method to return the correct subtype.

+ * + * @param behavior the behavior configuration for the new session + * @return a new {@code Session} with the specified behavior + * @see Cluster#createSession(Behavior) + */ + public Session sessionFor(Behavior behavior) { + return cluster.createSession(behavior); + } + /** * Remove records in specified namespace/set efficiently. This method is many orders of magnitude * faster than deleting records one at a time. diff --git a/client/src/main/java/com/aerospike/client/sdk/SessionExtension.java b/client/src/main/java/com/aerospike/client/sdk/SessionExtension.java new file mode 100644 index 00000000..a631c425 --- /dev/null +++ b/client/src/main/java/com/aerospike/client/sdk/SessionExtension.java @@ -0,0 +1,42 @@ +package com.aerospike.client.sdk; + +import com.aerospike.client.sdk.policy.Behavior; + +/** + * Factory interface for creating custom {@link Session} subtypes. + * + *

Implement this interface to define how a specific session subtype is constructed. + * The type parameter {@code S} ensures compile-time safety: the return type of + * {@link Cluster#createSession(Behavior, SessionExtension)} is inferred from the + * extension's {@code S}.

+ * + *

Example usage (from an extension module):

+ *
{@code
+ * public class MySessionExtension implements SessionExtension {
+ *     @Override
+ *     public MySession create(Cluster cluster, Behavior behavior) {
+ *         return new MySession(cluster, behavior);
+ *     }
+ * }
+ *
+ * MySession session = cluster.createSession(behavior, new MySessionExtension());
+ * }
+ * + * @param the concrete session type produced by this extension + * @see Cluster#createSession(Behavior, SessionExtension) + * @see Session + */ +public interface SessionExtension { + + /** + * Creates a new session of type {@code S} for the given cluster and behavior. + * + *

The implementation is responsible for constructing the session subtype, + * typically by calling {@link Session}'s protected constructor via a subclass.

+ * + * @param cluster the cluster this session will operate on + * @param behavior the behavior configuration for the session + * @return a new session instance of type {@code S} + */ + S create(Cluster cluster, Behavior behavior); +} diff --git a/client/src/test/java/com/aerospike/client/sdk/SessionExtensionTest.java b/client/src/test/java/com/aerospike/client/sdk/SessionExtensionTest.java new file mode 100644 index 00000000..0c680a4f --- /dev/null +++ b/client/src/test/java/com/aerospike/client/sdk/SessionExtensionTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2012-2026 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.sdk; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.aerospike.client.sdk.policy.Behavior; + +class SessionExtensionTest extends ClusterTest { + + static class CustomSession extends Session { + private final String label; + + protected CustomSession(Cluster cluster, Behavior behavior, String label) { + super(cluster, behavior); + this.label = label; + } + + String getLabel() { + return label; + } + } + + static class AnotherSession extends Session { + protected AnotherSession(Cluster cluster, Behavior behavior) { + super(cluster, behavior); + } + } + + static class CustomSessionExtension implements SessionExtension { + private final String label; + + CustomSessionExtension(String label) { + this.label = label; + } + + @Override + public CustomSession create(Cluster cluster, Behavior behavior) { + return new CustomSession(cluster, behavior, label); + } + } + + static class AnotherSessionExtension implements SessionExtension { + @Override + public AnotherSession create(Cluster cluster, Behavior behavior) { + return new AnotherSession(cluster, behavior); + } + } + + @Test + @DisplayName("createSession with extension returns the correct subtype") + void createSessionWithExtensionReturnsSubtype() { + SessionExtension extension = new CustomSessionExtension("test-label"); + + CustomSession custom = cluster.createSession(Behavior.DEFAULT, extension); + + assertInstanceOf(CustomSession.class, custom); + assertEquals("test-label", custom.getLabel()); + assertSame(Behavior.DEFAULT, custom.getBehavior()); + assertSame(cluster, custom.getCluster()); + } + + @Test + @DisplayName("createSession with extension passes cluster and behavior to extension") + void createSessionPassesArguments() { + Behavior derived = Behavior.DEFAULT.deriveWithChanges( + "extensionTest", + b -> b.on(Behavior.Selectors.all(), ops -> ops.sendKey(true)) + ); + SessionExtension extension = new CustomSessionExtension("arg-check"); + + CustomSession custom = cluster.createSession(derived, extension); + + assertSame(cluster, custom.getCluster()); + assertSame(derived, custom.getBehavior()); + } + + @Test + @DisplayName("sessionFor returns a new Session with the specified behavior") + void sessionForReturnsNewSession() { + Behavior derived = Behavior.DEFAULT.deriveWithChanges( + "sessionForTest", + b -> b.on(Behavior.Selectors.all(), ops -> ops.sendKey(true)) + ); + + Session original = cluster.createSession(Behavior.DEFAULT); + Session newSession = original.sessionFor(derived); + + assertNotSame(original, newSession); + assertSame(derived, newSession.getBehavior()); + assertSame(cluster, newSession.getCluster()); + } + + @Test + @DisplayName("sessionFor preserves the cluster reference") + void sessionForPreservesCluster() { + Session original = cluster.createSession(Behavior.DEFAULT); + Session other = original.sessionFor(Behavior.DEFAULT); + + assertNotSame(original, other); + assertSame(original.getCluster(), other.getCluster()); + } + + @Test + @DisplayName("multiple extension types coexist on the same cluster") + void multipleExtensionsCoexist() { + CustomSession custom = cluster.createSession(Behavior.DEFAULT, new CustomSessionExtension("one")); + AnotherSession another = cluster.createSession(Behavior.DEFAULT, new AnotherSessionExtension()); + + assertInstanceOf(CustomSession.class, custom); + assertInstanceOf(AnotherSession.class, another); + assertSame(cluster, custom.getCluster()); + assertSame(cluster, another.getCluster()); + } + + @Test + @DisplayName("extended session is a fully functional Session") + void extendedSessionCanOperate() { + CustomSession custom = cluster.createSession(Behavior.DEFAULT, new CustomSessionExtension("functional")); + + assertNotNull(custom.info()); + assertEquals("functional", custom.getLabel()); + } +} diff --git a/client/src/test/java/com/aerospike/client/sdk/SuiteCluster.java b/client/src/test/java/com/aerospike/client/sdk/SuiteCluster.java index 205c319c..b593a59b 100644 --- a/client/src/test/java/com/aerospike/client/sdk/SuiteCluster.java +++ b/client/src/test/java/com/aerospike/client/sdk/SuiteCluster.java @@ -77,6 +77,7 @@ RecordStreamAdapterTest.class, ReplaceTest.class, ServerInfoTest.class, + SessionExtensionTest.class, TouchTest.class, TxnTest.class, UdfTest.class,