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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.reactivex.rxjava3.core.Single;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -35,8 +36,6 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* An in-memory implementation of {@link BaseSessionService} assuming {@link Session} objects are
Expand All @@ -50,9 +49,6 @@
* during retrieval operations ({@code getSession}, {@code createSession}).
*/
public final class InMemorySessionService implements BaseSessionService {

private static final Logger logger = LoggerFactory.getLogger(InMemorySessionService.class);

// Structure: appName -> userId -> sessionId -> Session
private final ConcurrentMap<String, ConcurrentMap<String, ConcurrentMap<String, Session>>>
sessions;
Expand Down Expand Up @@ -178,9 +174,7 @@ public Single<ListSessionsResponse> listSessions(String appName, String userId)

// Create copies with empty events and state for the response
List<Session> sessionCopies =
userSessionsMap.values().stream()
.map(this::copySessionMetadata)
.collect(toCollection(ArrayList::new));
prepareSessionsForListResponse(appName, userId, userSessionsMap.values());

return Single.just(ListSessionsResponse.builder().sessions(sessionCopies).build());
}
Expand Down Expand Up @@ -298,21 +292,6 @@ private Session copySession(Session original) {
.build();
}

/**
* Creates a copy of the session containing only metadata fields (ID, appName, userId, timestamp).
* State and Events are explicitly *not* copied.
*
* @param original The session whose metadata to copy.
* @return A new Session instance with only metadata fields populated.
*/
private Session copySessionMetadata(Session original) {
return Session.builder(original.id())
.appName(original.appName())
.userId(original.userId())
.lastUpdateTime(original.lastUpdateTime())
.build();
}

/**
* Merges the app-specific and user-specific state into the provided *mutable* session's state
* map.
Expand All @@ -338,4 +317,24 @@ private Session mergeWithGlobalState(String appName, String userId, Session sess

return session;
}

/**
* Prepares copies of sessions for use in a {@code listSessions} response.
*
* <p>For each session provided, this method creates a deep copy, clears the copy's event list,
* and merges app-level and user-level state into the copy's state map.
*
* @param appName The application name.
* @param userId The user ID.
* @param sessions The collection of sessions to process.
* @return A list of processed {@link Session} copies.
*/
private List<Session> prepareSessionsForListResponse(
String appName, String userId, Collection<Session> sessions) {
return sessions.stream()
.map(this::copySession)
.peek(s -> s.events().clear())
.map(s -> mergeWithGlobalState(appName, userId, s))
.collect(toCollection(ArrayList::new));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,31 @@ public void lifecycle_getSession() {
public void lifecycle_listSessions() {
InMemorySessionService sessionService = new InMemorySessionService();

Session session = sessionService.createSession("app-name", "user-id").blockingGet();
Session session =
sessionService
.createSession("app-name", "user-id", new ConcurrentHashMap<>(), "session-1")
.blockingGet();

ConcurrentMap<String, Object> stateDelta = new ConcurrentHashMap<>();
stateDelta.put("sessionKey", "sessionValue");
stateDelta.put("_app_appKey", "appValue");
stateDelta.put("_user_userKey", "userValue");

Event event =
Event.builder().actions(EventActions.builder().stateDelta(stateDelta).build()).build();

var unused = sessionService.appendEvent(session, event).blockingGet();

ListSessionsResponse response =
sessionService.listSessions(session.appName(), session.userId()).blockingGet();
Session listedSession = response.sessions().get(0);

assertThat(response.sessions()).hasSize(1);
assertThat(response.sessions().get(0).id()).isEqualTo(session.id());
assertThat(listedSession.id()).isEqualTo(session.id());
assertThat(listedSession.events()).isEmpty();
assertThat(listedSession.state()).containsEntry("sessionKey", "sessionValue");
assertThat(listedSession.state()).containsEntry("_app_appKey", "appValue");
assertThat(listedSession.state()).containsEntry("_user_userKey", "userValue");
}

@Test
Expand Down