
com.github.kshashov.telegram.TelegramScope Maven / Gradle / Ivy
package com.github.kshashov.telegram;
import com.github.kshashov.telegram.api.TelegramSession;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.lang.NonNull;
import javax.validation.constraints.NotNull;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* Scope store all beans in the cache by chat id (or user id)
* The bean lifetime after the last call can be redefined using the property {@code
* TelegramConfigurationProperties.getSessionSeconds()}.
*
* Note: All {@link TelegramSession} instances have this scope by
* default.
*
* @see TelegramScopeException
*/
@Slf4j
public class TelegramScope implements Scope {
public static final String SCOPE = "telegramScope";
private static final ThreadLocal USER_THREAD_LOCAL = new ThreadLocal<>();
private final ConfigurableListableBeanFactory beanFactory;
private final LoadingCache> conversations;
@SuppressWarnings("unchecked")
TelegramScope(@NotNull ConfigurableListableBeanFactory beanFactory, long expireSeconds) {
this.beanFactory = beanFactory;
conversations = CacheBuilder
.newBuilder()
.expireAfterAccess(expireSeconds, TimeUnit.SECONDS)
.removalListener(notification -> {
if (notification.wasEvicted()) {
log.debug("Evict session for key {}", notification.getKey());
Map userScope = (Map) notification.getValue();
if (userScope != null) {
userScope.values().forEach(this::removeBean);
}
}
})
.build(new CacheLoader>() {
@Override
public ConcurrentHashMap load(@NonNull String key) {
log.debug("Create session for key = {}", key);
return new ConcurrentHashMap<>();
}
});
}
static void setIdThreadLocal(Long chatId) {
USER_THREAD_LOCAL.set(chatId);
}
static void removeId() {
USER_THREAD_LOCAL.remove();
}
private void removeBean(Object bean) {
beanFactory.destroyBean(bean);
}
@NonNull
@Override
public Object get(@NonNull String name, @NonNull ObjectFactory> objectFactory) throws TelegramScopeException {
final String sessionId = getConversationId();
if (sessionId == null) {
throw new TelegramScopeException("There is no current session");
}
ConcurrentHashMap beans;
try {
beans = conversations.get(sessionId);
} catch (ExecutionException e) {
throw new TelegramScopeException(e);
}
return beans.computeIfAbsent(name, (k) -> objectFactory.getObject());
}
@Override
public Object remove(@NonNull String name) {
final String sessionId = getConversationId();
if (sessionId != null) {
final Map userBeans = conversations.getIfPresent(sessionId);
if (userBeans != null) {
return userBeans.remove(name);
}
}
return null;
}
@Override
public void registerDestructionCallback(@NonNull String name, @NonNull Runnable callback) {
}
@Override
public Object resolveContextualObject(@NonNull String key) {
return null;
}
@Override
public String getConversationId() {
Long id = USER_THREAD_LOCAL.get();
if (id == null) {
return null;
}
return Long.toString(id);
}
}