All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.redisson.micronaut.session.RedissonSessionStore Maven / Gradle / Ivy
/**
* Copyright (c) 2013-2024 Nikita Koksharov
*
* 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 org.redisson.micronaut.session;
import io.micronaut.context.annotation.Primary;
import io.micronaut.context.annotation.Replaces;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.event.ApplicationEventPublisher;
import io.micronaut.core.util.StringUtils;
import io.micronaut.session.InMemorySessionStore;
import io.micronaut.session.SessionIdGenerator;
import io.micronaut.session.SessionSettings;
import io.micronaut.session.SessionStore;
import io.micronaut.session.event.SessionCreatedEvent;
import io.micronaut.session.event.SessionDeletedEvent;
import io.micronaut.session.event.SessionExpiredEvent;
import jakarta.inject.Singleton;
import org.redisson.Redisson;
import org.redisson.api.*;
import org.redisson.api.listener.MessageListener;
import org.redisson.api.listener.PatternMessageListener;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.IntegerCodec;
import org.redisson.client.codec.StringCodec;
import org.redisson.codec.CompositeCodec;
import org.redisson.pubsub.PublishSubscribeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
*
* @author Nikita Koksharov
*
*/
@Singleton
@Primary
@Requires(property = RedissonSessionStore.ENABLED, value = StringUtils.TRUE)
@Replaces(InMemorySessionStore.class)
public class RedissonSessionStore implements SessionStore, PatternMessageListener, MessageListener {
public static final String ENABLED = SessionSettings.HTTP + ".redisson.enabled";
private static final String SESSION_PREFIX = "redisson:session:";
private MessageListener messageListener;
private final String nodeId = UUID.randomUUID().toString();
private static final Logger LOG = LoggerFactory.getLogger(RedissonSessionStore.class);
private RPatternTopic deletedTopic;
private RPatternTopic expiredTopic;
private RTopic createdTopic;
private RedissonClient redisson;
private final SessionIdGenerator sessionIdGenerator;
private RedissonHttpSessionConfiguration sessionConfiguration;
private final ApplicationEventPublisher eventPublisher;
public RedissonSessionStore(
RedissonClient redisson,
SessionIdGenerator sessionIdGenerator,
RedissonHttpSessionConfiguration sessionConfiguration,
ApplicationEventPublisher eventPublisher) {
this.sessionIdGenerator = sessionIdGenerator;
this.sessionConfiguration = sessionConfiguration;
this.eventPublisher = eventPublisher;
this.redisson = redisson;
deletedTopic = redisson.getPatternTopic("__keyevent@*:del", StringCodec.INSTANCE);
expiredTopic = redisson.getPatternTopic("__keyevent@*:expired", StringCodec.INSTANCE);
createdTopic = getTopic(getEventsChannelPrefix(), StringCodec.INSTANCE);
deletedTopic.addListener(String.class, this);
expiredTopic.addListener(String.class, this);
createdTopic.addListener(String.class, this);
if (sessionConfiguration.isBroadcastSessionUpdates()) {
RTopic updatesTopic = getTopic();
messageListener = new MessageListener() {
@Override
public void onMessage(CharSequence channel, AttributeMessage msg) {
if (msg.getNodeId().equals(nodeId)) {
return;
}
findSession(msg.getSessionId()).thenAccept(s -> {
if (s.isPresent()) {
return;
}
try {
RedissonSession session = s.get();
if (msg instanceof AttributeRemoveMessage) {
for (CharSequence name : ((AttributeRemoveMessage)msg).getNames()) {
session.superRemove(name);
}
}
if (msg instanceof AttributesClearMessage) {
deleteSession(session.getId());
}
if (msg instanceof AttributesPutAllMessage) {
AttributesPutAllMessage m = (AttributesPutAllMessage) msg;
Map attrs = m.getAttrs(getCodec().getMapValueDecoder());
session.load(attrs);
}
if (msg instanceof AttributeUpdateMessage) {
AttributeUpdateMessage m = (AttributeUpdateMessage)msg;
session.superPut(m.getName(), m.getValue(getCodec().getMapValueDecoder()));
}
} catch (Exception e) {
LOG.error("Unable to handle topic message", e);
}
});
}
};
updatesTopic.addListener(AttributeMessage.class, messageListener);
}
}
String getEventsChannelPrefix() {
return sessionConfiguration.getKeyPrefix() + "sessions:created:";
}
String getExpiredKeyPrefix() {
return sessionConfiguration.getKeyPrefix() + "sessions:expires:";
}
@Override
public RedissonSession newSession() {
return new RedissonSession(this, sessionIdGenerator.generateId(),
sessionConfiguration.getUpdateMode(), sessionConfiguration.getMaxInactiveInterval());
}
@Override
public CompletableFuture> findSession(String id) {
return loadSession(id, false);
}
@Override
public CompletableFuture deleteSession(String id) {
return loadSession(id, false).thenCompose(optional -> {
return optional.map(s -> {
return s.delete().thenApply(r -> {
return true;
});
}).orElse(CompletableFuture.completedFuture(false));
}).toCompletableFuture();
}
@Override
public CompletableFuture save(RedissonSession session) {
CompletableFuture f = session.save();
return f.thenCompose(v -> {
if (session.isNew()) {
return createdTopic.publishAsync(v.getId()).thenApply(val -> v);
}
return CompletableFuture.completedFuture(session);
});
}
@Override
public void onMessage(CharSequence pattern, CharSequence channel, String body) {
if (deletedTopic.getPatternNames().contains(pattern.toString())) {
if (!body.contains(SESSION_PREFIX +"notification:")) {
return;
}
String id = body.split(SESSION_PREFIX +"notification:")[1];
loadSession(id, true).whenComplete((r, e) -> {
r.ifPresent(v -> {
eventPublisher.publishEvent(new SessionDeletedEvent(v));
});
});
} else if (expiredTopic.getPatternNames().contains(pattern.toString())) {
if (!body.contains(SESSION_PREFIX +"notification:")) {
return;
}
String id = body.split(SESSION_PREFIX +"notification:")[1];
loadSession(id, true).whenComplete((r, e) -> {
r.ifPresent(v -> {
eventPublisher.publishEvent(new SessionExpiredEvent(v));
});
});
}
}
private CompletableFuture> loadSession(String id, boolean useExpired) {
RMap map = getMap(id);
return map.readAllMapAsync().thenApply(data -> {
if (data.isEmpty()) {
return Optional.empty();
}
RedissonSession session = new RedissonSession(this, id,
sessionConfiguration.getUpdateMode());
session.load(data);
if (useExpired || !session.isExpired()) {
return Optional.of(session);
}
return Optional.empty();
}).toCompletableFuture();
}
@Override
public void onMessage(CharSequence channel, String id) {
loadSession(id, true).whenComplete((r, e) -> {
r.ifPresent(v -> {
eventPublisher.publishEvent(new SessionCreatedEvent(v));
});
});
}
public RTopic getTopic() {
String keyPrefix = sessionConfiguration.getKeyPrefix();
String separator = keyPrefix == null || keyPrefix.isEmpty() ? "" : ":";
final String name = keyPrefix + separator + "redisson:session_updates";
return getTopic(name, getCodec());
}
private RTopic getTopic(String name, Codec codec) {
PublishSubscribeService ss = ((Redisson) redisson).getConnectionManager().getSubscribeService();
if (ss.isShardingSupported()) {
return redisson.getShardedTopic(name, codec);
}
return redisson.getTopic(name, codec);
}
public String getNodeId() {
return nodeId;
}
public RBatch createBatch() {
return redisson.createBatch();
}
private Codec getCodec() {
return Optional.ofNullable(sessionConfiguration.getCodec()).orElse(redisson.getConfig().getCodec());
}
public RMap getMap(String sessionId) {
String keyPrefix = sessionConfiguration.getKeyPrefix();
String separator = keyPrefix == null || keyPrefix.isEmpty() ? "" : ":";
String name = keyPrefix + separator + SESSION_PREFIX + sessionId;
return redisson.getMap(name, new CompositeCodec(StringCodec.INSTANCE, getCodec(), getCodec()));
}
public RBucket getNotificationBucket(String sessionId) {
String keyPrefix = sessionConfiguration.getKeyPrefix();
String separator = keyPrefix == null || keyPrefix.isEmpty() ? "" : ":";
String name = keyPrefix + separator + SESSION_PREFIX +"notification:" + sessionId;
return redisson.getBucket(name, IntegerCodec.INSTANCE);
}
}