Merge branch 'master' of github.com:redisson/redisson

pull/6181/head
Nikita Koksharov 5 months ago
commit 22926309c8

@ -34,7 +34,7 @@ Add `RedissonSessionManager` in global context - `tomcat/conf/context.xml` or pe
* In `readMode=MEMORY` all session attributes are always stored into Redis after the end of the request regardless of the `Session.setAttribute` method invocation. It is useful in case when some objects stored in session change their own state without `Session.setAttribute` method execution. Updated attributes are removed from all other Session instances if `broadcastSessionUpdates=true` and reloaded from Redis when these attributes are requested.
<br/>
`configPath` - path to Redisson YAML config. See [configuration wiki page](https://github.com/redisson/redisson/wiki/2.-Configuration) for more details.
`configPath` - path to Redisson YAML config. See [configuration wiki page](https://github.com/redisson/redisson/wiki/2.-Configuration) for more details. In case session manager is configured programatically, a config object can be passed using the `setConfig()` method
#### Shared Redisson instance

@ -52,6 +52,7 @@ public class RedissonSessionManager extends ManagerBase {
protected RedissonClient redisson;
private String configPath;
private Config config;
private ReadMode readMode = ReadMode.REDIS;
private UpdateMode updateMode = UpdateMode.DEFAULT;
@ -354,19 +355,20 @@ public class RedissonSessionManager extends ManagerBase {
}
protected RedissonClient buildClient() throws LifecycleException {
Config config = null;
try {
config = Config.fromYAML(new File(configPath), getClass().getClassLoader());
} catch (IOException e) {
// trying next format
if (config == null) {
try {
config = Config.fromJSON(new File(configPath), getClass().getClassLoader());
} catch (IOException e1) {
log.error("Can't parse json config " + configPath, e);
throw new LifecycleException("Can't parse yaml config " + configPath, e1);
config = Config.fromYAML(new File(configPath), getClass().getClassLoader());
} catch (IOException e) {
// trying next format
try {
config = Config.fromJSON(new File(configPath), getClass().getClassLoader());
} catch (IOException e1) {
log.error("Can't parse json config " + configPath, e);
throw new LifecycleException("Can't parse yaml config " + configPath, e1);
}
}
}
try {
return Redisson.create(config);
} catch (Exception e) {

@ -52,6 +52,7 @@ public class RedissonSessionManager extends ManagerBase {
protected RedissonClient redisson;
private String configPath;
private Config config;
private ReadMode readMode = ReadMode.REDIS;
private UpdateMode updateMode = UpdateMode.DEFAULT;
@ -354,19 +355,20 @@ public class RedissonSessionManager extends ManagerBase {
}
protected RedissonClient buildClient() throws LifecycleException {
Config config = null;
try {
config = Config.fromYAML(new File(configPath), getClass().getClassLoader());
} catch (IOException e) {
// trying next format
if (config == null) {
try {
config = Config.fromJSON(new File(configPath), getClass().getClassLoader());
} catch (IOException e1) {
log.error("Can't parse json config " + configPath, e);
throw new LifecycleException("Can't parse yaml config " + configPath, e1);
config = Config.fromYAML(new File(configPath), getClass().getClassLoader());
} catch (IOException e) {
// trying next format
try {
config = Config.fromJSON(new File(configPath), getClass().getClassLoader());
} catch (IOException e1) {
log.error("Can't parse json config " + configPath, e);
throw new LifecycleException("Can't parse yaml config " + configPath, e1);
}
}
}
try {
return Redisson.create(config);
} catch (Exception e) {

@ -52,6 +52,7 @@ public class RedissonSessionManager extends ManagerBase {
protected RedissonClient redisson;
private String configPath;
private Config config;
private ReadMode readMode = ReadMode.REDIS;
private UpdateMode updateMode = UpdateMode.DEFAULT;
@ -354,19 +355,20 @@ public class RedissonSessionManager extends ManagerBase {
}
protected RedissonClient buildClient() throws LifecycleException {
Config config = null;
try {
config = Config.fromYAML(new File(configPath), getClass().getClassLoader());
} catch (IOException e) {
// trying next format
if (config == null) {
try {
config = Config.fromJSON(new File(configPath), getClass().getClassLoader());
} catch (IOException e1) {
log.error("Can't parse json config " + configPath, e);
throw new LifecycleException("Can't parse yaml config " + configPath, e1);
config = Config.fromYAML(new File(configPath), getClass().getClassLoader());
} catch (IOException e) {
// trying next format
try {
config = Config.fromJSON(new File(configPath), getClass().getClassLoader());
} catch (IOException e1) {
log.error("Can't parse json config " + configPath, e);
throw new LifecycleException("Can't parse yaml config " + configPath, e1);
}
}
}
try {
return Redisson.create(config);
} catch (Exception e) {

@ -52,6 +52,7 @@ public class RedissonSessionManager extends ManagerBase {
protected RedissonClient redisson;
private String configPath;
private Config config;
private ReadMode readMode = ReadMode.REDIS;
private UpdateMode updateMode = UpdateMode.DEFAULT;
@ -108,6 +109,14 @@ public class RedissonSessionManager extends ManagerBase {
return configPath;
}
public void setConfig(Config config) {
this.config = config;
}
public Config getConfig() {
return config;
}
public String getKeyPrefix() {
return keyPrefix;
}
@ -354,19 +363,20 @@ public class RedissonSessionManager extends ManagerBase {
}
protected RedissonClient buildClient() throws LifecycleException {
Config config = null;
try {
config = Config.fromYAML(new File(configPath), getClass().getClassLoader());
} catch (IOException e) {
// trying next format
if (config == null) {
try {
config = Config.fromJSON(new File(configPath), getClass().getClassLoader());
} catch (IOException e1) {
log.error("Can't parse json config " + configPath, e);
throw new LifecycleException("Can't parse yaml config " + configPath, e1);
config = Config.fromYAML(new File(configPath), getClass().getClassLoader());
} catch (IOException e) {
// trying next format
try {
config = Config.fromJSON(new File(configPath), getClass().getClassLoader());
} catch (IOException e1) {
log.error("Can't parse json config " + configPath, e);
throw new LifecycleException("Can't parse yaml config " + configPath, e1);
}
}
}
try {
return Redisson.create(config);
} catch (Exception e) {

@ -1,9 +1,11 @@
package org.redisson.tomcat;
import org.apache.catalina.Manager;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@ -18,6 +20,9 @@ import java.nio.file.Paths;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import static org.redisson.tomcat.RedissonSessionManager.ReadMode.MEMORY;
import static org.redisson.tomcat.RedissonSessionManager.UpdateMode.AFTER_REQUEST;
public class RedissonSessionManagerTest {
public static Iterable<Object[]> data() {
@ -35,6 +40,48 @@ public class RedissonSessionManagerTest {
Files.copy(Paths.get(basePath + contextName), Paths.get(basePath + "context.xml"));
}
private Manager prepareManager() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonSessionManager redissonSessionManager = new RedissonSessionManager();
redissonSessionManager.setReadMode(MEMORY.name());
redissonSessionManager.setUpdateMode(AFTER_REQUEST.name());
redissonSessionManager.setBroadcastSessionEvents(true);
redissonSessionManager.setBroadcastSessionUpdates(true);
redissonSessionManager.setConfig(config);
return redissonSessionManager;
}
@Test
public void testProgrammaticManagerConfigurationUpdateTwoServers_readValue() throws Exception {
prepare("context_empty.xml");
TomcatServer server1 = new TomcatServer("myapp", 8080, "src/test/");
server1.setManager(prepareManager());
TomcatServer server2 = new TomcatServer("myapp", 8081, "src/test/");
server2.setManager(prepareManager());
try {
server1.start();
server2.start();
Executor executor = Executor.newInstance();
BasicCookieStore cookieStore = new BasicCookieStore();
executor.use(cookieStore);
write(8080, executor, "test", "from_server1");
write(8081, executor, "test", "from_server2");
read(8080, executor, "test", "from_server2");
read(8081, executor, "test", "from_server2");
} finally {
Executor.closeIdleConnections();
server1.stop();
server2.stop();
}
}
@ParameterizedTest
@MethodSource("data")
public void testUpdateTwoServers_readValue(String contextName) throws Exception {

@ -4,7 +4,9 @@ import java.net.MalformedURLException;
import javax.servlet.ServletException;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Manager;
import org.apache.catalina.startup.Tomcat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -12,6 +14,7 @@ import org.slf4j.LoggerFactory;
public class TomcatServer {
private Tomcat tomcat = new Tomcat();
private Context context;
private int port;
private boolean isRunning;
@ -30,7 +33,14 @@ public class TomcatServer {
tomcat.setPort(port);
tomcat.getHost().setAppBase(".");
tomcat.addWebapp(contextPath, appBase + "/webapp");
context = tomcat.addWebapp(contextPath, appBase + "/webapp");
}
void setManager(Manager manager) {
if (context.getManager() != null) {
throw new IllegalArgumentException("Manager already configured");
}
context.setManager(manager);
}
/**

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<Context>
</Context>

@ -128,6 +128,35 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
Collections.singletonList(optionsName), maxSize, mode);
}
@Override
public V computeIfAbsent(K key, Duration ttl, Function<? super K, ? extends V> mappingFunction) {
checkNotBatch();
checkKey(key);
Objects.requireNonNull(mappingFunction);
V value = get(key);
if (value != null) {
return value;
}
RLock lock = getLock(key);
lock.lock();
try {
value = get(key);
if (value == null) {
V newValue = mappingFunction.apply(key);
if (newValue != null) {
fastPut(key, newValue, ttl.toMillis(), TimeUnit.MILLISECONDS);
return newValue;
}
return null;
}
return value;
} finally {
lock.unlock();
}
}
@Override
public void setMaxSize(int maxSize) {
get(setMaxSizeAsync(maxSize));

@ -23,6 +23,7 @@ import java.time.Duration;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* <p>Map-based cache with ability to set TTL for each entry via
@ -81,7 +82,20 @@ public interface RMapCache<K, V> extends RMap<K, V>, RMapCacheAsync<K, V> {
* @return <code>true</code> if max size has been successfully set, otherwise <code>false</code>.
*/
boolean trySetMaxSize(int maxSize, EvictionMode mode);
/**
* If the specified key is not already associated
* with a value, attempts to compute its value using the given mapping function and enters it into this map .
* <p>
* Stores value mapped by key with specified time to live.
* Entry expires after specified time to live.
*
* @param key - map key
* @param ttl - time to live for key\value entry.
* If <code>0</code> then stores infinitely.
* @param mappingFunction the mapping function to compute a value
* @return current associated value
*/
V computeIfAbsent(K key, Duration ttl, Function<? super K, ? extends V> mappingFunction);
/**
* If the specified key is not already associated
* with a value, associate it with the given value.

@ -24,6 +24,7 @@ import java.time.Duration;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* <p>Map-based cache with ability to set TTL for each entry via
@ -126,7 +127,20 @@ public interface RMapCacheReactive<K, V> extends RMapReactive<K, V>, RDestroyabl
* @return previous associated value
*/
Mono<V> putIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit);
/**
* If the specified key is not already associated
* with a value, attempts to compute its value using the given mapping function and enters it into this map .
* <p>
* Stores value mapped by key with specified time to live.
* Entry expires after specified time to live.
*
* @param key - map key
* @param ttl - time to live for key\value entry.
* If <code>0</code> then stores infinitely.
* @param mappingFunction the mapping function to compute a value
* @return current associated value
*/
Mono<V> computeIfAbsentAsync(K key, Duration ttl, Function<? super K, ? extends V> mappingFunction);
/**
* Stores value mapped by key with specified time to live.
* Entry expires after specified time to live.

@ -26,6 +26,7 @@ import java.time.Duration;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* <p>Map-based cache with ability to set TTL for each entry via
@ -128,7 +129,20 @@ public interface RMapCacheRx<K, V> extends RMapRx<K, V>, RDestroyable {
* @return previous associated value
*/
Maybe<V> putIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit);
/**
* If the specified key is not already associated
* with a value, attempts to compute its value using the given mapping function and enters it into this map .
* <p>
* Stores value mapped by key with specified time to live.
* Entry expires after specified time to live.
*
* @param key - map key
* @param ttl - time to live for key\value entry.
* If <code>0</code> then stores infinitely.
* @param mappingFunction the mapping function to compute a value
* @return current associated value
*/
Maybe<V> computeIfAbsentAsync(K key, Duration ttl, Function<? super K, ? extends V> mappingFunction);
/**
* Stores value mapped by key with specified time to live.
* Entry expires after specified time to live.

@ -1470,5 +1470,21 @@ public class RedissonMapCacheTest extends BaseMapTest {
map.destroy();
}
@Test
public void testComputeIfAbsentWithTTL() throws Exception{
RMapCache<String, String> map = redisson.getMapCache("testMap");
map.delete();
Duration duration = Duration.ofSeconds(1);
String value = map.computeIfAbsent("key1",duration, (t1) -> "value1");
assertThat("value1".equals(value)).isTrue();
value = map.computeIfAbsent("key1", duration, (t1) -> "value2");
assertThat("value2".equals(value)).isFalse();
Thread.sleep(1100);
value = map.computeIfAbsent("key1", duration, (t1) -> "value3");
assertThat("value3".equals(value)).isTrue();
map.destroy();
}
}

Loading…
Cancel
Save