diff --git a/pom.xml b/pom.xml
index c5b3cac..dee08b2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,12 +1,12 @@
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
4.0.0
com.amazonaws
aws-dynamodb-session-tomcat
jar
Amazon DynamoDB Session Manager for Tomcat
- 1.2
+ 1.0.5
The Amazon DynamoDB Session Manager for Tomcat provides a custom session manager for Tomcat 7 that stores session data in Amazon DynamoDB, Amazon's fully managed NoSQL database service.
https://aws.amazon.com/java
@@ -34,145 +34,173 @@
-
- com.amazonaws
- aws-java-sdk
- 1.5.4
-
-
- org.apache.tomcat
- tomcat-catalina
- 7.0.47
-
+
+ com.amazonaws
+ aws-java-sdk-dynamodb
+ 1.9.23
+
+
+ org.apache.tomcat
+ tomcat-catalina
+ 7.0.42
+ provided
+
+
+ org.mockito
+ mockito-core
+ 1.10.19
+ test
+
+
+ junit
+ junit
+ 4.12
+ test
+
-
-
- ${basedir}
-
- LICENSE.txt
- NOTICE.txt
- README.txt
-
-
-
+
+
+ ${basedir}
+
+ LICENSE.txt
+ NOTICE.txt
+ README.md
+
+
+
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 2.3
-
-
- 1.7
- UTF-8
-
-
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 2.3
+
+
+ 1.6
+ UTF-8
+
+
-
- org.apache.maven.plugins
- maven-shade-plugin
- 2.2
-
-
- package
-
- shade
-
-
- true
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.2
+
+
+ package
+
+ shade
+
+
+
+
+
+ false
+
+
+ true
-
-
- com.amazonaws
- com.amazonaws.tomcatsessionmanager.amazonaws
-
-
- com.amazonaws.services.dynamodb.sessionmanager.DynamoDBSessionManager
-
-
-
- org.apache.http
- com.amazonaws.tomcatsessionmanager.apache.http
-
-
- org.apache.commons.logging
- com.amazonaws.tomcatsessionmanager.apache.commons.logging
-
-
- org.apache.commons.codec
- com.amazonaws.tomcatsessionmanager.apache.commons.codec
-
-
-
- org.codehaus
- com.amazonaws.tomcatsessionmanager.codehaus
-
-
-
-
-
- com.amazonaws:aws-java-sdk
- commons-logging:*
- org.apache.httpcomponents:*
- commons-codec:*
- org.codehaus.jackson:*
-
-
+
+
+ com.amazonaws
+ com.amazonaws.tomcatsessionmanager.amazonaws
+
+
+ com.amazonaws.services.dynamodb.sessionmanager.DynamoDBSessionManager
+
+
+
+ org.apache.http
+ com.amazonaws.tomcatsessionmanager.apache.http
+
+
+ org.apache.commons.logging
+ com.amazonaws.tomcatsessionmanager.apache.commons.logging
+
+
+ org.apache.commons.codec
+ com.amazonaws.tomcatsessionmanager.apache.commons.codec
+
+
+ com.fasterxml
+ com.amazonaws.tomcatsessionmanager.fasterxml
+
+
+ org.joda.time
+ com.amazonaws.tomcatsessionmanager.joda.time
+
+
-
-
-
- commons-logging:commons-logging
-
- **
-
-
-
+
+
+ com.amazonaws:aws-java-sdk*
+ commons-logging:*
+ org.apache.httpcomponents:*
+ commons-codec:*
+ com.fasterxml.jackson.core:*
+ joda-time:*
+
+
-
-
-
-
-
+
+
+
+ commons-logging:commons-logging
+
+ **
+
+
+
+ com.fasterxml.jackson.core:*
+
+ **
+
+
+
+
+
+
+
+
-
- publishing
-
-
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
-
-
- sign-artifacts
- verify
-
- sign
-
-
-
-
+
+ publishing
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.5.1
- true
-
- sonatype-nexus-staging
- https://oss.sonatype.org
- true
-
-
-
-
-
+
+ org.sonatype.plugins
+ nexus-staging-maven-plugin
+ 1.5.1
+ true
+
+ sonatype-nexus-staging
+ https://oss.sonatype.org
+ true
+
+
+
+
+
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java
index 0a098b1..444a792 100644
--- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java
@@ -16,30 +16,42 @@
import java.io.File;
+import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.session.PersistentManagerBase;
import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
import com.amazonaws.AmazonClientException;
+import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.auth.PropertiesCredentials;
import com.amazonaws.internal.StaticCredentialsProvider;
import com.amazonaws.regions.RegionUtils;
+import com.amazonaws.services.dynamodb.sessionmanager.converters.DefaultTomcatSessionConverter;
+import com.amazonaws.services.dynamodb.sessionmanager.converters.LegacyDynamoDBSessionItemConverter;
+import com.amazonaws.services.dynamodb.sessionmanager.converters.LegacyTomcatSessionConverter;
+import com.amazonaws.services.dynamodb.sessionmanager.converters.SessionConverter;
+import com.amazonaws.services.dynamodb.sessionmanager.converters.TomcatSessionConverterChain;
import com.amazonaws.services.dynamodb.sessionmanager.util.DynamoUtils;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
+import com.amazonaws.services.dynamodbv2.util.Tables;
+import com.amazonaws.util.StringUtils;
/**
- * Tomcat 7.0 persistent session manager implementation that uses Amazon
- * DynamoDB to store HTTP session data.
+ * Tomcat persistent session manager implementation that uses Amazon DynamoDB to store HTTP session
+ * data.
*/
public class DynamoDBSessionManager extends PersistentManagerBase {
- private static final String DEFAULT_TABLE_NAME = "Tomcat_SessionState";
+ public static final String DEFAULT_TABLE_NAME = "Tomcat_SessionState";
+ private static final String USER_AGENT = "DynamoSessionManager/1.1";
private static final String name = "AmazonDynamoDBSessionManager";
- private static final String info = name + "/2.0";
+ private static final String info = name + "/1.1";
private String regionId = "us-east-1";
private String endpoint;
@@ -50,15 +62,12 @@ public class DynamoDBSessionManager extends PersistentManagerBase {
private long writeCapacityUnits = 5;
private boolean createIfNotExist = true;
private String tableName = DEFAULT_TABLE_NAME;
+ private String proxyHost;
+ private Integer proxyPort;
- private final DynamoDBSessionStore dynamoSessionStore;
-
- private static Log logger;
-
+ private static final Log logger = LogFactory.getLog(DynamoDBSessionManager.class);
public DynamoDBSessionManager() {
- dynamoSessionStore = new DynamoDBSessionStore();
- setStore(dynamoSessionStore);
setSaveOnRestart(true);
// MaxInactiveInterval controls when sessions are removed from the store
@@ -118,6 +127,13 @@ public void setCreateIfNotExist(boolean createIfNotExist) {
this.createIfNotExist = createIfNotExist;
}
+ public void setProxyHost(String proxyHost) {
+ this.proxyHost = proxyHost;
+ }
+
+ public void setProxyPort(Integer proxyPort) {
+ this.proxyPort = proxyPort;
+ }
//
// Private Interface
@@ -127,78 +143,146 @@ public void setCreateIfNotExist(boolean createIfNotExist) {
protected void initInternal() throws LifecycleException {
this.setDistributable(true);
- // Grab the container's logger
- logger = getContainer().getLogger();
-
- AWSCredentialsProvider credentialsProvider = initCredentials();
- AmazonDynamoDBClient dynamo = new AmazonDynamoDBClient(credentialsProvider);
- if (this.regionId != null) dynamo.setRegion(RegionUtils.getRegion(this.regionId));
- if (this.endpoint != null) dynamo.setEndpoint(this.endpoint);
-
- initDynamoTable(dynamo);
-
- // init session store
- dynamoSessionStore.setDynamoClient(dynamo);
- dynamoSessionStore.setSessionTableName(this.tableName);
-
- }
-
- @Override
- protected synchronized void stopInternal() throws LifecycleException {
- super.stopInternal();
+ AmazonDynamoDBClient dynamoClient = createDynamoClient();
+ initDynamoTable(dynamoClient);
+ DynamoSessionStorage sessionStorage = createSessionStorage(dynamoClient);
+ setStore(new DynamoDBSessionStore(sessionStorage));
+ new ExpiredSessionReaperExecutor(new ExpiredSessionReaper(sessionStorage));
}
- private void initDynamoTable(AmazonDynamoDBClient dynamo) {
- boolean tableExists = DynamoUtils.doesTableExist(dynamo, this.tableName);
-
- if (!tableExists && !createIfNotExist) {
- throw new AmazonClientException("Session table '" + tableName + "' does not exist, "
- + "and automatic table creation has been disabled in context.xml");
+ private AmazonDynamoDBClient createDynamoClient() {
+ AWSCredentialsProvider credentialsProvider = initCredentials();
+ ClientConfiguration clientConfiguration = initClientConfiguration();
+ AmazonDynamoDBClient dynamoClient = new AmazonDynamoDBClient(credentialsProvider, clientConfiguration);
+ if (this.regionId != null) {
+ dynamoClient.setRegion(RegionUtils.getRegion(this.regionId));
}
-
- if (!tableExists) DynamoUtils.createSessionTable(dynamo, this.tableName,
- this.readCapacityUnits, this.writeCapacityUnits);
-
- DynamoUtils.waitForTableToBecomeActive(dynamo, this.tableName);
+ if (this.endpoint != null) {
+ dynamoClient.setEndpoint(this.endpoint);
+ }
+ return dynamoClient;
}
private AWSCredentialsProvider initCredentials() {
- // Attempt to use any explicitly specified credentials first
- if (accessKey != null || secretKey != null) {
- getContainer().getLogger().debug("Reading security credentials from context.xml");
- if (accessKey == null || secretKey == null) {
+ // Attempt to use any credentials specified in context.xml first
+ if (credentialsExistInContextConfig()) {
+ // Fail fast if credentials aren't valid as user has likely made a configuration mistake
+ if (credentialsInContextConfigAreValid()) {
throw new AmazonClientException("Incomplete AWS security credentials specified in context.xml.");
}
- getContainer().getLogger().debug("Using AWS access key ID and secret key from context.xml");
+ debug("Using AWS access key ID and secret key from context.xml");
return new StaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey));
}
// Use any explicitly specified credentials properties file next
if (credentialsFile != null) {
try {
- getContainer().getLogger().debug("Reading security credentials from properties file: " + credentialsFile);
+ debug("Reading security credentials from properties file: " + credentialsFile);
PropertiesCredentials credentials = new PropertiesCredentials(credentialsFile);
- getContainer().getLogger().debug("Using AWS credentials from file: " + credentialsFile);
+ debug("Using AWS credentials from file: " + credentialsFile);
return new StaticCredentialsProvider(credentials);
} catch (Exception e) {
throw new AmazonClientException(
- "Unable to read AWS security credentials from file specified in context.xml: " + credentialsFile, e);
+ "Unable to read AWS security credentials from file specified in context.xml: "
+ + credentialsFile, e);
}
}
// Fall back to the default credentials chain provider if credentials weren't explicitly set
AWSCredentialsProvider defaultChainProvider = new DefaultAWSCredentialsProviderChain();
if (defaultChainProvider.getCredentials() == null) {
- getContainer().getLogger().debug("Loading security credentials from default credentials provider chain.");
+ debug("Loading security credentials from default credentials provider chain.");
throw new AmazonClientException(
- "Unable find AWS security credentials. " +
- "Searched JVM system properties, OS env vars, and EC2 instance roles. " +
- "Specify credentials in Tomcat's context.xml file or put them in one of the places mentioned above.");
+ "Unable find AWS security credentials. "
+ + "Searched JVM system properties, OS env vars, and EC2 instance roles. "
+ + "Specify credentials in Tomcat's context.xml file or put them in one of the places mentioned above.");
}
- getContainer().getLogger().debug("Using default AWS credentials provider chain to load credentials");
+ debug("Using default AWS credentials provider chain to load credentials");
return defaultChainProvider;
}
+ /**
+ * @return True if the user has set their AWS credentials either partially or completely in
+ * context.xml. False otherwise
+ */
+ private boolean credentialsExistInContextConfig() {
+ return accessKey != null || secretKey != null;
+ }
+
+ /**
+ * @return True if both the access key and secret key were set in context.xml config. False
+ * otherwise
+ */
+ private boolean credentialsInContextConfigAreValid() {
+ return StringUtils.isNullOrEmpty(accessKey) || StringUtils.isNullOrEmpty(secretKey);
+ }
+
+ private ClientConfiguration initClientConfiguration() {
+ ClientConfiguration clientConfiguration = new ClientConfiguration();
+ clientConfiguration.setUserAgent(USER_AGENT);
+
+ // Attempt to use an explicit proxy configuration
+ if (proxyHost != null || proxyPort != null) {
+ debug("Reading proxy settings from context.xml");
+ if (proxyHost == null || proxyPort == null) {
+ throw new AmazonClientException("Incomplete proxy settings specified in context.xml."
+ + " Both proxy hot and proxy port needs to be specified");
+ }
+ debug("Using proxy host and port from context.xml");
+ clientConfiguration.withProxyHost(proxyHost).withProxyPort(proxyPort);
+ }
+
+ return clientConfiguration;
+ }
+
+ private void initDynamoTable(AmazonDynamoDBClient dynamo) {
+ boolean tableExists = Tables.doesTableExist(dynamo, this.tableName);
+
+ if (!tableExists && !createIfNotExist) {
+ throw new AmazonClientException("Session table '" + tableName + "' does not exist, "
+ + "and automatic table creation has been disabled in context.xml");
+ }
+
+ if (!tableExists) {
+ DynamoUtils.createSessionTable(dynamo, this.tableName, this.readCapacityUnits, this.writeCapacityUnits);
+ }
+
+ Tables.waitForTableToBecomeActive(dynamo, this.tableName);
+ }
+
+ private DynamoSessionStorage createSessionStorage(AmazonDynamoDBClient dynamoClient) {
+ DynamoDBMapper dynamoMapper = DynamoUtils.createDynamoMapper(dynamoClient, tableName);
+ return new DynamoSessionStorage(dynamoMapper, getSessionConverter());
+ }
+
+ private SessionConverter getSessionConverter() {
+ ClassLoader classLoader = getAssociatedContext().getLoader().getClassLoader();
+ LegacyTomcatSessionConverter legacyConverter = new LegacyTomcatSessionConverter(this, classLoader,
+ maxInactiveInterval);
+ DefaultTomcatSessionConverter defaultConverter = new DefaultTomcatSessionConverter(this, classLoader);
+ // Converter compatible with the legacy schema but can understand new schema
+ return new SessionConverter(TomcatSessionConverterChain.wrap(legacyConverter, defaultConverter),
+ new LegacyDynamoDBSessionItemConverter());
+ }
+
+ /**
+ * To be compatible with Tomcat7 we have to call the getContainer method rather than getContext.
+ * The cast is safe as it only makes sense to use a session manager within the context of a
+ * webapp, the Tomcat 8 version of getContainer just delegates to getContext. When Tomcat7 is no
+ * longer supported this can be changed to getContext
+ *
+ * @return The context this manager is associated with
+ */
+ // TODO Inline this method with getManager().getContext() when Tomcat7 is no longer supported
+ private Context getAssociatedContext() {
+ try {
+ return (Context) getContainer();
+ } catch (ClassCastException e) {
+ logger.fatal("Unable to cast " + getClass().getName() + " to a Context."
+ + " DynamoDB SessionManager can only be used with a Context");
+ throw new IllegalStateException(e);
+ }
+ }
//
// Logger Utility Functions
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java
index c9df083..3b97dc1 100644
--- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java
@@ -14,204 +14,93 @@
*/
package com.amazonaws.services.dynamodb.sessionmanager;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.HashSet;
-import java.util.List;
import java.util.Set;
-import org.apache.catalina.Container;
-import org.apache.catalina.Loader;
import org.apache.catalina.Session;
-import org.apache.catalina.session.StandardSession;
import org.apache.catalina.session.StoreBase;
-import org.apache.catalina.util.CustomObjectInputStream;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
-import com.amazonaws.services.dynamodb.sessionmanager.util.DynamoUtils;
-import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
-import com.amazonaws.services.dynamodbv2.model.DescribeTableRequest;
-import com.amazonaws.services.dynamodbv2.model.TableDescription;
+import com.amazonaws.services.dynamodb.sessionmanager.util.ValidatorUtils;
/**
- * Session store implementation that loads and stores HTTP sessions from Amazon
- * DynamoDB.
+ * Session store implementation that loads and stores HTTP sessions from Amazon DynamoDB.
*/
public class DynamoDBSessionStore extends StoreBase {
- private static final String name = "AmazonDynamoDBSessionStore";
- private static final String info = name + "/1.0";
-
- private AmazonDynamoDBClient dynamo;
- private String sessionTableName;
-
- private Set keys = Collections.synchronizedSet(new HashSet());
- private long keysTimestamp=0;
-
- @Override
- public String getInfo() {
- return info;
- }
-
- @Override
- public String getStoreName() {
- return name;
- }
-
- public void setDynamoClient(AmazonDynamoDBClient dynamo) {
- this.dynamo = dynamo;
- }
-
- public void setSessionTableName(String tableName) {
- this.sessionTableName = tableName;
- }
-
- @Override
- public void clear() throws IOException {
- final Set keysCopy = new HashSet();
- keysCopy.addAll(keys);
-
- new Thread("dynamodb-session-manager-clear") {
- @Override
- public void run() {
- for (String sessionId : keysCopy) {
- remove(sessionId);
- }
- }
- }.start();
-
- }
-
- @Override
- public int getSize() throws IOException {
- TableDescription table = dynamo.describeTable(new DescribeTableRequest().withTableName(sessionTableName))
- .getTable();
- long itemCount = table.getItemCount();
-
- return (int) itemCount;
- }
-
- @Override
- public String[] keys() throws IOException {
- // refresh the keys stored in memory in every hour.
- if(keysTimestamp list=DynamoUtils.loadKeys(dynamo, sessionTableName);
- keys.clear();
- keys.addAll(list);
- keysTimestamp=System.currentTimeMillis();
- }
-
- return keys.toArray(new String[0]);
-
- }
-
- @Override
- public Session load(String id) throws ClassNotFoundException, IOException {
-
- ByteBuffer byteBuffer = DynamoUtils.loadItemBySessionId(dynamo, sessionTableName, id);
- if (byteBuffer == null) {
- keys.remove(id);
- return (null);
- }
-
- if (manager.getContainer().getLogger().isDebugEnabled()) {
- manager.getContainer().getLogger().debug(sm.getString(getStoreName() + ".loading", id, sessionTableName));
- }
-
- ByteArrayInputStream fis = null;
- BufferedInputStream bis = null;
- ObjectInputStream ois = null;
- Loader loader = null;
- ClassLoader classLoader = null;
- try {
- fis = new ByteArrayInputStream(byteBuffer.array());
- bis = new BufferedInputStream(fis);
- Container container = manager.getContainer();
- if (container != null) {
- loader = container.getLoader();
- }
- if (loader != null) {
- classLoader = loader.getClassLoader();
- }
- if (classLoader != null) {
- ois = new CustomObjectInputStream(bis, classLoader);
- } else {
- ois = new ObjectInputStream(bis);
- }
- } catch (Exception e) {
- if (bis != null) {
- try {
- bis.close();
- } catch (IOException f) {
- }
- }
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException f) {
- }
- }
- throw e;
- }
-
- try {
- StandardSession session = (StandardSession) manager.createEmptySession();
- session.readObjectData(ois);
- session.setManager(manager);
- keys.add(id);
- return (session);
- } finally {
- try {
- ois.close();
- } catch (IOException f) {
- }
- }
- }
-
- @Override
- public void save(Session session) throws IOException {
-
- String id = session.getIdInternal();
-
- if (manager.getContainer().getLogger().isDebugEnabled()) {
- manager.getContainer().getLogger()
- .debug(sm.getString(getStoreName() + ".saving", id, sessionTableName));
- }
-
- ByteArrayOutputStream fos = new ByteArrayOutputStream();
- ObjectOutputStream oos = null;
- try {
- oos = new ObjectOutputStream(new BufferedOutputStream(fos));
- } catch (IOException e) {
- try {
- fos.close();
- } catch (IOException f) {
- }
- throw e;
- }
-
- try {
- ((StandardSession) session).writeObjectData(oos);
- } finally {
- oos.close();
- }
- DynamoUtils.storeSession(dynamo, sessionTableName, id, ByteBuffer.wrap(fos.toByteArray()));
- keys.add(id);
- }
-
- @Override
- public void remove(String id) {
- if (manager.getContainer().getLogger().isDebugEnabled()) {
- manager.getContainer().getLogger().debug(sm.getString(getStoreName() + ".removing", id, sessionTableName));
- }
- DynamoUtils.deleteSession(dynamo, sessionTableName, id);
- keys.remove(id);
- }
-}
\ No newline at end of file
+ private static final Log logger = LogFactory.getLog(DynamoDBSessionStore.class);
+ private static final String name = "AmazonDynamoDBSessionStore";
+ private static final String info = name + "/1.0";
+
+ private final Set sessionIds = Collections.synchronizedSet(new HashSet());
+ private final DynamoSessionStorage sessionStorage;
+
+ public DynamoDBSessionStore(DynamoSessionStorage sessionStorage) {
+ ValidatorUtils.nonNull(sessionStorage, "SessionStorage");
+ this.sessionStorage = sessionStorage;
+ }
+
+ @Override
+ public String getInfo() {
+ return info;
+ }
+
+ @Override
+ public String getStoreName() {
+ return name;
+ }
+
+ @Override
+ public void clear() throws IOException {
+ synchronized (sessionIds) {
+ final Set sessionsToDelete = new HashSet(sessionIds);
+ new Thread("dynamodb-session-manager-clear") {
+ @Override
+ public void run() {
+ for (String sessionId : sessionsToDelete) {
+ sessionStorage.deleteSession(sessionId);
+ }
+ }
+ }.start();
+ sessionIds.clear();
+ }
+ }
+
+ @Override
+ public int getSize() throws IOException {
+ return sessionStorage.count();
+ }
+
+ @Override
+ public String[] keys() throws IOException {
+ return sessionIds.toArray(new String[0]);
+ }
+
+ @Override
+ public Session load(String id) throws ClassNotFoundException, IOException {
+ Session session = sessionStorage.loadSession(id);
+ if (session == null) {
+ logger.warn("Unable to load session with id " + id);
+ return null;
+ }
+
+ sessionIds.add(id);
+ return session;
+ }
+
+ @Override
+ public void save(Session session) throws IOException {
+ sessionStorage.saveSession(session);
+ sessionIds.add(session.getId());
+ }
+
+ @Override
+ public void remove(String id) throws IOException {
+ sessionStorage.deleteSession(id);
+ sessionIds.remove(id);
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoSessionItem.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoSessionItem.java
new file mode 100644
index 0000000..23fd5c8
--- /dev/null
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoSessionItem.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.dynamodb.sessionmanager;
+
+import java.nio.ByteBuffer;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
+
+@DynamoDBTable(tableName = DynamoDBSessionManager.DEFAULT_TABLE_NAME)
+public class DynamoSessionItem {
+
+ public static final String SESSION_ID_ATTRIBUTE_NAME = "sessionId";
+ public static final String SESSION_DATA_ATTRIBUTE_NAME = "sessionData";
+ public static final String CREATED_AT_ATTRIBUTE_NAME = "createdAt";
+ public static final String LAST_UPDATED_AT_ATTRIBUTE_NAME = "lastUpdatedAt";
+
+ private String sessionId;
+ private ByteBuffer sessionData;
+
+ // Legacy item attributes
+ private long lastUpdatedTime;
+ private long createdTime;
+
+ public DynamoSessionItem() {
+ }
+
+ public DynamoSessionItem(String id) {
+ this.sessionId = id;
+ }
+
+ @DynamoDBHashKey(attributeName = SESSION_ID_ATTRIBUTE_NAME)
+ public String getSessionId() {
+ return sessionId;
+ }
+
+ public void setSessionId(String sessionId) {
+ this.sessionId = sessionId;
+ }
+
+ @DynamoDBAttribute(attributeName = SESSION_DATA_ATTRIBUTE_NAME)
+ public ByteBuffer getSessionData() {
+ return sessionData;
+ }
+
+ public void setSessionData(ByteBuffer sessionData) {
+ this.sessionData = sessionData;
+ }
+
+ /**
+ * @deprecated Part of the legacy item format. Will be removed in a later version
+ */
+ @Deprecated
+ @DynamoDBAttribute(attributeName = LAST_UPDATED_AT_ATTRIBUTE_NAME)
+ public long getLastUpdatedTime() {
+ return lastUpdatedTime;
+ }
+
+ /**
+ * @deprecated Part of the legacy item format. Will be removed in a later version
+ */
+ @Deprecated
+ public void setLastUpdatedTime(long lastUpdatedTime) {
+ this.lastUpdatedTime = lastUpdatedTime;
+ }
+
+ /**
+ * @deprecated Part of the legacy item format. Will be removed in a later version
+ */
+ @Deprecated
+ @DynamoDBAttribute(attributeName = CREATED_AT_ATTRIBUTE_NAME)
+ public long getCreatedTime() {
+ return createdTime;
+ }
+
+ /**
+ * @deprecated Part of the legacy item format. Will be removed in a later version
+ */
+ @Deprecated
+ public void setCreatedTime(long createdTime) {
+ this.createdTime = createdTime;
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoSessionStorage.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoSessionStorage.java
new file mode 100644
index 0000000..2f4760f
--- /dev/null
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoSessionStorage.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.dynamodb.sessionmanager;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.apache.catalina.Session;
+
+import com.amazonaws.services.dynamodb.sessionmanager.converters.SessionConverter;
+import com.amazonaws.services.dynamodb.sessionmanager.util.ValidatorUtils;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression;
+import com.amazonaws.services.dynamodbv2.datamodeling.PaginatedScanList;
+
+public class DynamoSessionStorage {
+
+ private final DynamoDBMapper mapper;
+ private final SessionConverter sessionConverter;
+
+ public DynamoSessionStorage(DynamoDBMapper dynamoMapper, SessionConverter sessionConverter) {
+ ValidatorUtils.nonNull(dynamoMapper, "DynamoDBMapper");
+ ValidatorUtils.nonNull(sessionConverter, "SessionConverter");
+ this.mapper = dynamoMapper;
+ this.sessionConverter = sessionConverter;
+ }
+
+ public int count() {
+ return mapper.count(DynamoSessionItem.class, new DynamoDBScanExpression());
+ }
+
+ public Session loadSession(String sessionId) {
+ DynamoSessionItem sessionItem = mapper.load(new DynamoSessionItem(sessionId));
+ if (sessionItem != null) {
+ return sessionConverter.toSession(sessionItem);
+ } else {
+ return null;
+ }
+ }
+
+ public void deleteSession(String sessionId) {
+ mapper.delete(new DynamoSessionItem(sessionId));
+ }
+
+ public void saveSession(Session session) {
+ mapper.save(sessionConverter.toSessionItem(session));
+ }
+
+ public Iterable listSessions() {
+ PaginatedScanList sessions = mapper.scan(DynamoSessionItem.class,
+ new DynamoDBScanExpression());
+ return new SessionConverterIterable(sessions);
+ }
+
+ private class SessionConverterIterable implements Iterable {
+
+ private final Iterable sessionIterable;
+
+ private SessionConverterIterable(Iterable sessionIterable) {
+ this.sessionIterable = sessionIterable;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new SessionConverterIterator(getIteratorSafe(sessionIterable));
+ }
+
+ /**
+ * Returns either the Iterator for a given Iterable or an empty Iterator but not null.
+ */
+ private Iterator getIteratorSafe(Iterable iterable) {
+ if (iterable != null) {
+ return iterable.iterator();
+ } else {
+ return Collections. emptyList().iterator();
+ }
+ }
+
+ }
+
+ /**
+ * Custom iterator to convert a {@link DynamoSessionItem} to a Tomcat {@link Session} before
+ * returning it
+ */
+ private class SessionConverterIterator implements Iterator {
+
+ private final Iterator sessionItemterator;
+
+ private SessionConverterIterator(Iterator sessionItemIterator) {
+ this.sessionItemterator = sessionItemIterator;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return sessionItemterator.hasNext();
+ }
+
+ @Override
+ public Session next() {
+ return sessionConverter.toSession(sessionItemterator.next());
+ }
+
+ @Override
+ public void remove() {
+ sessionItemterator.remove();
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaper.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaper.java
new file mode 100644
index 0000000..98e157d
--- /dev/null
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaper.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.dynamodb.sessionmanager;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.catalina.Session;
+
+import com.amazonaws.services.dynamodb.sessionmanager.util.ValidatorUtils;
+
+/**
+ * Scans Session table and deletes any sessions that have expired
+ */
+public class ExpiredSessionReaper implements Runnable {
+
+ private final DynamoSessionStorage sessionStorage;
+
+ public ExpiredSessionReaper(DynamoSessionStorage sessionStorage) {
+ ValidatorUtils.nonNull(sessionStorage, "SessionStorage");
+ this.sessionStorage = sessionStorage;
+ }
+
+ /**
+ * Scans the session table for expired sessions and deletes them.
+ */
+ @Override
+ public void run() {
+ Iterable sessions = sessionStorage.listSessions();
+ for (Session session : sessions) {
+ if (ExpiredSessionReaper.isExpired(session)) {
+ sessionStorage.deleteSession(session.getId());
+ }
+ }
+ }
+
+ public static boolean isExpired(Session session) {
+ if (canSessionExpire(session)) {
+ return session.getLastAccessedTimeInternal() < getInactiveCutoffTime(session);
+ }
+ return false;
+ }
+
+ /**
+ * Sessions with a negative max inactive time never expire
+ */
+ private static boolean canSessionExpire(Session session) {
+ return session.getMaxInactiveInterval() > 0;
+ }
+
+ /**
+ * Any sessions whose access time is older than the cutoff time are considered inactive, those
+ * with access time after the cutoff time are still active
+ */
+ private static long getInactiveCutoffTime(Session session) {
+ return System.currentTimeMillis()
+ - TimeUnit.MILLISECONDS.convert(session.getMaxInactiveInterval(), TimeUnit.SECONDS);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperExecutor.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperExecutor.java
new file mode 100644
index 0000000..8f880fc
--- /dev/null
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperExecutor.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.dynamodb.sessionmanager;
+
+import java.util.Random;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A background process to periodically scan and remove any expired session data from the session
+ * table in Amazon DynamoDB.
+ */
+public class ExpiredSessionReaperExecutor {
+
+ private static final int REAP_FREQUENCY_HOURS = 12;
+ private static final int MAX_JITTER_HOURS = 5;
+ private static final String THREAD_NAME = "dynamo-session-manager-expired-sesion-reaper";
+
+ private final ScheduledThreadPoolExecutor executor;
+
+ public ExpiredSessionReaperExecutor(Runnable expiredSessionRunnable) {
+ int initialDelay = new Random().nextInt(MAX_JITTER_HOURS) + 1;
+ executor = new ScheduledThreadPoolExecutor(1, new ExpiredSessionReaperThreadFactory());
+ executor.scheduleAtFixedRate(expiredSessionRunnable, initialDelay, REAP_FREQUENCY_HOURS, TimeUnit.HOURS);
+ }
+
+ /**
+ * Shuts down the expired session reaper.
+ */
+ public void shutdown() {
+ executor.shutdown();
+ }
+
+ /**
+ * ThreadFactory for creating the daemon reaper thread.
+ */
+ private final class ExpiredSessionReaperThreadFactory implements ThreadFactory {
+ @Override
+ public Thread newThread(Runnable runnable) {
+ Thread thread = new Thread(runnable);
+ thread.setDaemon(true);
+ thread.setName(THREAD_NAME);
+ return thread;
+ }
+ }
+}
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultDynamoSessionItemConverter.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultDynamoSessionItemConverter.java
new file mode 100644
index 0000000..913aea3
--- /dev/null
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultDynamoSessionItemConverter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.dynamodb.sessionmanager.converters;
+
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectOutputStream;
+import java.nio.ByteBuffer;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.session.StandardSession;
+
+import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem;
+import com.amazonaws.util.IOUtils;
+
+public class DefaultDynamoSessionItemConverter implements DynamoSessionItemConverter {
+
+ @Override
+ public DynamoSessionItem toSessionItem(Session session) {
+ ObjectOutputStream oos = null;
+ try {
+ ByteArrayOutputStream fos = new ByteArrayOutputStream();
+ oos = new ObjectOutputStream(fos);
+ ((StandardSession) session).writeObjectData(oos);
+ oos.close();
+ DynamoSessionItem sessionItem = new DynamoSessionItem(session.getIdInternal());
+ sessionItem.setSessionData(ByteBuffer.wrap(fos.toByteArray()));
+ return sessionItem;
+ } catch (Exception e) {
+ IOUtils.closeQuietly(oos, null);
+ throw new SessionConversionException("Unable to convert Tomcat Session into Dynamo storage representation",
+ e);
+ }
+ }
+}
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultTomcatSessionConverter.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultTomcatSessionConverter.java
new file mode 100644
index 0000000..b16e095
--- /dev/null
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultTomcatSessionConverter.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.dynamodb.sessionmanager.converters;
+
+import java.io.ByteArrayInputStream;
+import java.io.ObjectInputStream;
+
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.catalina.session.StandardSession;
+import org.apache.catalina.util.CustomObjectInputStream;
+
+import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem;
+import com.amazonaws.services.dynamodb.sessionmanager.util.ValidatorUtils;
+import com.amazonaws.util.IOUtils;
+
+public class DefaultTomcatSessionConverter implements TomcatSessionConverter {
+
+ private final ClassLoader classLoader;
+ private final Manager manager;
+
+ public DefaultTomcatSessionConverter(Manager manager, ClassLoader classLoader) {
+ ValidatorUtils.nonNull(manager, "Manager");
+ ValidatorUtils.nonNull(classLoader, "ClassLoader");
+ this.classLoader = classLoader;
+ this.manager = manager;
+ }
+
+ @Override
+ public Session toSession(DynamoSessionItem sessionItem) {
+ ObjectInputStream ois = null;
+ try {
+ ByteArrayInputStream fis = new ByteArrayInputStream(sessionItem.getSessionData().array());
+ ois = new CustomObjectInputStream(fis, classLoader);
+
+ StandardSession session = new StandardSession(manager);
+ session.readObjectData(ois);
+ return session;
+ } catch (Exception e) {
+ throw new SessionConversionException("Unable to convert Dynamo storage representation to a Tomcat Session",
+ e);
+ } finally {
+ IOUtils.closeQuietly(ois, null);
+ }
+ }
+
+}
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DynamoSessionItemConverter.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DynamoSessionItemConverter.java
new file mode 100644
index 0000000..28d4855
--- /dev/null
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DynamoSessionItemConverter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.dynamodb.sessionmanager.converters;
+
+import org.apache.catalina.Session;
+
+import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem;
+
+public interface DynamoSessionItemConverter {
+
+ /**
+ * Converts the Tomcat {@link Session} into a {@link DynamoSessionItem} to be persisted to
+ * DynamoDB
+ */
+ DynamoSessionItem toSessionItem(Session session);
+}
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacyDynamoDBSessionItemConverter.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacyDynamoDBSessionItemConverter.java
new file mode 100644
index 0000000..4e2f3f3
--- /dev/null
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacyDynamoDBSessionItemConverter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.dynamodb.sessionmanager.converters;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.nio.ByteBuffer;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.Session;
+
+import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem;
+
+public class LegacyDynamoDBSessionItemConverter implements DynamoSessionItemConverter {
+
+ @Override
+ public DynamoSessionItem toSessionItem(Session session) {
+ try {
+ DynamoSessionItem sessionItem = new DynamoSessionItem(session.getIdInternal());
+ sessionItem.setCreatedTime(session.getCreationTimeInternal());
+ sessionItem.setLastUpdatedTime(session.getLastAccessedTimeInternal());
+ sessionItem.setSessionData(sessionDataToByteBuffer(session));
+ return sessionItem;
+ } catch (Exception e) {
+ throw new SessionConversionException("Unable to convert Tomcat Session into Dynamo storage representation",
+ e);
+ }
+ }
+
+ private static ByteBuffer sessionDataToByteBuffer(Session session) throws IOException {
+ Map getterReturnResult = sessionDataToMap(session);
+ return objectToByteBuffer(getterReturnResult);
+ }
+
+ private static Map sessionDataToMap(Session session) {
+ HttpSession httpSession = session.getSession();
+ Map sessionAttributes = new HashMap();
+ Enumeration attributeNames = httpSession.getAttributeNames();
+ while (attributeNames.hasMoreElements()) {
+ String attributeName = attributeNames.nextElement();
+ Object attributeValue = httpSession.getAttribute(attributeName);
+ sessionAttributes.put(attributeName, attributeValue);
+ }
+ return sessionAttributes;
+ }
+
+ static ByteBuffer objectToByteBuffer(Object object) throws IOException {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+ objectOutputStream.writeObject(object);
+ objectOutputStream.close();
+ byte[] byteArray = byteArrayOutputStream.toByteArray();
+ return ByteBuffer.wrap(byteArray);
+ }
+}
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacyTomcatSessionConverter.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacyTomcatSessionConverter.java
new file mode 100644
index 0000000..07787f0
--- /dev/null
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacyTomcatSessionConverter.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.dynamodb.sessionmanager.converters;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.catalina.session.StandardSession;
+import org.apache.catalina.util.CustomObjectInputStream;
+
+import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem;
+import com.amazonaws.services.dynamodb.sessionmanager.util.ValidatorUtils;
+import com.amazonaws.util.BinaryUtils;
+import com.amazonaws.util.IOUtils;
+
+public class LegacyTomcatSessionConverter implements TomcatSessionConverter {
+
+ private final Manager manager;
+ private final ClassLoader classLoader;
+ private final int maxInactiveInterval;
+
+ public LegacyTomcatSessionConverter(Manager manager, ClassLoader classLoader, int maxInactiveInterval) {
+ ValidatorUtils.nonNull(manager, "Manager");
+ ValidatorUtils.nonNull(classLoader, "ClassLoader");
+ this.manager = manager;
+ this.classLoader = classLoader;
+ this.maxInactiveInterval = maxInactiveInterval;
+ }
+
+ @Override
+ public Session toSession(DynamoSessionItem sessionItem) {
+ try {
+ LegacySession session = new LegacySession(null);
+ session.setValid(true);
+ session.setId(sessionItem.getSessionId(), false);
+ session.setCreationTime(sessionItem.getCreatedTime());
+ session.setLastAccessedTime(sessionItem.getLastUpdatedTime());
+ session.setMaxInactiveInterval(maxInactiveInterval);
+ session.setSessionAttributes(unmarshallSessionData(sessionItem));
+ session.setManager(manager);
+ return session;
+ } catch (Exception e) {
+ throw new SessionConversionException("Unable to convert Dynamo storage representation to a Tomcat Session",
+ e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map unmarshallSessionData(DynamoSessionItem sessionItem) throws IOException,
+ ClassNotFoundException {
+ ByteBuffer rawSessionData = sessionItem.getSessionData();
+
+ Object marshalledSessionData;
+ ObjectInputStream objectInputStream = null;
+ try {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(BinaryUtils.copyAllBytesFrom(rawSessionData));
+ objectInputStream = new CustomObjectInputStream(inputStream, classLoader);
+ marshalledSessionData = objectInputStream.readObject();
+ } finally {
+ IOUtils.closeQuietly(objectInputStream, null);
+ }
+ if (!(marshalledSessionData instanceof Map, ?>)) {
+ throw new SessionConversionException("Unable to unmarshall session attributes from DynamoDB store");
+ }
+ return (Map) marshalledSessionData;
+ }
+
+ /**
+ * Subclassed standard session to allow setting lastAccessedTime through means other than
+ * readObject
+ */
+ public static class LegacySession extends StandardSession {
+
+ private static final long serialVersionUID = 9163946735192227235L;
+
+ public LegacySession(Manager manager) {
+ super(manager);
+ }
+
+ public void setLastAccessedTime(long lastAccessedTime) {
+ this.lastAccessedTime = lastAccessedTime;
+ this.thisAccessedTime = lastAccessedTime;
+ }
+
+ public void setSessionAttributes(Map attributes) {
+ for (Entry attribute : attributes.entrySet()) {
+ this.setAttribute(attribute.getKey(), attribute.getValue(), false);
+ }
+ }
+
+ }
+}
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/SessionConversionException.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/SessionConversionException.java
new file mode 100644
index 0000000..9545c64
--- /dev/null
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/SessionConversionException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.dynamodb.sessionmanager.converters;
+
+public class SessionConversionException extends RuntimeException {
+
+ private static final long serialVersionUID = -1123132762994997791L;
+
+ public SessionConversionException(String message) {
+ super(message);
+ }
+
+ public SessionConversionException(String message, Throwable t) {
+ super(message, t);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/SessionConverter.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/SessionConverter.java
new file mode 100644
index 0000000..07ad012
--- /dev/null
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/SessionConverter.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.dynamodb.sessionmanager.converters;
+
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+
+import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem;
+import com.amazonaws.services.dynamodb.sessionmanager.util.ValidatorUtils;
+
+public final class SessionConverter implements TomcatSessionConverter, DynamoSessionItemConverter {
+
+ private final TomcatSessionConverter fromDynamo;
+ private final DynamoSessionItemConverter toDynamo;
+
+ public SessionConverter(TomcatSessionConverter fromDynamo, DynamoSessionItemConverter toDynamo) {
+ ValidatorUtils.nonNull(fromDynamo, "TomcatSessionConverter");
+ ValidatorUtils.nonNull(toDynamo, "DynamoSessionItemConverter");
+ this.fromDynamo = fromDynamo;
+ this.toDynamo = toDynamo;
+ }
+
+ @Override
+ public DynamoSessionItem toSessionItem(Session session) {
+ return toDynamo.toSessionItem(session);
+ }
+
+ @Override
+ public Session toSession(DynamoSessionItem dynamoSessionItem) {
+ return fromDynamo.toSession(dynamoSessionItem);
+ }
+
+ /**
+ * Factory method to create a SessionConverter with the default implementation of
+ * TomcatSessionConverter and DynamoSessionConverter
+ */
+ public static SessionConverter createDefaultSessionConverter(Manager manager, ClassLoader classLoader) {
+ return new SessionConverter(new DefaultTomcatSessionConverter(manager, classLoader),
+ new DefaultDynamoSessionItemConverter());
+ }
+
+ /**
+ * Factory method to create a SessionConverter with the legacy implementation of
+ * TomcatSessionConverter and DynamoSessionConverter
+ */
+ public static SessionConverter createLegacySessionConverter(Manager manager,
+ ClassLoader classLoader,
+ int maxInactiveInterval) {
+ return new SessionConverter(new LegacyTomcatSessionConverter(manager, classLoader, maxInactiveInterval),
+ new LegacyDynamoDBSessionItemConverter());
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverter.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverter.java
new file mode 100644
index 0000000..6d03b82
--- /dev/null
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverter.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.dynamodb.sessionmanager.converters;
+
+import org.apache.catalina.Session;
+
+import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem;
+
+public interface TomcatSessionConverter {
+
+ /**
+ * Converts a {@link DynamoSessionItem} into a Tomcat {@link Session}
+ */
+ Session toSession(DynamoSessionItem sessionItem);
+}
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChain.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChain.java
new file mode 100644
index 0000000..8838710
--- /dev/null
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChain.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.dynamodb.sessionmanager.converters;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.catalina.Session;
+
+import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem;
+
+public class TomcatSessionConverterChain implements TomcatSessionConverter {
+
+ private final List tomcatSessionCoverters;
+
+ private TomcatSessionConverterChain(List tomcatSessionConverters) {
+ this.tomcatSessionCoverters = tomcatSessionConverters;
+ }
+
+ @Override
+ public Session toSession(DynamoSessionItem sessionItem) {
+ for (TomcatSessionConverter converter : tomcatSessionCoverters) {
+ try {
+ return converter.toSession(sessionItem);
+ } catch (SessionConversionException e) {
+ // Try next converter in chain
+ }
+ }
+ throw new SessionConversionException(
+ "Unable to convert Dynamo storage representation to a Tomcat Session with any converter provided");
+ }
+
+ public static TomcatSessionConverter wrap(TomcatSessionConverter... converters) {
+ return new TomcatSessionConverterChain(Arrays.asList(asArray(converters)));
+ }
+
+ private static TomcatSessionConverter[] asArray(TomcatSessionConverter[] converters) {
+ return converters;
+ }
+}
diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/util/DynamoUtils.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/util/DynamoUtils.java
index ae072c4..54b7423 100644
--- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/util/DynamoUtils.java
+++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/util/DynamoUtils.java
@@ -14,184 +14,43 @@
*/
package com.amazonaws.services.dynamodb.sessionmanager.util;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import com.amazonaws.AmazonClientException;
-import com.amazonaws.AmazonServiceException;
-import com.amazonaws.AmazonWebServiceRequest;
-import com.amazonaws.services.dynamodb.sessionmanager.DynamoDBSessionManager;
-import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
+import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig.TableNameOverride;
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
-import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
-import com.amazonaws.services.dynamodbv2.model.DeleteItemRequest;
-import com.amazonaws.services.dynamodbv2.model.DescribeTableRequest;
-import com.amazonaws.services.dynamodbv2.model.GetItemRequest;
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
-import com.amazonaws.services.dynamodbv2.model.PutItemRequest;
import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
-import com.amazonaws.services.dynamodbv2.model.ScanRequest;
-import com.amazonaws.services.dynamodbv2.model.ScanResult;
-import com.amazonaws.services.dynamodbv2.model.Select;
-import com.amazonaws.services.dynamodbv2.model.TableDescription;
-import com.amazonaws.services.dynamodbv2.model.TableStatus;
-/**
- * Utilities for working with Amazon DynamoDB for session management.
- */
public class DynamoUtils {
- public static final String SESSION_ID_KEY = "sessionId";
- public static final String SESSION_DATA_ATTRIBUTE = "sessionData";
-
- public static List loadKeys(AmazonDynamoDB dynamo, String tableName) {
- ScanRequest request = new ScanRequest(tableName);
- request.setSelect(Select.SPECIFIC_ATTRIBUTES);
- request.withAttributesToGet(SESSION_ID_KEY);
-
- ArrayList list = new ArrayList();
- ScanResult scanResult = null;
- do {
- if (scanResult != null)
- request.setExclusiveStartKey(scanResult.getLastEvaluatedKey());
-
- scanResult = dynamo.scan(request);
- List