Feature - Tomcat 10 support added. #3322
parent
b8ca7287d1
commit
35fce14906
@ -0,0 +1,88 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-tomcat</artifactId>
|
||||
<version>3.14.2-SNAPSHOT</version>
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>redisson-tomcat-10</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Redisson/Tomcat-10</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-core</artifactId>
|
||||
<version>10.0.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-jasper</artifactId>
|
||||
<version>10.0.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-jasper</artifactId>
|
||||
<version>10.0.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Build-Time>${maven.build.timestamp}</Build-Time>
|
||||
<Automatic-Module-Name>redisson.tomcat9</Automatic-Module-Name>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.mycila</groupId>
|
||||
<artifactId>license-maven-plugin</artifactId>
|
||||
<version>3.0</version>
|
||||
<configuration>
|
||||
<basedir>${basedir}</basedir>
|
||||
<header>${basedir}/../../header.txt</header>
|
||||
<quiet>false</quiet>
|
||||
<failIfMissing>true</failIfMissing>
|
||||
<aggregate>false</aggregate>
|
||||
<includes>
|
||||
<include>src/main/java/org/redisson/</include>
|
||||
</includes>
|
||||
<excludes>
|
||||
<exclude>target/**</exclude>
|
||||
</excludes>
|
||||
<useDefaultExcludes>true</useDefaultExcludes>
|
||||
<mapping>
|
||||
<java>JAVADOC_STYLE</java>
|
||||
</mapping>
|
||||
<strictCheck>true</strictCheck>
|
||||
<useDefaultMapping>true</useDefaultMapping>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2020 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.tomcat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.redisson.client.protocol.Decoder;
|
||||
import org.redisson.client.protocol.Encoder;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class AttributeMessage implements Serializable {
|
||||
|
||||
private String sessionId;
|
||||
|
||||
private String nodeId;
|
||||
|
||||
public AttributeMessage() {
|
||||
}
|
||||
|
||||
public AttributeMessage(String nodeId, String sessionId) {
|
||||
this.nodeId = nodeId;
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public String getNodeId() {
|
||||
return nodeId;
|
||||
}
|
||||
|
||||
protected byte[] toByteArray(Encoder encoder, Object value) throws IOException {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ByteBuf buf = encoder.encode(value);
|
||||
try {
|
||||
return ByteBufUtil.getBytes(buf);
|
||||
} finally {
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
|
||||
protected Object toObject(Decoder<?> decoder, byte[] value) throws IOException, ClassNotFoundException {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(value);
|
||||
try {
|
||||
return decoder.decode(buf, null);
|
||||
} finally {
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2020 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.tomcat;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class AttributeRemoveMessage extends AttributeMessage {
|
||||
|
||||
private Set<String> names;
|
||||
|
||||
public AttributeRemoveMessage() {
|
||||
super();
|
||||
}
|
||||
|
||||
public AttributeRemoveMessage(String nodeId, String sessionId, Set<String> names) {
|
||||
super(nodeId, sessionId);
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
public Set<String> getNames() {
|
||||
return names;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2020 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.tomcat;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.redisson.client.protocol.Decoder;
|
||||
import org.redisson.client.protocol.Encoder;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class AttributeUpdateMessage extends AttributeMessage {
|
||||
|
||||
private String name;
|
||||
private byte[] value;
|
||||
|
||||
public AttributeUpdateMessage() {
|
||||
}
|
||||
|
||||
public AttributeUpdateMessage(String nodeId, String sessionId, String name, Object value, Encoder encoder) throws IOException {
|
||||
super(nodeId, sessionId);
|
||||
this.name = name;
|
||||
this.value = toByteArray(encoder, value);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Object getValue(Decoder<?> decoder) throws IOException, ClassNotFoundException {
|
||||
return toObject(decoder, value);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2020 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.tomcat;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class AttributesClearMessage extends AttributeMessage {
|
||||
|
||||
public AttributesClearMessage() {
|
||||
}
|
||||
|
||||
public AttributesClearMessage(String nodeId, String sessionId) {
|
||||
super(nodeId, sessionId);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2020 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.tomcat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.redisson.client.protocol.Decoder;
|
||||
import org.redisson.client.protocol.Encoder;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class AttributesPutAllMessage extends AttributeMessage {
|
||||
|
||||
private Map<String, byte[]> attrs;
|
||||
|
||||
public AttributesPutAllMessage() {
|
||||
}
|
||||
|
||||
public AttributesPutAllMessage(String nodeId, String sessionId, Map<String, Object> attrs, Encoder encoder) throws IOException {
|
||||
super(nodeId, sessionId);
|
||||
if (attrs != null) {
|
||||
this.attrs = new HashMap<String, byte[]>();
|
||||
for (Entry<String, Object> entry: attrs.entrySet()) {
|
||||
this.attrs.put(entry.getKey(), toByteArray(encoder, entry.getValue()));
|
||||
}
|
||||
} else {
|
||||
this.attrs = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getAttrs(Decoder<?> decoder) throws IOException, ClassNotFoundException {
|
||||
if (attrs == null) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> result = new HashMap<String, Object>();
|
||||
for (Entry<String, byte[]> entry: attrs.entrySet()) {
|
||||
result.put(entry.getKey(), toObject(decoder, entry.getValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2020 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.tomcat;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.NamingException;
|
||||
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
/**
|
||||
* Redisson Session Manager for Apache Tomcat.
|
||||
* Uses Redisson instance located in JNDI.
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class JndiRedissonSessionManager extends RedissonSessionManager {
|
||||
|
||||
private String jndiName;
|
||||
|
||||
@Override
|
||||
public void setConfigPath(String configPath) {
|
||||
throw new IllegalArgumentException("configPath is unavaialble for JNDI based manager");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RedissonClient buildClient() throws LifecycleException {
|
||||
InitialContext context = null;
|
||||
try {
|
||||
context = new InitialContext();
|
||||
Context envCtx = (Context) context.lookup("java:comp/env");
|
||||
return (RedissonClient) envCtx.lookup(jndiName);
|
||||
} catch (NamingException e) {
|
||||
throw new LifecycleException("Unable to locate Redisson instance by name: " + jndiName, e);
|
||||
} finally {
|
||||
if (context != null) {
|
||||
try {
|
||||
context.close();
|
||||
} catch (NamingException e) {
|
||||
throw new LifecycleException("Unable to close JNDI context", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getJndiName() {
|
||||
return jndiName;
|
||||
}
|
||||
|
||||
public void setJndiName(String jndiName) {
|
||||
this.jndiName = jndiName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutdownRedisson() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,498 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2020 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.tomcat;
|
||||
|
||||
import org.apache.catalina.session.StandardSession;
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RSet;
|
||||
import org.redisson.api.RTopic;
|
||||
import org.redisson.tomcat.RedissonSessionManager.ReadMode;
|
||||
import org.redisson.tomcat.RedissonSessionManager.UpdateMode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.Principal;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Redisson Session object for Apache Tomcat
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class RedissonSession extends StandardSession {
|
||||
|
||||
private static final String IS_NEW_ATTR = "session:isNew";
|
||||
private static final String IS_VALID_ATTR = "session:isValid";
|
||||
private static final String THIS_ACCESSED_TIME_ATTR = "session:thisAccessedTime";
|
||||
private static final String MAX_INACTIVE_INTERVAL_ATTR = "session:maxInactiveInterval";
|
||||
private static final String LAST_ACCESSED_TIME_ATTR = "session:lastAccessedTime";
|
||||
private static final String CREATION_TIME_ATTR = "session:creationTime";
|
||||
private static final String IS_EXPIRATION_LOCKED = "session:isExpirationLocked";
|
||||
private static final String PRINCIPAL_ATTR = "session:principal";
|
||||
private static final String AUTHTYPE_ATTR = "session:authtype";
|
||||
|
||||
public static final Set<String> ATTRS = new HashSet<String>(Arrays.asList(
|
||||
IS_NEW_ATTR, IS_VALID_ATTR,
|
||||
THIS_ACCESSED_TIME_ATTR, MAX_INACTIVE_INTERVAL_ATTR,
|
||||
LAST_ACCESSED_TIME_ATTR, CREATION_TIME_ATTR, IS_EXPIRATION_LOCKED,
|
||||
PRINCIPAL_ATTR, AUTHTYPE_ATTR
|
||||
));
|
||||
|
||||
private boolean isExpirationLocked;
|
||||
private boolean loaded;
|
||||
private final RedissonSessionManager redissonManager;
|
||||
private final Map<String, Object> attrs;
|
||||
private RMap<String, Object> map;
|
||||
private final RTopic topic;
|
||||
private final ReadMode readMode;
|
||||
private final UpdateMode updateMode;
|
||||
|
||||
private final AtomicInteger usages = new AtomicInteger();
|
||||
private Map<String, Object> loadedAttributes = Collections.emptyMap();
|
||||
private Map<String, Object> updatedAttributes = Collections.emptyMap();
|
||||
private Set<String> removedAttributes = Collections.emptySet();
|
||||
|
||||
private final boolean broadcastSessionEvents;
|
||||
|
||||
public RedissonSession(RedissonSessionManager manager, ReadMode readMode, UpdateMode updateMode, boolean broadcastSessionEvents) {
|
||||
super(manager);
|
||||
this.redissonManager = manager;
|
||||
this.readMode = readMode;
|
||||
this.updateMode = updateMode;
|
||||
this.topic = redissonManager.getTopic();
|
||||
this.broadcastSessionEvents = broadcastSessionEvents;
|
||||
|
||||
if (updateMode == UpdateMode.AFTER_REQUEST) {
|
||||
removedAttributes = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
|
||||
}
|
||||
if (readMode == ReadMode.REDIS) {
|
||||
loadedAttributes = new ConcurrentHashMap<>();
|
||||
updatedAttributes = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
try {
|
||||
Field attr = StandardSession.class.getDeclaredField("attributes");
|
||||
attrs = (Map<String, Object>) attr.get(this);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = -2518607181636076487L;
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name) {
|
||||
if (readMode == ReadMode.REDIS) {
|
||||
if (!isValidInternal()) {
|
||||
throw new IllegalStateException(sm.getString("standardSession.getAttribute.ise"));
|
||||
}
|
||||
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (removedAttributes.contains(name)) {
|
||||
return super.getAttribute(name);
|
||||
}
|
||||
|
||||
Object value = loadedAttributes.get(name);
|
||||
if (value == null) {
|
||||
value = map.get(name);
|
||||
if (value != null) {
|
||||
loadedAttributes.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
} else {
|
||||
if (!loaded) {
|
||||
synchronized (this) {
|
||||
if (!loaded) {
|
||||
Map<String, Object> storedAttrs = map.readAllMap();
|
||||
|
||||
load(storedAttrs);
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getAttributeNames() {
|
||||
if (readMode == ReadMode.REDIS) {
|
||||
if (!isValidInternal()) {
|
||||
throw new IllegalStateException
|
||||
(sm.getString("standardSession.getAttributeNames.ise"));
|
||||
}
|
||||
|
||||
Set<String> attributeKeys = new HashSet<>();
|
||||
attributeKeys.addAll(map.readAllKeySet());
|
||||
attributeKeys.addAll(loadedAttributes.keySet());
|
||||
return Collections.enumeration(attributeKeys);
|
||||
}
|
||||
|
||||
return super.getAttributeNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getValueNames() {
|
||||
if (readMode == ReadMode.REDIS) {
|
||||
if (!isValidInternal()) {
|
||||
throw new IllegalStateException
|
||||
(sm.getString("standardSession.getAttributeNames.ise"));
|
||||
}
|
||||
Set<String> keys = map.readAllKeySet();
|
||||
return keys.toArray(new String[keys.size()]);
|
||||
}
|
||||
|
||||
return super.getValueNames();
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (map == null) {
|
||||
map = redissonManager.getMap(id);
|
||||
}
|
||||
|
||||
if (broadcastSessionEvents) {
|
||||
RSet<String> set = redissonManager.getNotifiedNodes(id);
|
||||
set.add(redissonManager.getNodeId());
|
||||
set.expire(60, TimeUnit.SECONDS);
|
||||
map.fastPut(IS_EXPIRATION_LOCKED, true);
|
||||
map.expire(60, TimeUnit.SECONDS);
|
||||
} else {
|
||||
map.delete();
|
||||
}
|
||||
if (readMode == ReadMode.MEMORY) {
|
||||
topic.publish(new AttributesClearMessage(redissonManager.getNodeId(), getId()));
|
||||
}
|
||||
map = null;
|
||||
loadedAttributes.clear();
|
||||
updatedAttributes.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreationTime(long time) {
|
||||
super.setCreationTime(time);
|
||||
|
||||
if (map != null) {
|
||||
Map<String, Object> newMap = new HashMap<String, Object>(3);
|
||||
newMap.put(CREATION_TIME_ATTR, creationTime);
|
||||
newMap.put(LAST_ACCESSED_TIME_ATTR, lastAccessedTime);
|
||||
newMap.put(THIS_ACCESSED_TIME_ATTR, thisAccessedTime);
|
||||
map.putAll(newMap);
|
||||
if (readMode == ReadMode.MEMORY) {
|
||||
topic.publish(createPutAllMessage(newMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void access() {
|
||||
super.access();
|
||||
|
||||
if (map != null) {
|
||||
fastPut(THIS_ACCESSED_TIME_ATTR, thisAccessedTime);
|
||||
expireSession();
|
||||
}
|
||||
}
|
||||
|
||||
protected void expireSession() {
|
||||
if (isExpirationLocked) {
|
||||
return;
|
||||
}
|
||||
if (maxInactiveInterval >= 0) {
|
||||
map.expire(maxInactiveInterval + 60, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
protected AttributesPutAllMessage createPutAllMessage(Map<String, Object> newMap) {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
for (Entry<String, Object> entry : newMap.entrySet()) {
|
||||
map.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
try {
|
||||
return new AttributesPutAllMessage(redissonManager.getNodeId(), getId(), map, this.map.getCodec().getMapValueEncoder());
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxInactiveInterval(int interval) {
|
||||
super.setMaxInactiveInterval(interval);
|
||||
|
||||
if (map != null) {
|
||||
fastPut(MAX_INACTIVE_INTERVAL_ATTR, maxInactiveInterval);
|
||||
expireSession();
|
||||
}
|
||||
}
|
||||
|
||||
private void fastPut(String name, Object value) {
|
||||
map.fastPut(name, value);
|
||||
if (readMode == ReadMode.MEMORY) {
|
||||
try {
|
||||
topic.publish(new AttributeUpdateMessage(redissonManager.getNodeId(), getId(), name, value, this.map.getCodec().getMapValueEncoder()));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrincipal(Principal principal) {
|
||||
super.setPrincipal(principal);
|
||||
|
||||
if (map != null) {
|
||||
fastPut(PRINCIPAL_ATTR, principal);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthType(String authType) {
|
||||
super.setAuthType(authType);
|
||||
|
||||
if (map != null && authType != null) {
|
||||
fastPut(AUTHTYPE_ATTR, authType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValid(boolean isValid) {
|
||||
super.setValid(isValid);
|
||||
|
||||
if (map != null) {
|
||||
if (!isValid && !map.isExists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
fastPut(IS_VALID_ATTR, isValid);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNew(boolean isNew) {
|
||||
super.setNew(isNew);
|
||||
|
||||
if (map != null) {
|
||||
fastPut(IS_NEW_ATTR, isNew);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAccess() {
|
||||
boolean oldValue = isNew;
|
||||
super.endAccess();
|
||||
|
||||
if (map != null) {
|
||||
if (isNew != oldValue) {
|
||||
fastPut(IS_NEW_ATTR, isNew);
|
||||
}
|
||||
|
||||
Map<String, Object> newMap = new HashMap<>(2);
|
||||
newMap.put(LAST_ACCESSED_TIME_ATTR, lastAccessedTime);
|
||||
newMap.put(THIS_ACCESSED_TIME_ATTR, thisAccessedTime);
|
||||
map.putAll(newMap);
|
||||
if (readMode == ReadMode.MEMORY) {
|
||||
topic.publish(createPutAllMessage(newMap));
|
||||
}
|
||||
expireSession();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void superSetAttribute(String name, Object value, boolean notify) {
|
||||
super.setAttribute(name, value, notify);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, Object value, boolean notify) {
|
||||
super.setAttribute(name, value, notify);
|
||||
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (updateMode == UpdateMode.DEFAULT && map != null) {
|
||||
fastPut(name, value);
|
||||
}
|
||||
if (readMode == ReadMode.REDIS) {
|
||||
loadedAttributes.put(name, value);
|
||||
updatedAttributes.put(name, value);
|
||||
}
|
||||
if (updateMode == UpdateMode.AFTER_REQUEST) {
|
||||
removedAttributes.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
public void superRemoveAttributeInternal(String name, boolean notify) {
|
||||
super.removeAttributeInternal(name, notify);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getIdleTimeInternal() {
|
||||
long idleTime = super.getIdleTimeInternal();
|
||||
if (map != null && readMode == ReadMode.REDIS) {
|
||||
if (idleTime >= getMaxInactiveInterval() * 1000) {
|
||||
load(map.getAll(RedissonSession.ATTRS));
|
||||
idleTime = super.getIdleTimeInternal();
|
||||
}
|
||||
}
|
||||
return idleTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeAttributeInternal(String name, boolean notify) {
|
||||
super.removeAttributeInternal(name, notify);
|
||||
|
||||
if (updateMode == UpdateMode.DEFAULT && map != null) {
|
||||
map.fastRemove(name);
|
||||
if (readMode == ReadMode.MEMORY) {
|
||||
topic.publish(new AttributeRemoveMessage(redissonManager.getNodeId(), getId(), new HashSet<String>(Arrays.asList(name))));
|
||||
}
|
||||
}
|
||||
if (readMode == ReadMode.REDIS) {
|
||||
loadedAttributes.remove(name);
|
||||
updatedAttributes.remove(name);
|
||||
}
|
||||
if (updateMode == UpdateMode.AFTER_REQUEST) {
|
||||
removedAttributes.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
public void save() {
|
||||
if (map == null) {
|
||||
map = redissonManager.getMap(id);
|
||||
}
|
||||
|
||||
Map<String, Object> newMap = new HashMap<String, Object>();
|
||||
newMap.put(CREATION_TIME_ATTR, creationTime);
|
||||
newMap.put(LAST_ACCESSED_TIME_ATTR, lastAccessedTime);
|
||||
newMap.put(THIS_ACCESSED_TIME_ATTR, thisAccessedTime);
|
||||
newMap.put(MAX_INACTIVE_INTERVAL_ATTR, maxInactiveInterval);
|
||||
newMap.put(IS_VALID_ATTR, isValid);
|
||||
newMap.put(IS_NEW_ATTR, isNew);
|
||||
if (principal != null) {
|
||||
newMap.put(PRINCIPAL_ATTR, principal);
|
||||
}
|
||||
if (authType != null) {
|
||||
newMap.put(AUTHTYPE_ATTR, authType);
|
||||
}
|
||||
if (broadcastSessionEvents) {
|
||||
newMap.put(IS_EXPIRATION_LOCKED, isExpirationLocked);
|
||||
}
|
||||
|
||||
if (readMode == ReadMode.MEMORY) {
|
||||
if (attrs != null) {
|
||||
for (Entry<String, Object> entry : attrs.entrySet()) {
|
||||
newMap.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newMap.putAll(updatedAttributes);
|
||||
updatedAttributes.clear();
|
||||
}
|
||||
|
||||
map.putAll(newMap);
|
||||
map.fastRemove(removedAttributes.toArray(new String[0]));
|
||||
|
||||
if (readMode == ReadMode.MEMORY) {
|
||||
topic.publish(createPutAllMessage(newMap));
|
||||
|
||||
if (updateMode == UpdateMode.AFTER_REQUEST) {
|
||||
if (!removedAttributes.isEmpty()) {
|
||||
topic.publish(new AttributeRemoveMessage(redissonManager.getNodeId(), getId(), new HashSet<>(removedAttributes)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removedAttributes.clear();
|
||||
|
||||
expireSession();
|
||||
}
|
||||
|
||||
public void load(Map<String, Object> attrs) {
|
||||
Long creationTime = (Long) attrs.remove(CREATION_TIME_ATTR);
|
||||
if (creationTime != null) {
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
Long lastAccessedTime = (Long) attrs.remove(LAST_ACCESSED_TIME_ATTR);
|
||||
if (lastAccessedTime != null) {
|
||||
this.lastAccessedTime = lastAccessedTime;
|
||||
}
|
||||
Integer maxInactiveInterval = (Integer) attrs.remove(MAX_INACTIVE_INTERVAL_ATTR);
|
||||
if (maxInactiveInterval != null) {
|
||||
this.maxInactiveInterval = maxInactiveInterval;
|
||||
}
|
||||
Long thisAccessedTime = (Long) attrs.remove(THIS_ACCESSED_TIME_ATTR);
|
||||
if (thisAccessedTime != null) {
|
||||
this.thisAccessedTime = thisAccessedTime;
|
||||
}
|
||||
Boolean isValid = (Boolean) attrs.remove(IS_VALID_ATTR);
|
||||
if (isValid != null) {
|
||||
this.isValid = isValid;
|
||||
}
|
||||
Boolean isNew = (Boolean) attrs.remove(IS_NEW_ATTR);
|
||||
if (isNew != null) {
|
||||
this.isNew = isNew;
|
||||
}
|
||||
Boolean isExpirationLocked = (Boolean) attrs.remove(IS_EXPIRATION_LOCKED);
|
||||
if (isExpirationLocked != null) {
|
||||
this.isExpirationLocked = isExpirationLocked;
|
||||
}
|
||||
Principal p = (Principal) attrs.remove(PRINCIPAL_ATTR);
|
||||
if (p != null) {
|
||||
this.principal = p;
|
||||
}
|
||||
String authType = (String) attrs.remove(AUTHTYPE_ATTR);
|
||||
if (authType != null) {
|
||||
this.authType = authType;
|
||||
}
|
||||
|
||||
if (readMode == ReadMode.MEMORY) {
|
||||
for (Entry<String, Object> entry : attrs.entrySet()) {
|
||||
super.setAttribute(entry.getKey(), entry.getValue(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
super.recycle();
|
||||
map = null;
|
||||
loadedAttributes.clear();
|
||||
updatedAttributes.clear();
|
||||
removedAttributes.clear();
|
||||
}
|
||||
|
||||
public void startUsage() {
|
||||
usages.incrementAndGet();
|
||||
}
|
||||
|
||||
public void endUsage() {
|
||||
// don't decrement usages if startUsage wasn't called
|
||||
// if (usages.decrementAndGet() == 0) {
|
||||
if (usages.get() == 0 || usages.decrementAndGet() == 0) {
|
||||
loadedAttributes.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,410 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2020 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.tomcat;
|
||||
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.apache.catalina.*;
|
||||
import org.apache.catalina.session.ManagerBase;
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RSet;
|
||||
import org.redisson.api.RTopic;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.api.listener.MessageListener;
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.client.codec.StringCodec;
|
||||
import org.redisson.codec.CompositeCodec;
|
||||
import org.redisson.config.Config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Redisson Session Manager for Apache Tomcat
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class RedissonSessionManager extends ManagerBase {
|
||||
|
||||
public enum ReadMode {REDIS, MEMORY}
|
||||
public enum UpdateMode {DEFAULT, AFTER_REQUEST}
|
||||
|
||||
private final Log log = LogFactory.getLog(RedissonSessionManager.class);
|
||||
|
||||
private RedissonClient redisson;
|
||||
private String configPath;
|
||||
|
||||
private ReadMode readMode = ReadMode.REDIS;
|
||||
private UpdateMode updateMode = UpdateMode.DEFAULT;
|
||||
|
||||
private String keyPrefix = "";
|
||||
private boolean broadcastSessionEvents = false;
|
||||
|
||||
private final String nodeId = UUID.randomUUID().toString();
|
||||
|
||||
private MessageListener messageListener;
|
||||
|
||||
private Codec codecToUse;
|
||||
|
||||
public String getNodeId() { return nodeId; }
|
||||
|
||||
public String getUpdateMode() {
|
||||
return updateMode.toString();
|
||||
}
|
||||
|
||||
public void setUpdateMode(String updateMode) {
|
||||
this.updateMode = UpdateMode.valueOf(updateMode);
|
||||
}
|
||||
|
||||
public boolean isBroadcastSessionEvents() {
|
||||
return broadcastSessionEvents;
|
||||
}
|
||||
|
||||
public void setBroadcastSessionEvents(boolean replicateSessionEvents) {
|
||||
this.broadcastSessionEvents = replicateSessionEvents;
|
||||
}
|
||||
|
||||
public String getReadMode() {
|
||||
return readMode.toString();
|
||||
}
|
||||
|
||||
public void setReadMode(String readMode) {
|
||||
this.readMode = ReadMode.valueOf(readMode);
|
||||
}
|
||||
|
||||
public void setConfigPath(String configPath) {
|
||||
this.configPath = configPath;
|
||||
}
|
||||
|
||||
public String getConfigPath() {
|
||||
return configPath;
|
||||
}
|
||||
|
||||
public String getKeyPrefix() {
|
||||
return keyPrefix;
|
||||
}
|
||||
|
||||
public void setKeyPrefix(String keyPrefix) {
|
||||
this.keyPrefix = keyPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return RedissonSessionManager.class.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() throws ClassNotFoundException, IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session createSession(String sessionId) {
|
||||
Session session = super.createSession(sessionId);
|
||||
|
||||
if (broadcastSessionEvents) {
|
||||
getTopic().publish(new SessionCreatedMessage(getNodeId(), session.getId()));
|
||||
session.addSessionListener(new SessionListener() {
|
||||
@Override
|
||||
public void sessionEvent(SessionEvent event) {
|
||||
if (event.getType().equals(Session.SESSION_DESTROYED_EVENT)) {
|
||||
getTopic().publish(new SessionDestroyedMessage(getNodeId(), session.getId()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
public RSet<String> getNotifiedNodes(String sessionId) {
|
||||
String separator = keyPrefix == null || keyPrefix.isEmpty() ? "" : ":";
|
||||
String name = keyPrefix + separator + "redisson:tomcat_notified_nodes:" + sessionId;
|
||||
return redisson.getSet(name, StringCodec.INSTANCE);
|
||||
}
|
||||
|
||||
public RMap<String, Object> getMap(String sessionId) {
|
||||
String separator = keyPrefix == null || keyPrefix.isEmpty() ? "" : ":";
|
||||
String name = keyPrefix + separator + "redisson:tomcat_session:" + sessionId;
|
||||
return redisson.getMap(name, new CompositeCodec(StringCodec.INSTANCE, codecToUse, codecToUse));
|
||||
}
|
||||
|
||||
public RTopic getTopic() {
|
||||
String separator = keyPrefix == null || keyPrefix.isEmpty() ? "" : ":";
|
||||
final String name = keyPrefix + separator + "redisson:tomcat_session_updates:" + getContext().getName();
|
||||
return redisson.getTopic(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session findSession(String id) throws IOException {
|
||||
return findSession(id, true);
|
||||
}
|
||||
|
||||
private Session findSession(String id, boolean notify) throws IOException {
|
||||
Session result = super.findSession(id);
|
||||
if (result == null) {
|
||||
if (id != null) {
|
||||
Map<String, Object> attrs = new HashMap<String, Object>();
|
||||
try {
|
||||
attrs = getMap(id).getAll(RedissonSession.ATTRS);
|
||||
} catch (Exception e) {
|
||||
log.error("Can't read session object by id: " + id, e);
|
||||
}
|
||||
|
||||
if (attrs.isEmpty() || (broadcastSessionEvents && getNotifiedNodes(id).contains(nodeId))) {
|
||||
log.info("Session " + id + " can't be found");
|
||||
return null;
|
||||
}
|
||||
|
||||
RedissonSession session = (RedissonSession) createEmptySession();
|
||||
session.load(attrs);
|
||||
session.setId(id, notify);
|
||||
|
||||
session.access();
|
||||
session.endAccess();
|
||||
return session;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
result.access();
|
||||
result.endAccess();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session createEmptySession() {
|
||||
return new RedissonSession(this, readMode, updateMode, broadcastSessionEvents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Session session, boolean update) {
|
||||
super.remove(session, update);
|
||||
|
||||
if (session.getIdInternal() != null) {
|
||||
((RedissonSession)session).delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Session session) {
|
||||
super.add(session);
|
||||
((RedissonSession)session).save();
|
||||
}
|
||||
|
||||
public RedissonClient getRedisson() {
|
||||
return redisson;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startInternal() throws LifecycleException {
|
||||
super.startInternal();
|
||||
redisson = buildClient();
|
||||
|
||||
final ClassLoader applicationClassLoader;
|
||||
if (getContext().getLoader().getClassLoader() != null) {
|
||||
applicationClassLoader = getContext().getLoader().getClassLoader();
|
||||
} else if (Thread.currentThread().getContextClassLoader() != null) {
|
||||
applicationClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
} else {
|
||||
applicationClassLoader = getClass().getClassLoader();
|
||||
}
|
||||
|
||||
Codec codec = redisson.getConfig().getCodec();
|
||||
try {
|
||||
codecToUse = codec.getClass()
|
||||
.getConstructor(ClassLoader.class, codec.getClass())
|
||||
.newInstance(applicationClassLoader, codec);
|
||||
} catch (Exception e) {
|
||||
throw new LifecycleException(e);
|
||||
}
|
||||
|
||||
Pipeline pipeline = getContext().getPipeline();
|
||||
synchronized (pipeline) {
|
||||
if (readMode == ReadMode.REDIS) {
|
||||
Optional<Valve> res = Arrays.stream(pipeline.getValves()).filter(v -> v.getClass() == UsageValve.class).findAny();
|
||||
if (res.isPresent()) {
|
||||
((UsageValve)res.get()).incUsage();
|
||||
} else {
|
||||
pipeline.addValve(new UsageValve());
|
||||
}
|
||||
}
|
||||
if (updateMode == UpdateMode.AFTER_REQUEST) {
|
||||
Optional<Valve> res = Arrays.stream(pipeline.getValves()).filter(v -> v.getClass() == UpdateValve.class).findAny();
|
||||
if (res.isPresent()) {
|
||||
((UpdateValve)res.get()).incUsage();
|
||||
} else {
|
||||
pipeline.addValve(new UpdateValve());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (readMode == ReadMode.MEMORY || broadcastSessionEvents) {
|
||||
RTopic updatesTopic = getTopic();
|
||||
messageListener = new MessageListener<AttributeMessage>() {
|
||||
|
||||
@Override
|
||||
public void onMessage(CharSequence channel, AttributeMessage msg) {
|
||||
try {
|
||||
if (msg.getNodeId().equals(nodeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
RedissonSession session = (RedissonSession) RedissonSessionManager.super.findSession(msg.getSessionId());
|
||||
if (session != null) {
|
||||
if (msg instanceof SessionDestroyedMessage) {
|
||||
session.expire();
|
||||
}
|
||||
|
||||
if (msg instanceof AttributeRemoveMessage) {
|
||||
for (String name : ((AttributeRemoveMessage)msg).getNames()) {
|
||||
session.superRemoveAttributeInternal(name, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (msg instanceof AttributesClearMessage) {
|
||||
RedissonSessionManager.super.remove(session, false);
|
||||
}
|
||||
|
||||
if (msg instanceof AttributesPutAllMessage) {
|
||||
AttributesPutAllMessage m = (AttributesPutAllMessage) msg;
|
||||
Map<String, Object> attrs = m.getAttrs(codecToUse.getMapValueDecoder());
|
||||
session.load(attrs);
|
||||
}
|
||||
|
||||
if (msg instanceof AttributeUpdateMessage) {
|
||||
AttributeUpdateMessage m = (AttributeUpdateMessage)msg;
|
||||
session.superSetAttribute(m.getName(), m.getValue(codecToUse.getMapValueDecoder()), true);
|
||||
}
|
||||
} else {
|
||||
if (msg instanceof SessionCreatedMessage) {
|
||||
Session s = findSession(msg.getSessionId());
|
||||
if (s == null) {
|
||||
throw new IllegalStateException("Unable to find session: " + msg.getSessionId());
|
||||
}
|
||||
}
|
||||
|
||||
if (msg instanceof SessionDestroyedMessage) {
|
||||
Session s = findSession(msg.getSessionId(), false);
|
||||
if (s == null) {
|
||||
throw new IllegalStateException("Unable to find session: " + msg.getSessionId());
|
||||
}
|
||||
s.expire();
|
||||
RSet<String> set = getNotifiedNodes(msg.getSessionId());
|
||||
set.add(nodeId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Unable to handle topic message", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
updatesTopic.addListener(AttributeMessage.class, messageListener);
|
||||
}
|
||||
|
||||
setState(LifecycleState.STARTING);
|
||||
}
|
||||
|
||||
protected RedissonClient buildClient() throws LifecycleException {
|
||||
Config config = null;
|
||||
try {
|
||||
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) {
|
||||
throw new LifecycleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopInternal() throws LifecycleException {
|
||||
super.stopInternal();
|
||||
|
||||
setState(LifecycleState.STOPPING);
|
||||
|
||||
Pipeline pipeline = getContext().getPipeline();
|
||||
synchronized (pipeline) {
|
||||
if (readMode == ReadMode.REDIS) {
|
||||
Arrays.stream(pipeline.getValves()).filter(v -> v.getClass() == UsageValve.class).forEach(v -> {
|
||||
if (((UsageValve)v).decUsage() == 0){
|
||||
pipeline.removeValve(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (updateMode == UpdateMode.AFTER_REQUEST) {
|
||||
Arrays.stream(pipeline.getValves()).filter(v -> v.getClass() == UpdateValve.class).forEach(v -> {
|
||||
if (((UpdateValve)v).decUsage() == 0){
|
||||
pipeline.removeValve(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (messageListener != null) {
|
||||
RTopic updatesTopic = getTopic();
|
||||
updatesTopic.removeListener(messageListener);
|
||||
}
|
||||
|
||||
codecToUse = null;
|
||||
|
||||
try {
|
||||
shutdownRedisson();
|
||||
} catch (Exception e) {
|
||||
throw new LifecycleException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void shutdownRedisson() {
|
||||
if (redisson != null) {
|
||||
redisson.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public void store(HttpSession session) throws IOException {
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
RedissonSession sess = (RedissonSession) super.findSession(session.getId());
|
||||
if (sess != null) {
|
||||
sess.access();
|
||||
sess.endAccess();
|
||||
sess.save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2020 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.tomcat;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class SessionCreatedMessage extends AttributeMessage {
|
||||
|
||||
public SessionCreatedMessage() {
|
||||
}
|
||||
|
||||
public SessionCreatedMessage(String nodeId, String sessionId) {
|
||||
super(nodeId, sessionId);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2020 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.tomcat;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class SessionDestroyedMessage extends AttributeMessage {
|
||||
|
||||
public SessionDestroyedMessage() {
|
||||
}
|
||||
|
||||
public SessionDestroyedMessage(String nodeId, String sessionId) {
|
||||
super(nodeId, sessionId);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2020 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.tomcat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import org.apache.catalina.Manager;
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.apache.catalina.connector.Response;
|
||||
import org.apache.catalina.valves.ValveBase;
|
||||
|
||||
/**
|
||||
* Redisson Valve object for Apache Tomcat
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class UpdateValve extends ValveBase {
|
||||
|
||||
private static final String ALREADY_FILTERED_NOTE = UpdateValve.class.getName() + ".ALREADY_FILTERED_NOTE";
|
||||
|
||||
private final AtomicInteger usage = new AtomicInteger(1);
|
||||
|
||||
public UpdateValve() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
public void incUsage() {
|
||||
usage.incrementAndGet();
|
||||
}
|
||||
|
||||
public int decUsage() {
|
||||
return usage.decrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(Request request, Response response) throws IOException, ServletException {
|
||||
if (getNext() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//check if we already filtered/processed this request
|
||||
if (request.getNote(ALREADY_FILTERED_NOTE) == null) {
|
||||
request.setNote(ALREADY_FILTERED_NOTE, Boolean.TRUE);
|
||||
try {
|
||||
getNext().invoke(request, response);
|
||||
} finally {
|
||||
request.removeNote(ALREADY_FILTERED_NOTE);
|
||||
if (request.getContext() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
ClassLoader applicationClassLoader = request.getContext().getLoader().getClassLoader();
|
||||
Thread.currentThread().setContextClassLoader(applicationClassLoader);
|
||||
Manager manager = request.getContext().getManager();
|
||||
((RedissonSessionManager)manager).store(request.getSession(false));
|
||||
} finally {
|
||||
Thread.currentThread().setContextClassLoader(classLoader);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
getNext().invoke(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2020 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.tomcat;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.apache.catalina.connector.Response;
|
||||
import org.apache.catalina.valves.ValveBase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Redisson Valve object for Apache Tomcat
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class UsageValve extends ValveBase {
|
||||
|
||||
private static final String ALREADY_FILTERED_NOTE = UsageValve.class.getName() + ".ALREADY_FILTERED_NOTE";
|
||||
|
||||
private final AtomicInteger usage = new AtomicInteger(1);
|
||||
|
||||
public UsageValve() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
public void incUsage() {
|
||||
usage.incrementAndGet();
|
||||
}
|
||||
|
||||
public int decUsage() {
|
||||
return usage.decrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(Request request, Response response) throws IOException, ServletException {
|
||||
if (getNext() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//check if we already filtered/processed this request
|
||||
if (request.getNote(ALREADY_FILTERED_NOTE) == null) {
|
||||
request.setNote(ALREADY_FILTERED_NOTE, Boolean.TRUE);
|
||||
try {
|
||||
if (request.getContext() != null) {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
RedissonSession s = (RedissonSession) request.getContext().getManager().findSession(session.getId());
|
||||
if (s != null) {
|
||||
s.startUsage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getNext().invoke(request, response);
|
||||
} finally {
|
||||
request.removeNote(ALREADY_FILTERED_NOTE);
|
||||
if (request.getContext() != null) {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
RedissonSession s = (RedissonSession) request.getContext().getManager().findSession(session.getId());
|
||||
if (s != null) {
|
||||
s.endUsage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
getNext().invoke(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,333 @@
|
||||
package org.redisson.tomcat;
|
||||
|
||||
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.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.config.Config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class RedissonSessionManagerTest {
|
||||
|
||||
@Parameterized.Parameters(name= "{index} - {0}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][] {
|
||||
{"context_memory.xml"},
|
||||
{"context_redis.xml"},
|
||||
{"context_redis_after_request.xml"},
|
||||
{"context_memory_after_request.xml"}
|
||||
});
|
||||
}
|
||||
|
||||
@Parameterized.Parameter(0)
|
||||
public String contextName;
|
||||
|
||||
@Before
|
||||
public void before() throws IOException {
|
||||
String basePath = "src/test/webapp/META-INF/";
|
||||
Files.deleteIfExists(Paths.get(basePath + "context.xml"));
|
||||
Files.copy(Paths.get(basePath + contextName), Paths.get(basePath + "context.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateTwoServers_readValue() throws Exception {
|
||||
TomcatServer server1 = new TomcatServer("myapp", 8080, "src/test/");
|
||||
TomcatServer server2 = new TomcatServer("myapp", 8081, "src/test/");
|
||||
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(8080, executor, "test", "from_server2");
|
||||
|
||||
} finally {
|
||||
Executor.closeIdleConnections();
|
||||
server1.stop();
|
||||
server2.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpSessionListener() throws Exception {
|
||||
TomcatServer server1 = new TomcatServer("myapp", 8080, "src/test/");
|
||||
server1.start();
|
||||
|
||||
TomcatServer server2 = new TomcatServer("myapp", 8081, "src/test/");
|
||||
server2.start();
|
||||
|
||||
Executor executor = Executor.newInstance();
|
||||
BasicCookieStore cookieStore = new BasicCookieStore();
|
||||
executor.use(cookieStore);
|
||||
|
||||
TestHttpSessionListener.CREATED_INVOCATION_COUNTER = 0;
|
||||
TestHttpSessionListener.DESTROYED_INVOCATION_COUNTER = 0;
|
||||
|
||||
write(8080, executor, "test", "1234");
|
||||
|
||||
TomcatServer server3 = new TomcatServer("myapp", 8082, "src/test/");
|
||||
server3.start();
|
||||
|
||||
invalidate(executor);
|
||||
|
||||
Thread.sleep(500);
|
||||
|
||||
Assert.assertEquals(2, TestHttpSessionListener.CREATED_INVOCATION_COUNTER);
|
||||
Assert.assertEquals(3, TestHttpSessionListener.DESTROYED_INVOCATION_COUNTER);
|
||||
|
||||
Executor.closeIdleConnections();
|
||||
server1.stop();
|
||||
server2.stop();
|
||||
server3.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateTwoServers_twoValues() throws Exception {
|
||||
TomcatServer server1 = new TomcatServer("myapp", 8080, "src/test/");
|
||||
TomcatServer server2 = new TomcatServer("myapp", 8081, "src/test/");
|
||||
try {
|
||||
server1.start();
|
||||
server2.start();
|
||||
|
||||
Executor executor = Executor.newInstance();
|
||||
BasicCookieStore cookieStore = new BasicCookieStore();
|
||||
executor.use(cookieStore);
|
||||
|
||||
write(8080, executor, "key1", "value1");
|
||||
write(8080, executor, "key2", "value1");
|
||||
|
||||
write(8081, executor, "key1", "value2");
|
||||
write(8081, executor, "key2", "value2");
|
||||
|
||||
read(8080, executor, "key1", "value2");
|
||||
read(8080, executor, "key2", "value2");
|
||||
|
||||
} finally {
|
||||
Executor.closeIdleConnections();
|
||||
server1.stop();
|
||||
server2.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateTwoServers() throws Exception {
|
||||
TomcatServer server1 = new TomcatServer("myapp", 8080, "src/test/");
|
||||
server1.start();
|
||||
TomcatServer server2 = new TomcatServer("myapp", 8081, "src/test/");
|
||||
server2.start();
|
||||
|
||||
Executor executor = Executor.newInstance();
|
||||
BasicCookieStore cookieStore = new BasicCookieStore();
|
||||
executor.use(cookieStore);
|
||||
|
||||
write(8080, executor, "test", "1234");
|
||||
|
||||
read(8081, executor, "test", "1234");
|
||||
read(8080, executor, "test", "1234");
|
||||
write(8080, executor, "test", "324");
|
||||
read(8081, executor, "test", "324");
|
||||
|
||||
Executor.closeIdleConnections();
|
||||
server1.stop();
|
||||
server2.stop();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testExpiration() throws Exception {
|
||||
TomcatServer server1 = new TomcatServer("myapp", 8080, "src/test/");
|
||||
server1.start();
|
||||
|
||||
Executor executor = Executor.newInstance();
|
||||
BasicCookieStore cookieStore = new BasicCookieStore();
|
||||
executor.use(cookieStore);
|
||||
|
||||
write(8080, executor, "test", "1234");
|
||||
|
||||
TomcatServer server2 = new TomcatServer("myapp", 8081, "src/test/");
|
||||
server2.start();
|
||||
|
||||
Thread.sleep(30000);
|
||||
|
||||
read(8081, executor, "test", "1234");
|
||||
|
||||
Thread.sleep(40000);
|
||||
|
||||
executor.use(cookieStore);
|
||||
read(8080, executor, "test", "1234");
|
||||
|
||||
Executor.closeIdleConnections();
|
||||
server1.stop();
|
||||
server2.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwitchServer() throws Exception {
|
||||
// start the server at http://localhost:8080/myapp
|
||||
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
|
||||
server.start();
|
||||
|
||||
Executor executor = Executor.newInstance();
|
||||
BasicCookieStore cookieStore = new BasicCookieStore();
|
||||
executor.use(cookieStore);
|
||||
|
||||
write(8080, executor, "test", "1234");
|
||||
System.out.println("done1");
|
||||
Cookie cookie = cookieStore.getCookies().get(0);
|
||||
|
||||
Executor.closeIdleConnections();
|
||||
server.stop();
|
||||
|
||||
server = new TomcatServer("myapp", 8080, "src/test/");
|
||||
server.start();
|
||||
|
||||
executor = Executor.newInstance();
|
||||
cookieStore = new BasicCookieStore();
|
||||
cookieStore.addCookie(cookie);
|
||||
executor.use(cookieStore);
|
||||
read(8080, executor, "test", "1234");
|
||||
remove(executor, "test", "null");
|
||||
|
||||
Executor.closeIdleConnections();
|
||||
server.stop();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWriteReadRemove() throws Exception {
|
||||
// start the server at http://localhost:8080/myapp
|
||||
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
|
||||
server.start();
|
||||
|
||||
Executor executor = Executor.newInstance();
|
||||
|
||||
write(8080, executor, "test", "1234");
|
||||
read(8080, executor, "test", "1234");
|
||||
remove(executor, "test", "null");
|
||||
|
||||
Executor.closeIdleConnections();
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecreate() throws Exception {
|
||||
// start the server at http://localhost:8080/myapp
|
||||
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
|
||||
server.start();
|
||||
|
||||
Executor executor = Executor.newInstance();
|
||||
|
||||
write(8080, executor, "test", "1");
|
||||
recreate(executor, "test", "2");
|
||||
read(8080, executor, "test", "2");
|
||||
|
||||
Executor.closeIdleConnections();
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdate() throws Exception {
|
||||
// start the server at http://localhost:8080/myapp
|
||||
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
|
||||
server.start();
|
||||
|
||||
Executor executor = Executor.newInstance();
|
||||
|
||||
write(8080, executor, "test", "1");
|
||||
read(8080, executor, "test", "1");
|
||||
write(8080, executor, "test", "2");
|
||||
read(8080, executor, "test", "2");
|
||||
|
||||
Executor.closeIdleConnections();
|
||||
server.stop();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testInvalidate() throws Exception {
|
||||
File f = Paths.get("").toAbsolutePath().resolve("src/test/webapp/WEB-INF/redisson.yaml").toFile();
|
||||
Config config = Config.fromYAML(f);
|
||||
RedissonClient r = Redisson.create(config);
|
||||
r.getKeys().flushall();
|
||||
|
||||
// start the server at http://localhost:8080/myapp
|
||||
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
|
||||
server.start();
|
||||
|
||||
Executor executor = Executor.newInstance();
|
||||
BasicCookieStore cookieStore = new BasicCookieStore();
|
||||
executor.use(cookieStore);
|
||||
|
||||
write(8080, executor, "test", "1234");
|
||||
Cookie cookie = cookieStore.getCookies().get(0);
|
||||
invalidate(executor);
|
||||
|
||||
Executor.closeIdleConnections();
|
||||
|
||||
executor = Executor.newInstance();
|
||||
cookieStore = new BasicCookieStore();
|
||||
cookieStore.addCookie(cookie);
|
||||
executor.use(cookieStore);
|
||||
read(8080, executor, "test", "null");
|
||||
invalidate(executor);
|
||||
|
||||
Executor.closeIdleConnections();
|
||||
server.stop();
|
||||
|
||||
TimeUnit.SECONDS.sleep(60);
|
||||
Assert.assertEquals(0, r.getKeys().count());
|
||||
}
|
||||
|
||||
private void write(int port, Executor executor, String key, String value) throws IOException {
|
||||
String url = "http://localhost:" + port + "/myapp/write?key=" + key + "&value=" + value;
|
||||
String response = executor.execute(Request.Get(url)).returnContent().asString();
|
||||
Assert.assertEquals("OK", response);
|
||||
}
|
||||
|
||||
private void read(int port, Executor executor, String key, String value) throws IOException {
|
||||
String url = "http://localhost:" + port + "/myapp/read?key=" + key;
|
||||
String response = executor.execute(Request.Get(url)).returnContent().asString();
|
||||
Assert.assertEquals(value, response);
|
||||
}
|
||||
|
||||
private void remove(Executor executor, String key, String value) throws IOException {
|
||||
String url = "http://localhost:8080/myapp/remove?key=" + key;
|
||||
String response = executor.execute(Request.Get(url)).returnContent().asString();
|
||||
Assert.assertEquals(value, response);
|
||||
}
|
||||
|
||||
private void invalidate(Executor executor) throws IOException {
|
||||
String url = "http://localhost:8080/myapp/invalidate";
|
||||
String response = executor.execute(Request.Get(url)).returnContent().asString();
|
||||
Assert.assertEquals("OK", response);
|
||||
}
|
||||
|
||||
private void recreate(Executor executor, String key, String value) throws IOException {
|
||||
String url = "http://localhost:8080/myapp/recreate?key=" + key + "&value=" + value;
|
||||
String response = executor.execute(Request.Get(url)).returnContent().asString();
|
||||
Assert.assertEquals("OK", response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.redisson.tomcat;
|
||||
|
||||
import jakarta.servlet.http.HttpSessionEvent;
|
||||
import jakarta.servlet.http.HttpSessionListener;
|
||||
|
||||
public class TestHttpSessionListener implements HttpSessionListener {
|
||||
|
||||
public static int CREATED_INVOCATION_COUNTER;
|
||||
public static int DESTROYED_INVOCATION_COUNTER;
|
||||
|
||||
@Override
|
||||
public void sessionCreated(HttpSessionEvent se) {
|
||||
CREATED_INVOCATION_COUNTER++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionDestroyed(HttpSessionEvent se) {
|
||||
DESTROYED_INVOCATION_COUNTER++;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package org.redisson.tomcat;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
public class TestServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1243830648280853203L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
HttpSession session = req.getSession();
|
||||
|
||||
if (req.getPathInfo().equals("/write")) {
|
||||
String[] params = req.getQueryString().split("&");
|
||||
String key = null;
|
||||
String value = null;
|
||||
for (String param : params) {
|
||||
String[] paramLine = param.split("=");
|
||||
String keyParam = paramLine[0];
|
||||
String valueParam = paramLine[1];
|
||||
|
||||
if ("key".equals(keyParam)) {
|
||||
key = valueParam;
|
||||
}
|
||||
if ("value".equals(keyParam)) {
|
||||
value = valueParam;
|
||||
}
|
||||
}
|
||||
session.setAttribute(key, value);
|
||||
|
||||
resp.getWriter().print("OK");
|
||||
} else if (req.getPathInfo().equals("/read")) {
|
||||
String[] params = req.getQueryString().split("&");
|
||||
String key = null;
|
||||
for (String param : params) {
|
||||
String[] line = param.split("=");
|
||||
String keyParam = line[0];
|
||||
if ("key".equals(keyParam)) {
|
||||
key = line[1];
|
||||
}
|
||||
}
|
||||
|
||||
Object attr = session.getAttribute(key);
|
||||
resp.getWriter().print(attr);
|
||||
} else if (req.getPathInfo().equals("/remove")) {
|
||||
String[] params = req.getQueryString().split("&");
|
||||
String key = null;
|
||||
for (String param : params) {
|
||||
String[] line = param.split("=");
|
||||
String keyParam = line[0];
|
||||
if ("key".equals(keyParam)) {
|
||||
key = line[1];
|
||||
}
|
||||
}
|
||||
|
||||
session.removeAttribute(key);
|
||||
resp.getWriter().print(String.valueOf(session.getAttribute(key)));
|
||||
} else if (req.getPathInfo().equals("/invalidate")) {
|
||||
session.invalidate();
|
||||
|
||||
resp.getWriter().print("OK");
|
||||
} else if (req.getPathInfo().equals("/recreate")) {
|
||||
session.invalidate();
|
||||
|
||||
session = req.getSession();
|
||||
|
||||
String[] params = req.getQueryString().split("&");
|
||||
String key = null;
|
||||
String value = null;
|
||||
for (String param : params) {
|
||||
String[] paramLine = param.split("=");
|
||||
String keyParam = paramLine[0];
|
||||
String valueParam = paramLine[1];
|
||||
|
||||
if ("key".equals(keyParam)) {
|
||||
key = valueParam;
|
||||
}
|
||||
if ("value".equals(keyParam)) {
|
||||
value = valueParam;
|
||||
}
|
||||
}
|
||||
session.setAttribute(key, value);
|
||||
|
||||
resp.getWriter().print("OK");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package org.redisson.tomcat;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class TomcatServer {
|
||||
|
||||
private Tomcat tomcat = new Tomcat();
|
||||
private int port;
|
||||
private boolean isRunning;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TomcatServer.class);
|
||||
private static final boolean isInfo = LOG.isInfoEnabled();
|
||||
|
||||
public TomcatServer(String contextPath, int port, String appBase) throws MalformedURLException, ServletException {
|
||||
if(contextPath == null || appBase == null || appBase.length() == 0) {
|
||||
throw new IllegalArgumentException("Context path or appbase should not be null");
|
||||
}
|
||||
if(!contextPath.startsWith("/")) {
|
||||
contextPath = "/" + contextPath;
|
||||
}
|
||||
|
||||
tomcat.setBaseDir("."); // location where temp dir is created
|
||||
tomcat.setPort(port);
|
||||
tomcat.getHost().setAppBase(".");
|
||||
|
||||
tomcat.addWebapp(contextPath, appBase + "/webapp");
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the tomcat embedded server
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public void start() throws LifecycleException, InterruptedException {
|
||||
tomcat.start();
|
||||
tomcat.getConnector();
|
||||
isRunning = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the tomcat embedded server
|
||||
*/
|
||||
public void stop() throws LifecycleException {
|
||||
if(!isRunning) {
|
||||
LOG.warn("Tomcat server is not running @ port={}", port);
|
||||
return;
|
||||
}
|
||||
|
||||
if(isInfo) LOG.info("Stopping the Tomcat server");
|
||||
|
||||
tomcat.stop();
|
||||
tomcat.destroy();
|
||||
tomcat.getServer().await();
|
||||
isRunning = false;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<Context>
|
||||
|
||||
<Manager className="org.redisson.tomcat.RedissonSessionManager"
|
||||
configPath="${catalina.base}/src/test/webapp/WEB-INF/redisson.yaml"
|
||||
readMode="MEMORY"
|
||||
updateMode="AFTER_REQUEST"
|
||||
broadcastSessionEvents="true"/>
|
||||
|
||||
</Context>
|
@ -0,0 +1,9 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<Context>
|
||||
|
||||
<Manager className="org.redisson.tomcat.RedissonSessionManager"
|
||||
configPath="${catalina.base}/src/test/webapp/WEB-INF/redisson.yaml"
|
||||
readMode="MEMORY"
|
||||
broadcastSessionEvents="true"/>
|
||||
|
||||
</Context>
|
@ -0,0 +1,10 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<Context>
|
||||
|
||||
<Manager className="org.redisson.tomcat.RedissonSessionManager"
|
||||
configPath="${catalina.base}/src/test/webapp/WEB-INF/redisson.yaml"
|
||||
readMode="MEMORY"
|
||||
updateMode="AFTER_REQUEST"
|
||||
broadcastSessionEvents="true"/>
|
||||
|
||||
</Context>
|
@ -0,0 +1,9 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<Context>
|
||||
|
||||
<Manager className="org.redisson.tomcat.RedissonSessionManager"
|
||||
configPath="${catalina.base}/src/test/webapp/WEB-INF/redisson.yaml"
|
||||
readMode="REDIS"
|
||||
broadcastSessionEvents="true"/>
|
||||
|
||||
</Context>
|
@ -0,0 +1,10 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<Context>
|
||||
|
||||
<Manager className="org.redisson.tomcat.RedissonSessionManager"
|
||||
configPath="${catalina.base}/src/test/webapp/WEB-INF/redisson.yaml"
|
||||
readMode="REDIS"
|
||||
updateMode="AFTER_REQUEST"
|
||||
broadcastSessionEvents="true"/>
|
||||
|
||||
</Context>
|
@ -0,0 +1,2 @@
|
||||
singleServerConfig:
|
||||
address: "redis://127.0.0.1:6379"
|
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
version="3.0">
|
||||
|
||||
<servlet>
|
||||
<servlet-name>testServlet</servlet-name>
|
||||
<servlet-class>org.redisson.tomcat.TestServlet</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>testServlet</servlet-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<session-config>
|
||||
<session-timeout>30</session-timeout>
|
||||
</session-config>
|
||||
|
||||
<listener>
|
||||
<listener-class>org.redisson.tomcat.TestHttpSessionListener</listener-class>
|
||||
</listener>
|
||||
|
||||
</web-app>
|
Loading…
Reference in New Issue