Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions client/src/main/java/com/aerospike/client/sdk/Cluster.java
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,27 @@ public Session createSession(Behavior behavior) {
return new Session(this, behavior);
}

/**
* Creates a new session of a custom type using the provided {@link SessionExtension}.
*
* <p>The extension is responsible for constructing the session subtype. The return type
* is inferred from the extension's type parameter, providing compile-time safety.</p>
*
* <p>Example:</p>
* <pre>{@code
* MappingSession session = cluster.createSession(behavior, mappingExtension);
* }</pre>
*
* @param <S> 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 extends Session> S createSession(Behavior behavior, SessionExtension<S> extension) {
return extension.create(this, behavior);
}

/**
* Gets the current record mapping factory.
*
Expand Down
17 changes: 17 additions & 0 deletions client/src/main/java/com/aerospike/client/sdk/Session.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,23 @@ public Cluster getCluster() {
return cluster;
}

/**
* Returns a new session backed by the same cluster but with a different behavior.
*
* <p>This is a convenience for {@code getCluster().createSession(behavior)}.
* The receiver is not modified.</p>
*
* <p>Subclasses (e.g. session types created via {@link SessionExtension}) should
* override this method to return the correct subtype.</p>
*
* @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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.aerospike.client.sdk;

import com.aerospike.client.sdk.policy.Behavior;

/**
* Factory interface for creating custom {@link Session} subtypes.
*
* <p>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}.</p>
*
* <p>Example usage (from an extension module):</p>
* <pre>{@code
* public class MySessionExtension implements SessionExtension<MySession> {
* @Override
* public MySession create(Cluster cluster, Behavior behavior) {
* return new MySession(cluster, behavior);
* }
* }
*
* MySession session = cluster.createSession(behavior, new MySessionExtension());
* }</pre>
*
* @param <S> the concrete session type produced by this extension
* @see Cluster#createSession(Behavior, SessionExtension)
* @see Session
*/
public interface SessionExtension<S extends Session> {

/**
* Creates a new session of type {@code S} for the given cluster and behavior.
*
* <p>The implementation is responsible for constructing the session subtype,
* typically by calling {@link Session}'s protected constructor via a subclass.</p>
*
* @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);
}
Original file line number Diff line number Diff line change
@@ -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<CustomSession> {
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<AnotherSession> {
@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<CustomSession> 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<CustomSession> 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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
RecordStreamAdapterTest.class,
ReplaceTest.class,
ServerInfoTest.class,
SessionExtensionTest.class,
TouchTest.class,
TxnTest.class,
UdfTest.class,
Expand Down
Loading