From 6f11a1160238a052d3e8877f8fe2827f4ff72efb Mon Sep 17 00:00:00 2001 From: Andrew Walters Date: Tue, 4 Nov 2014 16:26:37 +0000 Subject: [PATCH 01/23] Migrated to Java7 & Tomcat8 --- pom.xml | 2 +- .../DynamoDBSessionManager.java | 19 +++++++----------- .../sessionmanager/DynamoDBSessionStore.java | 20 +++++-------------- .../sessionmanager/util/DynamoUtils.java | 9 ++++----- 4 files changed, 17 insertions(+), 33 deletions(-) diff --git a/pom.xml b/pom.xml index 12b393f..1479b47 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ org.apache.tomcat tomcat-catalina - 7.0.42 + 8.0.14 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 136c340..a53bb8f 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java @@ -70,11 +70,6 @@ public DynamoDBSessionManager() { setMaxIdleBackup(30); // 30 seconds } - @Override - public String getInfo() { - return info; - } - @Override public String getName() { return name; @@ -130,7 +125,7 @@ protected void initInternal() throws LifecycleException { this.setDistributable(true); // Grab the container's logger - logger = getContainer().getLogger(); + logger = getContext().getLogger(); AWSCredentialsProvider credentialsProvider = initCredentials(); AmazonDynamoDBClient dynamo = new AmazonDynamoDBClient(credentialsProvider); @@ -169,20 +164,20 @@ private void initDynamoTable(AmazonDynamoDBClient dynamo) { 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"); + getContext().getLogger().debug("Reading security credentials from context.xml"); if (accessKey == null || secretKey == null) { 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"); + getContext().getLogger().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); + getContext().getLogger().debug("Reading security credentials from properties file: " + credentialsFile); PropertiesCredentials credentials = new PropertiesCredentials(credentialsFile); - getContainer().getLogger().debug("Using AWS credentials from file: " + credentialsFile); + getContext().getLogger().debug("Using AWS credentials from file: " + credentialsFile); return new StaticCredentialsProvider(credentials); } catch (Exception e) { throw new AmazonClientException( @@ -193,13 +188,13 @@ private AWSCredentialsProvider initCredentials() { // 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."); + getContext().getLogger().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."); } - getContainer().getLogger().debug("Using default AWS credentials provider chain to load credentials"); + getContext().getLogger().debug("Using default AWS credentials provider chain to load credentials"); return defaultChainProvider; } 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 091fbf9..6724f8f 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java @@ -23,6 +23,7 @@ import java.util.Set; import org.apache.catalina.Container; +import org.apache.catalina.Context; import org.apache.catalina.Session; import org.apache.catalina.session.StandardSession; import org.apache.catalina.session.StoreBase; @@ -48,12 +49,6 @@ public class DynamoDBSessionStore extends StoreBase { private Set keys = Collections.synchronizedSet(new HashSet()); - - @Override - public String getInfo() { - return info; - } - @Override public String getStoreName() { return name; @@ -70,7 +65,7 @@ public void setSessionTableName(String tableName) { @Override public void clear() throws IOException { - final Set keysCopy = new HashSet(); + final Set keysCopy = new HashSet<>(); keysCopy.addAll(keys); new Thread("dynamodb-session-manager-clear") { @@ -96,7 +91,7 @@ public int getSize() throws IOException { @Override public String[] keys() throws IOException { - return keys.toArray(new String[0]); + return keys.toArray(new String[keys.size()]); } @Override @@ -116,14 +111,9 @@ public Session load(String id) throws ClassNotFoundException, IOException { ByteArrayInputStream inputStream = new ByteArrayInputStream(byteBuffer.array()); Object readObject; - CustomObjectInputStream objectInputStream = null; - try { - Container webapp = getManager().getContainer(); - objectInputStream = new CustomObjectInputStream(inputStream, webapp.getLoader().getClassLoader()); + try(CustomObjectInputStream objectInputStream = new CustomObjectInputStream(inputStream, getManager().getContext().getLoader().getClassLoader())) { readObject = objectInputStream.readObject(); - } finally { - try { objectInputStream.close(); } catch (Exception e) {} } if (readObject instanceof Map) { @@ -154,4 +144,4 @@ public void remove(String id) throws IOException { DynamoUtils.deleteSession(dynamo, sessionTableName, id); keys.remove(id); } -} \ No newline at end of file +} 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 dd6a8ba..41fc838 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 @@ -84,7 +84,7 @@ public static void deleteSession(AmazonDynamoDB dynamo, String tableName, String } public static void storeSession(AmazonDynamoDB dynamo, String tableName, Session session) throws IOException { - Map sessionAttributes = new HashMap(); + Map sessionAttributes = new HashMap<>(); HttpSession httpSession = session.getSession(); Enumeration attributeNames = httpSession.getAttributeNames(); @@ -123,8 +123,7 @@ public static boolean doesTableExist(AmazonDynamoDBClient dynamo, String tableNa addClientMarker(request); TableDescription table = dynamo.describeTable(request).getTable(); - if (table == null) return false; - else return true; + return table != null; } catch (AmazonServiceException ase) { if (ase.getErrorCode().equalsIgnoreCase("ResourceNotFoundException")) return false; else throw ase; @@ -145,7 +144,7 @@ public static void waitForTableToBecomeActive(AmazonDynamoDBClient dynamo, Strin String tableStatus = tableDescription.getTableStatus(); if (tableStatus.equals(TableStatus.ACTIVE.toString())) return; } catch (AmazonServiceException ase) { - if (ase.getErrorCode().equalsIgnoreCase("ResourceNotFoundException") == false) + if (!ase.getErrorCode().equalsIgnoreCase("ResourceNotFoundException")) throw ase; } @@ -185,6 +184,6 @@ public static void addClientMarker(AmazonWebServiceRequest request) { } private static Map newAttributeValueMap() { - return new HashMap(); + return new HashMap<>(); } } From 09d9de96c30be4302c339061d17885db15ab2811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A8n-Dario=20Casta=C3=B1=C3=A9?= Date: Fri, 12 Dec 2014 10:18:01 +0100 Subject: [PATCH 02/23] Supressing Eclipse warnings and ignoring its generated files. --- .gitignore | 5 + .../DynamoDBSessionManager.java | 3 +- .../sessionmanager/DynamoDBSessionStore.java | 221 +++++++++--------- 3 files changed, 123 insertions(+), 106 deletions(-) diff --git a/.gitignore b/.gitignore index 4169b78..e75c406 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,8 @@ *.jar *.war *.ear +.classpath +.project +.settings/org.eclipse.core.resources.prefs +.settings/org.eclipse.jdt.core.prefs +.settings/org.eclipse.m2e.core.prefs 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 a53bb8f..77aae0b 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java @@ -39,7 +39,8 @@ public class DynamoDBSessionManager extends PersistentManagerBase { private static final String DEFAULT_TABLE_NAME = "Tomcat_SessionState"; private static final String name = "AmazonDynamoDBSessionManager"; - private static final String info = name + "/1.0"; + @SuppressWarnings("unused") + private static final String info = name + "/1.0"; private String regionId = "us-east-1"; private String endpoint; 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 6724f8f..5ce2110 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java @@ -22,8 +22,6 @@ import java.util.Map; import java.util.Set; -import org.apache.catalina.Container; -import org.apache.catalina.Context; import org.apache.catalina.Session; import org.apache.catalina.session.StandardSession; import org.apache.catalina.session.StoreBase; @@ -41,107 +39,120 @@ */ 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()); - - @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) { - DynamoUtils.deleteSession(dynamo, sessionTableName, sessionId); - } - } - }.start(); - - keys.clear(); - } - - @Override - public int getSize() throws IOException { - // The item count from describeTable is updated every ~6 hours - TableDescription table = dynamo.describeTable(new DescribeTableRequest().withTableName(sessionTableName)).getTable(); - long itemCount = table.getItemCount(); - - return (int)itemCount; - } - - @Override - public String[] keys() throws IOException { - return keys.toArray(new String[keys.size()]); - } - - @Override - public Session load(String id) throws ClassNotFoundException, IOException { - Map item = DynamoUtils.loadItemBySessionId(dynamo, sessionTableName, id); - if (item == null || !item.containsKey(SessionTableAttributes.SESSION_ID_KEY) || !item.containsKey(SessionTableAttributes.SESSION_DATA_ATTRIBUTE)) { - DynamoDBSessionManager.warn("Unable to load session attributes for session " + id); - return null; - } - - - Session session = getManager().createSession(id); - session.setCreationTime(Long.parseLong(item.get(SessionTableAttributes.CREATED_AT_ATTRIBUTE).getN())); - - - ByteBuffer byteBuffer = item.get(SessionTableAttributes.SESSION_DATA_ATTRIBUTE).getB(); - ByteArrayInputStream inputStream = new ByteArrayInputStream(byteBuffer.array()); - - Object readObject; - - try(CustomObjectInputStream objectInputStream = new CustomObjectInputStream(inputStream, getManager().getContext().getLoader().getClassLoader())) { - readObject = objectInputStream.readObject(); - } - - if (readObject instanceof Map) { - Map sessionAttributeMap = (Map)readObject; - - for (String s : sessionAttributeMap.keySet()) { - ((StandardSession)session).setAttribute(s, sessionAttributeMap.get(s)); - } - } else { - throw new RuntimeException("Error: Unable to unmarshall session attributes from DynamoDB store"); - } - - - keys.add(id); - manager.add(session); - - return session; - } - - @Override - public void save(Session session) throws IOException { - DynamoUtils.storeSession(dynamo, sessionTableName, session); - keys.add(session.getId()); - } - - @Override - public void remove(String id) throws IOException { - DynamoUtils.deleteSession(dynamo, sessionTableName, id); - keys.remove(id); - } + private static final String name = "AmazonDynamoDBSessionStore"; + @SuppressWarnings("unused") + private static final String info = name + "/1.0"; + + private AmazonDynamoDBClient dynamo; + private String sessionTableName; + + private Set keys = Collections + .synchronizedSet(new HashSet()); + + @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) { + DynamoUtils.deleteSession(dynamo, sessionTableName, + sessionId); + } + } + }.start(); + + keys.clear(); + } + + @Override + public int getSize() throws IOException { + // The item count from describeTable is updated every ~6 hours + TableDescription table = dynamo.describeTable( + new DescribeTableRequest().withTableName(sessionTableName)) + .getTable(); + long itemCount = table.getItemCount(); + + return (int) itemCount; + } + + @Override + public String[] keys() throws IOException { + return keys.toArray(new String[keys.size()]); + } + + @Override + public Session load(String id) throws ClassNotFoundException, IOException { + Map item = DynamoUtils.loadItemBySessionId( + dynamo, sessionTableName, id); + if (item == null + || !item.containsKey(SessionTableAttributes.SESSION_ID_KEY) + || !item.containsKey(SessionTableAttributes.SESSION_DATA_ATTRIBUTE)) { + DynamoDBSessionManager + .warn("Unable to load session attributes for session " + id); + return null; + } + + Session session = getManager().createSession(id); + session.setCreationTime(Long.parseLong(item.get( + SessionTableAttributes.CREATED_AT_ATTRIBUTE).getN())); + + ByteBuffer byteBuffer = item.get( + SessionTableAttributes.SESSION_DATA_ATTRIBUTE).getB(); + ByteArrayInputStream inputStream = new ByteArrayInputStream( + byteBuffer.array()); + + Object readObject; + + try (CustomObjectInputStream objectInputStream = new CustomObjectInputStream( + inputStream, getManager().getContext().getLoader() + .getClassLoader())) { + readObject = objectInputStream.readObject(); + } + + if (readObject instanceof Map) { + @SuppressWarnings("unchecked") + Map sessionAttributeMap = (Map) readObject; + + for (String s : sessionAttributeMap.keySet()) { + ((StandardSession) session).setAttribute(s, + sessionAttributeMap.get(s)); + } + } else { + throw new RuntimeException( + "Error: Unable to unmarshall session attributes from DynamoDB store"); + } + + keys.add(id); + manager.add(session); + + return session; + } + + @Override + public void save(Session session) throws IOException { + DynamoUtils.storeSession(dynamo, sessionTableName, session); + keys.add(session.getId()); + } + + @Override + public void remove(String id) throws IOException { + DynamoUtils.deleteSession(dynamo, sessionTableName, id); + keys.remove(id); + } } From 77a395cd48af95cffef916190d1297a0a6b8254d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A8n-Dario=20Casta=C3=B1=C3=A9?= Date: Fri, 12 Dec 2014 10:39:08 +0100 Subject: [PATCH 03/23] Updated AWS SDK. --- pom.xml | 3 ++- .../services/dynamodb/sessionmanager/util/DynamoUtils.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1479b47..942f69e 100644 --- a/pom.xml +++ b/pom.xml @@ -37,12 +37,13 @@ com.amazonaws aws-java-sdk - 1.5.4 + 1.9.10 org.apache.tomcat tomcat-catalina 8.0.14 + provided 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 41fc838..c6716aa 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 @@ -180,7 +180,7 @@ public static void createSessionTable(AmazonDynamoDBClient dynamo, String tableN } public static void addClientMarker(AmazonWebServiceRequest request) { - request.getRequestClientOptions().addClientMarker("DynamoSessionManager/1.0"); + request.getRequestClientOptions().appendUserAgent("DynamoSessionManager/1.0"); } private static Map newAttributeValueMap() { From 6bb880583a527f0b5d82f793882fbd84d279b5fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A8n-Dario=20Casta=C3=B1=C3=A9?= Date: Fri, 12 Dec 2014 12:40:49 +0100 Subject: [PATCH 04/23] Fix shade plugin issue. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 942f69e..dd9afdf 100644 --- a/pom.xml +++ b/pom.xml @@ -114,7 +114,7 @@ - com.amazonaws:aws-java-sdk + com.amazonaws:aws-java-sdk* commons-logging:* org.apache.httpcomponents:* commons-codec:* From 1500aae167bf46c38adc9b820d2df4e39c86f43a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A8n-Dario=20Casta=C3=B1=C3=A9?= Date: Fri, 12 Dec 2014 13:28:09 +0100 Subject: [PATCH 05/23] All shades fixed. --- .gitignore | 1 + pom.xml | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index e75c406..80e0ab8 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ .settings/org.eclipse.core.resources.prefs .settings/org.eclipse.jdt.core.prefs .settings/org.eclipse.m2e.core.prefs +dependency-reduced-pom.xml diff --git a/pom.xml b/pom.xml index dd9afdf..36cb662 100644 --- a/pom.xml +++ b/pom.xml @@ -105,10 +105,13 @@ org.apache.commons.codec com.amazonaws.tomcatsessionmanager.apache.commons.codec - - org.codehaus - com.amazonaws.tomcatsessionmanager.codehaus + com.fasterxml + com.amazonaws.tomcatsessionmanager.fasterxml + + + org.joda.time + com.amazonaws.tomcatsessionmanager.joda.time @@ -118,7 +121,8 @@ commons-logging:* org.apache.httpcomponents:* commons-codec:* - org.codehaus.jackson:* + com.fasterxml.jackson.core:* + joda-time:* @@ -131,6 +135,12 @@ ** + + com.fasterxml.jackson.core:* + + ** + + From 646e6d952620b0154b4e5ed4d81909addc44824d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A8n-Dario=20Casta=C3=B1=C3=A9?= Date: Fri, 12 Dec 2014 13:28:25 +0100 Subject: [PATCH 06/23] Maven POM formatted. --- pom.xml | 360 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 185 insertions(+), 175 deletions(-) diff --git a/pom.xml b/pom.xml index 36cb662..346973c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,189 +1,199 @@ - - 4.0.0 - com.amazonaws - aws-dynamodb-session-tomcat - jar - Amazon DynamoDB Session Manager for Tomcat - 1.0.2 - 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 + + 4.0.0 + com.amazonaws + aws-dynamodb-session-tomcat + jar + Amazon DynamoDB Session Manager for Tomcat + 1.0.2 + 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 - - https://github.com/aws/aws-dynamodb-session-tomcat.git - + + https://github.com/aws/aws-dynamodb-session-tomcat.git + - - - Apache License, Version 2.0 - https://aws.amazon.com/apache2.0 - repo - - + + + Apache License, Version 2.0 + https://aws.amazon.com/apache2.0 + repo + + - - - amazonwebservices - Amazon Web Services - https://aws.amazon.com - - developer - - - + + + amazonwebservices + Amazon Web Services + https://aws.amazon.com + + developer + + + - - - com.amazonaws - aws-java-sdk - 1.9.10 - - - org.apache.tomcat - tomcat-catalina - 8.0.14 - provided - - + + + com.amazonaws + aws-java-sdk + 1.9.10 + + + org.apache.tomcat + tomcat-catalina + 8.0.14 + provided + + - - - - ${basedir} - - LICENSE.txt - NOTICE.txt - README.txt - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3 - - 1.5 - 1.5 - UTF-8 - - + + + + ${basedir} + + LICENSE.txt + NOTICE.txt + README.txt + + + - - org.apache.maven.plugins - maven-shade-plugin - 2.2 - - - package - - shade - - - true + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3 + + 1.5 + 1.5 + UTF-8 + + - - - 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 - - - - - - com.amazonaws:aws-java-sdk* - commons-logging:* - org.apache.httpcomponents:* - commons-codec:* - com.fasterxml.jackson.core:* - joda-time:* - - + + org.apache.maven.plugins + maven-shade-plugin + 2.2 + + + package + + shade + + + true - - - - commons-logging:commons-logging - - ** - - - - com.fasterxml.jackson.core:* - - ** - - - + + + 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 + + + - - - - - - + + + com.amazonaws:aws-java-sdk* + commons-logging:* + org.apache.httpcomponents:* + commons-codec:* + com.fasterxml.jackson.core:* + joda-time:* + + - - - publishing - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - + + + + commons-logging:commons-logging + + ** + + + + com.fasterxml.jackson.core:* + + ** + + + - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.5.1 - true - - sonatype-nexus-staging - https://oss.sonatype.org - true - - - + + + + + - - + + + + 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 + + + + + + From 3665da142e96a7592694375d29059d9e4500be18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A8n-Dario=20Casta=C3=B1=C3=A9?= Date: Wed, 17 Dec 2014 08:29:15 +0100 Subject: [PATCH 07/23] Revert "Maven POM formatted." This reverts commit 646e6d952620b0154b4e5ed4d81909addc44824d. --- pom.xml | 360 +++++++++++++++++++++++++++----------------------------- 1 file changed, 175 insertions(+), 185 deletions(-) diff --git a/pom.xml b/pom.xml index 346973c..36cb662 100644 --- a/pom.xml +++ b/pom.xml @@ -1,199 +1,189 @@ - - 4.0.0 - com.amazonaws - aws-dynamodb-session-tomcat - jar - Amazon DynamoDB Session Manager for Tomcat - 1.0.2 - 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 + + 4.0.0 + com.amazonaws + aws-dynamodb-session-tomcat + jar + Amazon DynamoDB Session Manager for Tomcat + 1.0.2 + 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 - - https://github.com/aws/aws-dynamodb-session-tomcat.git - + + https://github.com/aws/aws-dynamodb-session-tomcat.git + - - - Apache License, Version 2.0 - https://aws.amazon.com/apache2.0 - repo - - + + + Apache License, Version 2.0 + https://aws.amazon.com/apache2.0 + repo + + - - - amazonwebservices - Amazon Web Services - https://aws.amazon.com - - developer - - - + + + amazonwebservices + Amazon Web Services + https://aws.amazon.com + + developer + + + - - - com.amazonaws - aws-java-sdk - 1.9.10 - - - org.apache.tomcat - tomcat-catalina - 8.0.14 - provided - - + + + com.amazonaws + aws-java-sdk + 1.9.10 + + + org.apache.tomcat + tomcat-catalina + 8.0.14 + provided + + - - - - ${basedir} - - LICENSE.txt - NOTICE.txt - README.txt - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3 - - 1.5 - 1.5 - UTF-8 - - - - - org.apache.maven.plugins - maven-shade-plugin - 2.2 - - - package - - shade - - - true + + + + ${basedir} + + LICENSE.txt + NOTICE.txt + README.txt + + + - - - 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 - - - + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3 + + 1.5 + 1.5 + UTF-8 + + - - - com.amazonaws:aws-java-sdk* - commons-logging:* - org.apache.httpcomponents:* - commons-codec:* - com.fasterxml.jackson.core:* - joda-time:* - - + + org.apache.maven.plugins + maven-shade-plugin + 2.2 + + + package + + shade + + + true - - - - commons-logging:commons-logging - - ** - - - - com.fasterxml.jackson.core:* - - ** - - - + + + 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 + + + + + + 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 + + + + + + From f079854e49da3ff3fee7f77f2e63553d5249618b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Casta=C3=B1=C3=A9?= Date: Thu, 8 Jan 2015 16:17:31 +0100 Subject: [PATCH 08/23] Short note on how to build. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 349b2a4..26dfbb0 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ For more information on using the session manager, see the Developer Information --------------------- -You can check out the source for the session manager here, and build it with Maven. +You can check out the source for the session manager here, and build it with Maven (mvn package). The official release builds use JarJar to package all the dependencies in the session manager jar *(to provide an easy, one-jar install)* and rename classes *(to avoid exposing the SDK code to all web apps running in Tomcat)*. To run with a development build, From 72afae4f3bfb4b378da4158c470d6660fb25e534 Mon Sep 17 00:00:00 2001 From: Selindek Date: Mon, 26 Jan 2015 18:13:44 +0100 Subject: [PATCH 09/23] v2.0 --- pom.xml | 8 +- .../DynamoDBSessionManager.java | 6 +- .../sessionmanager/DynamoDBSessionStore.java | 292 ++++++++++------- .../sessionmanager/ExpiredSessionReaper.java | 131 -------- .../SessionTableAttributes.java | 26 -- .../sessionmanager/util/DynamoUtils.java | 293 +++++++++--------- 6 files changed, 331 insertions(+), 425 deletions(-) delete mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaper.java delete mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/SessionTableAttributes.java diff --git a/pom.xml b/pom.xml index 12b393f..c5b3cac 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ aws-dynamodb-session-tomcat jar Amazon DynamoDB Session Manager for Tomcat - 1.0.2 + 1.2 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 @@ -42,7 +42,7 @@ org.apache.tomcat tomcat-catalina - 7.0.42 + 7.0.47 @@ -64,8 +64,8 @@ maven-compiler-plugin 2.3 - 1.5 - 1.5 + 1.7 + 1.7 UTF-8 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 136c340..0a098b1 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java @@ -39,7 +39,7 @@ public class DynamoDBSessionManager extends PersistentManagerBase { private static final String DEFAULT_TABLE_NAME = "Tomcat_SessionState"; private static final String name = "AmazonDynamoDBSessionManager"; - private static final String info = name + "/1.0"; + private static final String info = name + "/2.0"; private String regionId = "us-east-1"; private String endpoint; @@ -53,8 +53,6 @@ public class DynamoDBSessionManager extends PersistentManagerBase { private final DynamoDBSessionStore dynamoSessionStore; - private ExpiredSessionReaper expiredSessionReaper; - private static Log logger; @@ -143,13 +141,11 @@ protected void initInternal() throws LifecycleException { dynamoSessionStore.setDynamoClient(dynamo); dynamoSessionStore.setSessionTableName(this.tableName); - expiredSessionReaper = new ExpiredSessionReaper(dynamo, tableName, this.maxInactiveInterval); } @Override protected synchronized void stopInternal() throws LifecycleException { super.stopInternal(); - if (expiredSessionReaper != null) expiredSessionReaper.shutdown(); } private void initDynamoTable(AmazonDynamoDBClient dynamo) { 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 091fbf9..c9df083 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java @@ -14,15 +14,21 @@ */ 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.Map; +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; @@ -30,7 +36,6 @@ import com.amazonaws.services.dynamodb.sessionmanager.util.DynamoUtils; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; -import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.amazonaws.services.dynamodbv2.model.DescribeTableRequest; import com.amazonaws.services.dynamodbv2.model.TableDescription; @@ -40,118 +45,173 @@ */ 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()); - - - @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) { - DynamoUtils.deleteSession(dynamo, sessionTableName, sessionId); - } - } - }.start(); - - keys.clear(); - } - - @Override - public int getSize() throws IOException { - // The item count from describeTable is updated every ~6 hours - TableDescription table = dynamo.describeTable(new DescribeTableRequest().withTableName(sessionTableName)).getTable(); - long itemCount = table.getItemCount(); - - return (int)itemCount; - } - - @Override - public String[] keys() throws IOException { - return keys.toArray(new String[0]); - } - - @Override - public Session load(String id) throws ClassNotFoundException, IOException { - Map item = DynamoUtils.loadItemBySessionId(dynamo, sessionTableName, id); - if (item == null || !item.containsKey(SessionTableAttributes.SESSION_ID_KEY) || !item.containsKey(SessionTableAttributes.SESSION_DATA_ATTRIBUTE)) { - DynamoDBSessionManager.warn("Unable to load session attributes for session " + id); - return null; - } - - - Session session = getManager().createSession(id); - session.setCreationTime(Long.parseLong(item.get(SessionTableAttributes.CREATED_AT_ATTRIBUTE).getN())); - - - ByteBuffer byteBuffer = item.get(SessionTableAttributes.SESSION_DATA_ATTRIBUTE).getB(); - ByteArrayInputStream inputStream = new ByteArrayInputStream(byteBuffer.array()); - - Object readObject; - CustomObjectInputStream objectInputStream = null; - try { - Container webapp = getManager().getContainer(); - objectInputStream = new CustomObjectInputStream(inputStream, webapp.getLoader().getClassLoader()); - - readObject = objectInputStream.readObject(); - } finally { - try { objectInputStream.close(); } catch (Exception e) {} - } - - if (readObject instanceof Map) { - Map sessionAttributeMap = (Map)readObject; - - for (String s : sessionAttributeMap.keySet()) { - ((StandardSession)session).setAttribute(s, sessionAttributeMap.get(s)); - } - } else { - throw new RuntimeException("Error: Unable to unmarshall session attributes from DynamoDB store"); - } - - - keys.add(id); - manager.add(session); - - return session; - } - - @Override - public void save(Session session) throws IOException { - DynamoUtils.storeSession(dynamo, sessionTableName, session); - keys.add(session.getId()); - } - - @Override - public void remove(String id) throws IOException { - DynamoUtils.deleteSession(dynamo, sessionTableName, id); - keys.remove(id); - } + 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 diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaper.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaper.java deleted file mode 100644 index 4009c52..0000000 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaper.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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.List; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; - -import com.amazonaws.services.dynamodb.sessionmanager.util.DynamoUtils; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; -import com.amazonaws.services.dynamodbv2.model.AttributeValue; -import com.amazonaws.services.dynamodbv2.model.ScanRequest; -import com.amazonaws.services.dynamodbv2.model.ScanResult; -import com.amazonaws.services.dynamodbv2.model.Select; - -/** - * A background process to periodically scan (once every 12 hours, with an - * initial random jitter) and remove any expired session data from the session - * table in Amazon DynamoDB. - */ -public class ExpiredSessionReaper { - - private AmazonDynamoDBClient dynamo; - private String tableName; - private long expirationTimeInMillis; - private ScheduledThreadPoolExecutor executor; - - - /** - * Constructs and immediately starts an ExpiredSessionReaper. - * - * @param dynamo - * The client to use when accessing Amazon DynamoDB. - * @param tableName - * The name of the DynamoDB table containing session information. - * @param expirationTimeInMillis - * The time, in milliseconds, after which a session is considered - * expired and should be removed from the session table. - */ - public ExpiredSessionReaper(AmazonDynamoDBClient dynamo, String tableName, long expirationTimeInMillis) { - this.dynamo = dynamo; - this.tableName = tableName; - this.expirationTimeInMillis = expirationTimeInMillis; - - int initialDelay = new Random().nextInt(5) + 1; - executor = new ScheduledThreadPoolExecutor(1, new ExpiredSessionReaperThreadFactory()); - executor.scheduleAtFixedRate(new ExpiredSessionReaperRunnable(), initialDelay, 12, 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("dynamo-session-manager-expired-sesion-reaper"); - return thread; - } - } - - /** - * Runnable that is invoked periodically to scan for expired sessions. - */ - private class ExpiredSessionReaperRunnable implements Runnable { - @Override - public void run() { - reapExpiredSessions(); - } - } - - /** - * Scans the session table for expired sessions and deletes them. - */ - private void reapExpiredSessions() { - ScanRequest request = new ScanRequest(tableName); - request.setSelect(Select.SPECIFIC_ATTRIBUTES); - request.withAttributesToGet( - SessionTableAttributes.SESSION_ID_KEY, - SessionTableAttributes.LAST_UPDATED_AT_ATTRIBUTE); - - ScanResult scanResult = null; - do { - if (scanResult != null) request.setExclusiveStartKey(scanResult.getLastEvaluatedKey()); - - scanResult = dynamo.scan(request); - List> items = scanResult.getItems(); - for (Map item : items) { - if (isExpired(Long.parseLong(item.get(SessionTableAttributes.LAST_UPDATED_AT_ATTRIBUTE).getN()))) { - String sessionId = item.get(SessionTableAttributes.SESSION_ID_KEY).getS(); - DynamoUtils.deleteSession(dynamo, tableName, sessionId); - } - } - } while (scanResult.getLastEvaluatedKey() != null); - } - - /** - * Returns true if the specified session date is past the expiration point. - * - * @param sessionDateInMillis - * The last access date, in milliseconds, for a session. - * - * @return True if the specified session date is past the expiration point. - */ - private boolean isExpired(long sessionDateInMillis) { - return sessionDateInMillis < (System.currentTimeMillis() - expirationTimeInMillis); - } -} diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/SessionTableAttributes.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/SessionTableAttributes.java deleted file mode 100644 index 1720d13..0000000 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/SessionTableAttributes.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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; - -/** - * Constants for DynamoDB table attributes - */ -public class SessionTableAttributes { - public static final String SESSION_ID_KEY = "sessionId"; - - public static final String SESSION_DATA_ATTRIBUTE = "sessionData"; - public static final String LAST_UPDATED_AT_ATTRIBUTE = "lastUpdatedAt"; - public static final String CREATED_AT_ATTRIBUTE = "createdAt"; -} \ No newline at end of file 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 dd6a8ba..ae072c4 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,23 +14,17 @@ */ package com.amazonaws.services.dynamodb.sessionmanager.util; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.ObjectOutputStream; import java.nio.ByteBuffer; -import java.util.Enumeration; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; -import javax.servlet.http.HttpSession; - -import org.apache.catalina.Session; - import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; import com.amazonaws.AmazonWebServiceRequest; import com.amazonaws.services.dynamodb.sessionmanager.DynamoDBSessionManager; -import com.amazonaws.services.dynamodb.sessionmanager.SessionTableAttributes; import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; import com.amazonaws.services.dynamodbv2.model.AttributeDefinition; @@ -39,12 +33,14 @@ 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.GetItemResult; 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; @@ -53,138 +49,149 @@ */ public class DynamoUtils { - public static Map loadItemBySessionId(AmazonDynamoDB dynamo, String tableName, String sessionId) { - Map map = newAttributeValueMap(); - map.put(SessionTableAttributes.SESSION_ID_KEY, new AttributeValue(sessionId)); - GetItemRequest request = new GetItemRequest(tableName, map); - addClientMarker(request); - - try { - GetItemResult result = dynamo.getItem(request); - return result.getItem(); - } catch (Exception e) { - DynamoDBSessionManager.warn("Unable to load session " + sessionId, e); - } - - return null; - } - - public static void deleteSession(AmazonDynamoDB dynamo, String tableName, String sessionId) { - Map key = newAttributeValueMap(); - key.put(SessionTableAttributes.SESSION_ID_KEY, new AttributeValue(sessionId)); - - DeleteItemRequest request = new DeleteItemRequest(tableName, key); - addClientMarker(request); - - try { - dynamo.deleteItem(request); - } catch (Exception e) { - DynamoDBSessionManager.warn("Unable to delete session " + sessionId, e); - } - } - - public static void storeSession(AmazonDynamoDB dynamo, String tableName, Session session) throws IOException { - Map sessionAttributes = new HashMap(); - - HttpSession httpSession = session.getSession(); - Enumeration attributeNames = httpSession.getAttributeNames(); - while (attributeNames.hasMoreElements()) { - String attributeName = attributeNames.nextElement(); - Object attributeValue = httpSession.getAttribute(attributeName); - sessionAttributes.put(attributeName, attributeValue); - } - - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); - objectOutputStream.writeObject(sessionAttributes); - objectOutputStream.close(); - - byte[] byteArray = byteArrayOutputStream.toByteArray(); - - Map attributes = newAttributeValueMap(); - attributes.put(SessionTableAttributes.SESSION_ID_KEY, new AttributeValue(session.getId())); - ByteBuffer b = ByteBuffer.wrap(byteArray); - attributes.put(SessionTableAttributes.SESSION_DATA_ATTRIBUTE, new AttributeValue().withB(b)); - attributes.put(SessionTableAttributes.CREATED_AT_ATTRIBUTE, new AttributeValue().withN(Long.toString(session.getCreationTime()))); - attributes.put(SessionTableAttributes.LAST_UPDATED_AT_ATTRIBUTE, new AttributeValue().withN(Long.toString(System.currentTimeMillis()))); - - try { - PutItemRequest request = new PutItemRequest(tableName, attributes); - addClientMarker(request); - dynamo.putItem(request); - } catch (Exception e) { - DynamoDBSessionManager.error("Unable to save session " + session.getId(), e); - } - } - - public static boolean doesTableExist(AmazonDynamoDBClient dynamo, String tableName) { - try { - DescribeTableRequest request = new DescribeTableRequest().withTableName(tableName); - addClientMarker(request); - - TableDescription table = dynamo.describeTable(request).getTable(); - if (table == null) return false; - else return true; - } catch (AmazonServiceException ase) { - if (ase.getErrorCode().equalsIgnoreCase("ResourceNotFoundException")) return false; - else throw ase; - } - } - - public static void waitForTableToBecomeActive(AmazonDynamoDBClient dynamo, String tableName) { - long startTime = System.currentTimeMillis(); - long endTime = startTime + (10 * 60 * 1000); - while (System.currentTimeMillis() < endTime) { - try { - DescribeTableRequest request = new DescribeTableRequest().withTableName(tableName); - addClientMarker(request); - - TableDescription tableDescription = dynamo.describeTable(request).getTable(); - if (tableDescription == null) continue; - - String tableStatus = tableDescription.getTableStatus(); - if (tableStatus.equals(TableStatus.ACTIVE.toString())) return; - } catch (AmazonServiceException ase) { - if (ase.getErrorCode().equalsIgnoreCase("ResourceNotFoundException") == false) - throw ase; - } - - try { - Thread.sleep(1000 * 5); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new AmazonClientException( - "Interrupted while waiting for table '" + tableName + "' to become active.", e); - } - } - - throw new AmazonClientException("Table '" + tableName + "' never became active"); - } - - public static void createSessionTable(AmazonDynamoDBClient dynamo, String tableName, long readCapacityUnits, long writeCapacityUnits) { - CreateTableRequest request = new CreateTableRequest().withTableName(tableName); - addClientMarker(request); - - request.withKeySchema(new KeySchemaElement() - .withAttributeName(SessionTableAttributes.SESSION_ID_KEY) - .withKeyType(KeyType.HASH)); - - request.withAttributeDefinitions(new AttributeDefinition() - .withAttributeName(SessionTableAttributes.SESSION_ID_KEY) - .withAttributeType(ScalarAttributeType.S)); - - request.setProvisionedThroughput(new ProvisionedThroughput() - .withReadCapacityUnits(readCapacityUnits) - .withWriteCapacityUnits(writeCapacityUnits)); - - dynamo.createTable(request); - } - - public static void addClientMarker(AmazonWebServiceRequest request) { - request.getRequestClientOptions().addClientMarker("DynamoSessionManager/1.0"); - } - - private static Map newAttributeValueMap() { - return new HashMap(); - } + 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> items = scanResult.getItems(); + for (Map item : items) { + list.add(item.get(SESSION_ID_KEY).getS()); + } + } while (scanResult.getLastEvaluatedKey() != null); + + return list; + } + + public static ByteBuffer loadItemBySessionId(AmazonDynamoDB dynamo, String tableName, String sessionId) { + Map key = newAttributeValueMap(); + key.put(SESSION_ID_KEY, new AttributeValue(sessionId)); + GetItemRequest request = new GetItemRequest(tableName, key); + addClientMarker(request); + try { + Map item = dynamo.getItem(request).getItem(); + if (item == null || !item.containsKey(SESSION_ID_KEY) || !item.containsKey(SESSION_DATA_ATTRIBUTE)) { + DynamoDBSessionManager.warn("Unable to load session attributes for session " + sessionId); + return null; + } + return item.get(SESSION_DATA_ATTRIBUTE).getB(); + } catch (Exception e) { + DynamoDBSessionManager.warn("Unable to load session " + sessionId, e); + } + return null; + } + + public static void deleteSession(AmazonDynamoDB dynamo, String tableName, String sessionId) { + Map key = newAttributeValueMap(); + key.put(SESSION_ID_KEY, new AttributeValue(sessionId)); + + DeleteItemRequest request = new DeleteItemRequest(tableName, key); + addClientMarker(request); + + try { + dynamo.deleteItem(request); + } catch (Exception e) { + DynamoDBSessionManager.warn("Unable to delete session " + sessionId, e); + } + } + + public static void storeSession(AmazonDynamoDB dynamo, String tableName, String sessionId, ByteBuffer byteBuffer) + throws IOException { + + Map attributes = newAttributeValueMap(); + attributes.put(SESSION_ID_KEY, new AttributeValue(sessionId)); + attributes.put(SESSION_DATA_ATTRIBUTE, new AttributeValue().withB(byteBuffer)); + + try { + PutItemRequest request = new PutItemRequest(tableName, attributes); + addClientMarker(request); + dynamo.putItem(request); + } catch (Exception e) { + DynamoDBSessionManager.error("Unable to save session " + sessionId, e); + } + } + + public static boolean doesTableExist(AmazonDynamoDBClient dynamo, String tableName) { + try { + DescribeTableRequest request = new DescribeTableRequest().withTableName(tableName); + addClientMarker(request); + + TableDescription table = dynamo.describeTable(request).getTable(); + if (table == null) + return false; + else + return true; + } catch (AmazonServiceException ase) { + if (ase.getErrorCode().equalsIgnoreCase("ResourceNotFoundException")) + return false; + else + throw ase; + } + } + + public static void waitForTableToBecomeActive(AmazonDynamoDBClient dynamo, String tableName) { + long startTime = System.currentTimeMillis(); + long endTime = startTime + (10 * 60 * 1000); + while (System.currentTimeMillis() < endTime) { + try { + DescribeTableRequest request = new DescribeTableRequest().withTableName(tableName); + addClientMarker(request); + + TableDescription tableDescription = dynamo.describeTable(request).getTable(); + if (tableDescription == null) + continue; + + String tableStatus = tableDescription.getTableStatus(); + if (tableStatus.equals(TableStatus.ACTIVE.toString())) + return; + } catch (AmazonServiceException ase) { + if (ase.getErrorCode().equalsIgnoreCase("ResourceNotFoundException") == false) + throw ase; + } + + try { + Thread.sleep(1000 * 5); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new AmazonClientException("Interrupted while waiting for table '" + tableName + + "' to become active.", e); + } + } + + throw new AmazonClientException("Table '" + tableName + "' never became active"); + } + + public static void createSessionTable(AmazonDynamoDBClient dynamo, String tableName, long readCapacityUnits, + long writeCapacityUnits) { + CreateTableRequest request = new CreateTableRequest().withTableName(tableName); + addClientMarker(request); + + request.withKeySchema(new KeySchemaElement().withAttributeName(SESSION_ID_KEY).withKeyType(KeyType.HASH)); + + request.withAttributeDefinitions(new AttributeDefinition().withAttributeName(SESSION_ID_KEY).withAttributeType( + ScalarAttributeType.S)); + + request.setProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(readCapacityUnits) + .withWriteCapacityUnits(writeCapacityUnits)); + + dynamo.createTable(request); + } + + public static void addClientMarker(AmazonWebServiceRequest request) { + request.getRequestClientOptions().addClientMarker("DynamoSessionManager/2.0"); + } + + private static Map newAttributeValueMap() { + return new HashMap(); + } } From 1f3237aa9c7251ed6392b4693421602940bec56f Mon Sep 17 00:00:00 2001 From: Andrew Shore Date: Wed, 11 Mar 2015 00:02:35 +0000 Subject: [PATCH 10/23] Merge pull request #17 from imdario/master. Tomcat 8 support, backwards compatible with Tomcat 7. --- NOTICE.txt | 8 - README.md | 2 +- pom.xml | 287 +++++++++--------- .../DynamoDBSessionManager.java | 97 +++--- .../sessionmanager/DynamoDBSessionStore.java | 258 +++++++++------- .../sessionmanager/util/DynamoUtils.java | 55 ++-- 6 files changed, 376 insertions(+), 331 deletions(-) diff --git a/NOTICE.txt b/NOTICE.txt index 1f81414..c004f1d 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -3,11 +3,3 @@ Copyright 2010-2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. This product includes software developed by Amazon Technologies, Inc (http://www.amazon.com/). - -********************** -THIRD PARTY COMPONENTS -********************** -This software includes third party software subject to the following copyrights: -- JSON parsing and utility functions from JSON.org - Copyright 2002 JSON.org. - -The licenses for these third party components are included in LICENSE.txt diff --git a/README.md b/README.md index 26dfbb0..349b2a4 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ For more information on using the session manager, see the Developer Information --------------------- -You can check out the source for the session manager here, and build it with Maven (mvn package). +You can check out the source for the session manager here, and build it with Maven. The official release builds use JarJar to package all the dependencies in the session manager jar *(to provide an easy, one-jar install)* and rename classes *(to avoid exposing the SDK code to all web apps running in Tomcat)*. To run with a development build, diff --git a/pom.xml b/pom.xml index 36cb662..f4effb6 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.0.2 + 1.0.4 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,156 +34,161 @@ - - com.amazonaws - aws-java-sdk - 1.9.10 - - - org.apache.tomcat - tomcat-catalina - 8.0.14 - provided - + + com.amazonaws + aws-java-sdk-dynamodb + 1.9.23 + + + org.apache.tomcat + tomcat-catalina + 8.0.14 + provided + - - - ${basedir} - - LICENSE.txt - NOTICE.txt - README.txt - - - + + + ${basedir} + + LICENSE.txt + NOTICE.txt + README.md + + + - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3 - - 1.5 - 1.5 - UTF-8 - - + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3 + + 1.6 + 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 - - - com.fasterxml - com.amazonaws.tomcatsessionmanager.fasterxml - - - org.joda.time - com.amazonaws.tomcatsessionmanager.joda.time - - - - - - com.amazonaws:aws-java-sdk* - commons-logging:* - org.apache.httpcomponents:* - commons-codec:* - com.fasterxml.jackson.core:* - joda-time:* - - + + + 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.fasterxml.jackson.core:* - - ** - - - + + + 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 97172a3..28bfa47 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java @@ -19,6 +19,7 @@ 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; @@ -30,18 +31,18 @@ import com.amazonaws.regions.RegionUtils; import com.amazonaws.services.dynamodb.sessionmanager.util.DynamoUtils; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; +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"; private static final String name = "AmazonDynamoDBSessionManager"; - @SuppressWarnings("unused") - private static final String info = name + "/1.0"; + private static final String info = name + "/1.0"; private String regionId = "us-east-1"; private String endpoint; @@ -57,10 +58,7 @@ public class DynamoDBSessionManager extends PersistentManagerBase { private final DynamoDBSessionStore dynamoSessionStore; - private ExpiredSessionReaper expiredSessionReaper; - - private static Log logger; - + private static final Log logger = LogFactory.getLog(DynamoDBSessionManager.class); public DynamoDBSessionManager() { dynamoSessionStore = new DynamoDBSessionStore(); @@ -74,6 +72,10 @@ public DynamoDBSessionManager() { setMaxIdleBackup(30); // 30 seconds } + public String getInfo() { + return info; + } + @Override public String getName() { return name; @@ -135,29 +137,22 @@ public void setProxyPort(Integer proxyPort) { protected void initInternal() throws LifecycleException { this.setDistributable(true); - // Grab the container's logger - logger = getContext().getLogger(); - AWSCredentialsProvider credentialsProvider = initCredentials(); ClientConfiguration clientConfiguration = initClientConfiguration(); AmazonDynamoDBClient dynamo = new AmazonDynamoDBClient(credentialsProvider, clientConfiguration); - if (this.regionId != null) dynamo.setRegion(RegionUtils.getRegion(this.regionId)); - if (this.endpoint != null) dynamo.setEndpoint(this.endpoint); + 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); - - expiredSessionReaper = new ExpiredSessionReaper(dynamo, tableName, (this.maxInactiveInterval * 1000)); - } - - @Override - protected synchronized void stopInternal() throws LifecycleException { - super.stopInternal(); - if (expiredSessionReaper != null) expiredSessionReaper.shutdown(); } private void initDynamoTable(AmazonDynamoDBClient dynamo) { @@ -168,68 +163,84 @@ private void initDynamoTable(AmazonDynamoDBClient dynamo) { + "and automatic table creation has been disabled in context.xml"); } - if (!tableExists) DynamoUtils.createSessionTable(dynamo, this.tableName, - this.readCapacityUnits, this.writeCapacityUnits); + if (!tableExists) { + DynamoUtils.createSessionTable(dynamo, this.tableName, this.readCapacityUnits, this.writeCapacityUnits); + } DynamoUtils.waitForTableToBecomeActive(dynamo, this.tableName); } private AWSCredentialsProvider initCredentials() { - // Attempt to use any explicitly specified credentials first - if (accessKey != null || secretKey != null) { - getContext().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."); } - getContext().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 { - getContext().getLogger().debug("Reading security credentials from properties file: " + credentialsFile); + debug("Reading security credentials from properties file: " + credentialsFile); PropertiesCredentials credentials = new PropertiesCredentials(credentialsFile); - getContext().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) { - getContext().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."); } - getContext().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(); // Attempt to use an explicit proxy configuration if (proxyHost != null || proxyPort != null) { - getContainer().getLogger().debug("Reading proxy settings from context.xml"); + debug("Reading proxy settings from context.xml"); if (proxyHost == null || proxyPort == null) { - throw new AmazonClientException("Incomplete proxy settings specified in context.xml."); + throw new AmazonClientException("Incomplete proxy settings specified in context.xml." + + " Both proxy hot and proxy port needs to be specified"); } - getContainer().getLogger().debug("Using proxy host and port from context.xml"); - clientConfiguration - .withProxyHost(proxyHost) - .withProxyPort(proxyPort); + debug("Using proxy host and port from context.xml"); + clientConfiguration.withProxyHost(proxyHost).withProxyPort(proxyPort); } return clientConfiguration; } - // // 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 5ce2110..c171e14 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java @@ -14,145 +14,167 @@ */ package com.amazonaws.services.dynamodb.sessionmanager; +import static com.amazonaws.util.BinaryUtils.copyAllBytesFrom; + import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.ObjectInputStream; import java.nio.ByteBuffer; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; +import org.apache.catalina.Context; 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.AttributeValue; import com.amazonaws.services.dynamodbv2.model.DescribeTableRequest; import com.amazonaws.services.dynamodbv2.model.TableDescription; +import com.amazonaws.util.IOUtils; /** - * 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"; - @SuppressWarnings("unused") - private static final String info = name + "/1.0"; - - private AmazonDynamoDBClient dynamo; - private String sessionTableName; - - private Set keys = Collections - .synchronizedSet(new HashSet()); - - @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) { - DynamoUtils.deleteSession(dynamo, sessionTableName, - sessionId); - } - } - }.start(); - - keys.clear(); - } - - @Override - public int getSize() throws IOException { - // The item count from describeTable is updated every ~6 hours - TableDescription table = dynamo.describeTable( - new DescribeTableRequest().withTableName(sessionTableName)) - .getTable(); - long itemCount = table.getItemCount(); - - return (int) itemCount; - } - - @Override - public String[] keys() throws IOException { - return keys.toArray(new String[keys.size()]); - } - - @Override - public Session load(String id) throws ClassNotFoundException, IOException { - Map item = DynamoUtils.loadItemBySessionId( - dynamo, sessionTableName, id); - if (item == null - || !item.containsKey(SessionTableAttributes.SESSION_ID_KEY) - || !item.containsKey(SessionTableAttributes.SESSION_DATA_ATTRIBUTE)) { - DynamoDBSessionManager - .warn("Unable to load session attributes for session " + id); - return null; - } - - Session session = getManager().createSession(id); - session.setCreationTime(Long.parseLong(item.get( - SessionTableAttributes.CREATED_AT_ATTRIBUTE).getN())); - - ByteBuffer byteBuffer = item.get( - SessionTableAttributes.SESSION_DATA_ATTRIBUTE).getB(); - ByteArrayInputStream inputStream = new ByteArrayInputStream( - byteBuffer.array()); - - Object readObject; - - try (CustomObjectInputStream objectInputStream = new CustomObjectInputStream( - inputStream, getManager().getContext().getLoader() - .getClassLoader())) { - readObject = objectInputStream.readObject(); - } - - if (readObject instanceof Map) { - @SuppressWarnings("unchecked") - Map sessionAttributeMap = (Map) readObject; - - for (String s : sessionAttributeMap.keySet()) { - ((StandardSession) session).setAttribute(s, - sessionAttributeMap.get(s)); - } - } else { - throw new RuntimeException( - "Error: Unable to unmarshall session attributes from DynamoDB store"); - } - - keys.add(id); - manager.add(session); - - return session; - } - - @Override - public void save(Session session) throws IOException { - DynamoUtils.storeSession(dynamo, sessionTableName, session); - keys.add(session.getId()); - } - - @Override - public void remove(String id) throws IOException { - DynamoUtils.deleteSession(dynamo, sessionTableName, id); - keys.remove(id); - } + private static final String name = "AmazonDynamoDBSessionStore"; + private static final String info = name + "/1.0"; + + private AmazonDynamoDBClient dynamo; + private String sessionTableName; + + private final Set keys = Collections.synchronizedSet(new HashSet()); + + private static final Log logger = LogFactory.getLog(DynamoDBSessionStore.class); + + 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) { + DynamoUtils.deleteSession(dynamo, sessionTableName, sessionId); + } + } + }.start(); + + keys.clear(); + } + + @Override + public int getSize() throws IOException { + // The item count from describeTable is updated every ~6 hours + TableDescription table = dynamo.describeTable(new DescribeTableRequest().withTableName(sessionTableName)) + .getTable(); + long itemCount = table.getItemCount(); + + return (int) itemCount; + } + + @Override + public String[] keys() throws IOException { + return keys.toArray(new String[0]); + } + + @Override + public Session load(String id) throws ClassNotFoundException, IOException { + Map item = DynamoUtils.loadItemBySessionId(dynamo, sessionTableName, id); + if (item == null || !item.containsKey(SessionTableAttributes.SESSION_ID_KEY) + || !item.containsKey(SessionTableAttributes.SESSION_DATA_ATTRIBUTE)) { + logger.warn("Unable to load session attributes for session " + id); + return null; + } + + Session session = getManager().createSession(id); + session.setCreationTime(Long.parseLong(item.get(SessionTableAttributes.CREATED_AT_ATTRIBUTE).getN())); + + ByteBuffer byteBuffer = item.get(SessionTableAttributes.SESSION_DATA_ATTRIBUTE).getB(); + ByteArrayInputStream inputStream = new ByteArrayInputStream(copyAllBytesFrom(byteBuffer)); + + Object readObject; + ObjectInputStream objectInputStream = null; + try { + Context webapp = getAssociatedContext(); + objectInputStream = new CustomObjectInputStream(inputStream, webapp.getLoader().getClassLoader()); + + readObject = objectInputStream.readObject(); + } finally { + IOUtils.closeQuietly(objectInputStream, null); + } + + if (readObject instanceof Map) { + @SuppressWarnings("unchecked") + Map sessionAttributeMap = (Map) readObject; + + for (String s : sessionAttributeMap.keySet()) { + ((StandardSession) session).setAttribute(s, sessionAttributeMap.get(s)); + } + } else { + throw new RuntimeException("Error: Unable to unmarshall session attributes from DynamoDB store"); + } + + keys.add(id); + manager.add(session); + + return session; + } + + /** + * 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) getManager().getContainer(); + } catch (ClassCastException e) { + logger.fatal("Unable to cast " + getManager().getClass().getName() + " to a Context." + + " DynamoDB SessionManager can only be used with a Context"); + throw new IllegalStateException(e); + } + } + + @Override + public void save(Session session) throws IOException { + DynamoUtils.storeSession(dynamo, sessionTableName, session); + keys.add(session.getId()); + } + + @Override + public void remove(String id) throws IOException { + DynamoUtils.deleteSession(dynamo, sessionTableName, id); + keys.remove(id); + } } 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 c6716aa..86a843c 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 @@ -53,11 +53,13 @@ */ public class DynamoUtils { - public static Map loadItemBySessionId(AmazonDynamoDB dynamo, String tableName, String sessionId) { + public static Map loadItemBySessionId(AmazonDynamoDB dynamo, + String tableName, + String sessionId) { Map map = newAttributeValueMap(); map.put(SessionTableAttributes.SESSION_ID_KEY, new AttributeValue(sessionId)); GetItemRequest request = new GetItemRequest(tableName, map); - addClientMarker(request); + appendUserAgent(request); try { GetItemResult result = dynamo.getItem(request); @@ -74,7 +76,7 @@ public static void deleteSession(AmazonDynamoDB dynamo, String tableName, String key.put(SessionTableAttributes.SESSION_ID_KEY, new AttributeValue(sessionId)); DeleteItemRequest request = new DeleteItemRequest(tableName, key); - addClientMarker(request); + appendUserAgent(request); try { dynamo.deleteItem(request); @@ -84,7 +86,7 @@ public static void deleteSession(AmazonDynamoDB dynamo, String tableName, String } public static void storeSession(AmazonDynamoDB dynamo, String tableName, Session session) throws IOException { - Map sessionAttributes = new HashMap<>(); + Map sessionAttributes = new HashMap(); HttpSession httpSession = session.getSession(); Enumeration attributeNames = httpSession.getAttributeNames(); @@ -105,12 +107,14 @@ public static void storeSession(AmazonDynamoDB dynamo, String tableName, Session attributes.put(SessionTableAttributes.SESSION_ID_KEY, new AttributeValue(session.getId())); ByteBuffer b = ByteBuffer.wrap(byteArray); attributes.put(SessionTableAttributes.SESSION_DATA_ATTRIBUTE, new AttributeValue().withB(b)); - attributes.put(SessionTableAttributes.CREATED_AT_ATTRIBUTE, new AttributeValue().withN(Long.toString(session.getCreationTime()))); - attributes.put(SessionTableAttributes.LAST_UPDATED_AT_ATTRIBUTE, new AttributeValue().withN(Long.toString(System.currentTimeMillis()))); + attributes.put(SessionTableAttributes.CREATED_AT_ATTRIBUTE, + new AttributeValue().withN(Long.toString(session.getCreationTime()))); + attributes.put(SessionTableAttributes.LAST_UPDATED_AT_ATTRIBUTE, + new AttributeValue().withN(Long.toString(System.currentTimeMillis()))); try { PutItemRequest request = new PutItemRequest(tableName, attributes); - addClientMarker(request); + appendUserAgent(request); dynamo.putItem(request); } catch (Exception e) { DynamoDBSessionManager.error("Unable to save session " + session.getId(), e); @@ -120,13 +124,16 @@ public static void storeSession(AmazonDynamoDB dynamo, String tableName, Session public static boolean doesTableExist(AmazonDynamoDBClient dynamo, String tableName) { try { DescribeTableRequest request = new DescribeTableRequest().withTableName(tableName); - addClientMarker(request); + appendUserAgent(request); TableDescription table = dynamo.describeTable(request).getTable(); return table != null; } catch (AmazonServiceException ase) { - if (ase.getErrorCode().equalsIgnoreCase("ResourceNotFoundException")) return false; - else throw ase; + if (ase.getErrorCode().equalsIgnoreCase("ResourceNotFoundException")) { + return false; + } else { + throw ase; + } } } @@ -136,33 +143,41 @@ public static void waitForTableToBecomeActive(AmazonDynamoDBClient dynamo, Strin while (System.currentTimeMillis() < endTime) { try { DescribeTableRequest request = new DescribeTableRequest().withTableName(tableName); - addClientMarker(request); + appendUserAgent(request); TableDescription tableDescription = dynamo.describeTable(request).getTable(); - if (tableDescription == null) continue; + if (tableDescription == null) { + continue; + } String tableStatus = tableDescription.getTableStatus(); - if (tableStatus.equals(TableStatus.ACTIVE.toString())) return; + if (tableStatus.equals(TableStatus.ACTIVE.toString())) { + return; + } } catch (AmazonServiceException ase) { - if (!ase.getErrorCode().equalsIgnoreCase("ResourceNotFoundException")) + if (!ase.getErrorCode().equalsIgnoreCase("ResourceNotFoundException")) { throw ase; + } } try { Thread.sleep(1000 * 5); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new AmazonClientException( - "Interrupted while waiting for table '" + tableName + "' to become active.", e); + throw new AmazonClientException("Interrupted while waiting for table '" + tableName + + "' to become active.", e); } } throw new AmazonClientException("Table '" + tableName + "' never became active"); } - public static void createSessionTable(AmazonDynamoDBClient dynamo, String tableName, long readCapacityUnits, long writeCapacityUnits) { + public static void createSessionTable(AmazonDynamoDBClient dynamo, + String tableName, + long readCapacityUnits, + long writeCapacityUnits) { CreateTableRequest request = new CreateTableRequest().withTableName(tableName); - addClientMarker(request); + appendUserAgent(request); request.withKeySchema(new KeySchemaElement() .withAttributeName(SessionTableAttributes.SESSION_ID_KEY) @@ -179,11 +194,11 @@ public static void createSessionTable(AmazonDynamoDBClient dynamo, String tableN dynamo.createTable(request); } - public static void addClientMarker(AmazonWebServiceRequest request) { + public static void appendUserAgent(AmazonWebServiceRequest request) { request.getRequestClientOptions().appendUserAgent("DynamoSessionManager/1.0"); } private static Map newAttributeValueMap() { - return new HashMap<>(); + return new HashMap(); } } From ff0d48b1178c02ad817a86359926d8370840fca6 Mon Sep 17 00:00:00 2001 From: Andrew Shore Date: Thu, 2 Apr 2015 19:02:24 +0000 Subject: [PATCH 11/23] Incorporating Pull Request #19 to fix stale sessions from overwriting active sessions. --- pom.xml | 288 ++++++++++-------- .../DynamoDBSessionManager.java | 190 ++++++++---- .../sessionmanager/DynamoDBSessionStore.java | 265 +++++----------- .../sessionmanager/DynamoSessionItem.java | 97 ++++++ .../sessionmanager/DynamoSessionStorage.java | 122 ++++++++ .../sessionmanager/ExpiredSessionReaper.java | 71 +++++ .../ExpiredSessionReaperExecutor.java | 59 ++++ .../DefaultDynamoSessionItemConverter.java | 46 +++ .../DefaultTomcatSessionConverter.java | 59 ++++ .../DynamoSessionItemConverter.java | 28 ++ .../LegacyDynamoDBSessionItemConverter.java | 72 +++++ .../LegacyTomcatSessionConverter.java | 110 +++++++ .../SessionConversionException.java | 28 ++ .../converters/SessionConverter.java | 65 ++++ .../converters/TomcatSessionConverter.java | 27 ++ .../TomcatSessionConverterChain.java | 52 ++++ .../sessionmanager/util/DynamoUtils.java | 187 ++---------- .../sessionmanager/util/ValidatorUtils.java | 10 + .../sessionmanager/CustomAsserts.java | 53 ++++ .../sessionmanager/CustomSessionClass.java | 55 ++++ .../ExpiredSessionReaperTest.java | 63 ++++ .../DefaultSessionConverterTest.java | 72 +++++ .../LegacySessionConverterTest.java | 95 ++++++ .../converters/TestSessionFactory.java | 144 +++++++++ .../TomcatSessionConverterChainTest.java | 69 +++++ 25 files changed, 1792 insertions(+), 535 deletions(-) create mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoSessionItem.java create mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoSessionStorage.java create mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaper.java create mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperExecutor.java create mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultDynamoSessionItemConverter.java create mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultTomcatSessionConverter.java create mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DynamoSessionItemConverter.java create mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacyDynamoDBSessionItemConverter.java create mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacyTomcatSessionConverter.java create mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/SessionConversionException.java create mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/SessionConverter.java create mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverter.java create mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChain.java create mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/util/ValidatorUtils.java create mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/CustomAsserts.java create mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/CustomSessionClass.java create mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperTest.java create mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultSessionConverterTest.java create mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacySessionConverterTest.java create mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TestSessionFactory.java create mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChainTest.java 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 - 1.7 - UTF-8 - - + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3 + + 1.6 + 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> items = scanResult.getItems(); - for (Map item : items) { - list.add(item.get(SESSION_ID_KEY).getS()); - } - } while (scanResult.getLastEvaluatedKey() != null); - - return list; - } - - public static ByteBuffer loadItemBySessionId(AmazonDynamoDB dynamo, String tableName, String sessionId) { - Map key = newAttributeValueMap(); - key.put(SESSION_ID_KEY, new AttributeValue(sessionId)); - GetItemRequest request = new GetItemRequest(tableName, key); - addClientMarker(request); - try { - Map item = dynamo.getItem(request).getItem(); - if (item == null || !item.containsKey(SESSION_ID_KEY) || !item.containsKey(SESSION_DATA_ATTRIBUTE)) { - DynamoDBSessionManager.warn("Unable to load session attributes for session " + sessionId); - return null; - } - return item.get(SESSION_DATA_ATTRIBUTE).getB(); - } catch (Exception e) { - DynamoDBSessionManager.warn("Unable to load session " + sessionId, e); - } - return null; - } - - public static void deleteSession(AmazonDynamoDB dynamo, String tableName, String sessionId) { - Map key = newAttributeValueMap(); - key.put(SESSION_ID_KEY, new AttributeValue(sessionId)); - - DeleteItemRequest request = new DeleteItemRequest(tableName, key); - addClientMarker(request); - - try { - dynamo.deleteItem(request); - } catch (Exception e) { - DynamoDBSessionManager.warn("Unable to delete session " + sessionId, e); - } - } - - public static void storeSession(AmazonDynamoDB dynamo, String tableName, String sessionId, ByteBuffer byteBuffer) - throws IOException { - - Map attributes = newAttributeValueMap(); - attributes.put(SESSION_ID_KEY, new AttributeValue(sessionId)); - attributes.put(SESSION_DATA_ATTRIBUTE, new AttributeValue().withB(byteBuffer)); - - try { - PutItemRequest request = new PutItemRequest(tableName, attributes); - addClientMarker(request); - dynamo.putItem(request); - } catch (Exception e) { - DynamoDBSessionManager.error("Unable to save session " + sessionId, e); - } - } - - public static boolean doesTableExist(AmazonDynamoDBClient dynamo, String tableName) { - try { - DescribeTableRequest request = new DescribeTableRequest().withTableName(tableName); - addClientMarker(request); - - TableDescription table = dynamo.describeTable(request).getTable(); - if (table == null) - return false; - else - return true; - } catch (AmazonServiceException ase) { - if (ase.getErrorCode().equalsIgnoreCase("ResourceNotFoundException")) - return false; - else - throw ase; - } - } - - public static void waitForTableToBecomeActive(AmazonDynamoDBClient dynamo, String tableName) { - long startTime = System.currentTimeMillis(); - long endTime = startTime + (10 * 60 * 1000); - while (System.currentTimeMillis() < endTime) { - try { - DescribeTableRequest request = new DescribeTableRequest().withTableName(tableName); - addClientMarker(request); - - TableDescription tableDescription = dynamo.describeTable(request).getTable(); - if (tableDescription == null) - continue; - - String tableStatus = tableDescription.getTableStatus(); - if (tableStatus.equals(TableStatus.ACTIVE.toString())) - return; - } catch (AmazonServiceException ase) { - if (ase.getErrorCode().equalsIgnoreCase("ResourceNotFoundException") == false) - throw ase; - } - - try { - Thread.sleep(1000 * 5); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new AmazonClientException("Interrupted while waiting for table '" + tableName - + "' to become active.", e); - } - } - - throw new AmazonClientException("Table '" + tableName + "' never became active"); - } - - public static void createSessionTable(AmazonDynamoDBClient dynamo, String tableName, long readCapacityUnits, - long writeCapacityUnits) { - CreateTableRequest request = new CreateTableRequest().withTableName(tableName); - addClientMarker(request); + public static void createSessionTable(AmazonDynamoDBClient dynamo, + String tableName, + long readCapacityUnits, + long writeCapacityUnits) { + CreateTableRequest request = new CreateTableRequest().withTableName(tableName); - request.withKeySchema(new KeySchemaElement().withAttributeName(SESSION_ID_KEY).withKeyType(KeyType.HASH)); + request.withKeySchema(new KeySchemaElement().withAttributeName(DynamoSessionItem.SESSION_ID_ATTRIBUTE_NAME) + .withKeyType(KeyType.HASH)); - request.withAttributeDefinitions(new AttributeDefinition().withAttributeName(SESSION_ID_KEY).withAttributeType( - ScalarAttributeType.S)); + request.withAttributeDefinitions(new AttributeDefinition().withAttributeName( + DynamoSessionItem.SESSION_ID_ATTRIBUTE_NAME).withAttributeType(ScalarAttributeType.S)); - request.setProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(readCapacityUnits) - .withWriteCapacityUnits(writeCapacityUnits)); + request.setProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(readCapacityUnits) + .withWriteCapacityUnits(writeCapacityUnits)); - dynamo.createTable(request); - } + dynamo.createTable(request); + } - public static void addClientMarker(AmazonWebServiceRequest request) { - request.getRequestClientOptions().addClientMarker("DynamoSessionManager/2.0"); - } + /** + * Create a new DynamoDBMapper with table name override + */ + public static DynamoDBMapper createDynamoMapper(AmazonDynamoDBClient dynamoDbClient, String tableName) { + return new DynamoDBMapper(dynamoDbClient, new DynamoDBMapperConfig(new TableNameOverride(tableName))); + } - private static Map newAttributeValueMap() { - return new HashMap(); - } } diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/util/ValidatorUtils.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/util/ValidatorUtils.java new file mode 100644 index 0000000..6523844 --- /dev/null +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/util/ValidatorUtils.java @@ -0,0 +1,10 @@ +package com.amazonaws.services.dynamodb.sessionmanager.util; + +public class ValidatorUtils { + + public static void nonNull(Object obj, String argName) { + if (obj == null) { + throw new IllegalArgumentException(String.format("%s cannot be null", argName)); + } + } +} diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/CustomAsserts.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/CustomAsserts.java new file mode 100644 index 0000000..c3bcca6 --- /dev/null +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/CustomAsserts.java @@ -0,0 +1,53 @@ +/* + * 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 static org.junit.Assert.assertEquals; + +import java.util.Enumeration; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.servlet.http.HttpSession; + +import org.apache.catalina.Session; + +public class CustomAsserts { + + public static void assertSessionEquals(Session expectedSession, Session actualSession) { + assertEquals(expectedSession.getIdInternal(), actualSession.getIdInternal()); + assertEquals(expectedSession.getCreationTimeInternal(), actualSession.getCreationTimeInternal()); + assertEquals(expectedSession.getLastAccessedTimeInternal(), actualSession.getLastAccessedTimeInternal()); + assertSessionDataEquals(expectedSession.getSession(), actualSession.getSession()); + } + + public static void assertSessionDataEquals(HttpSession expectedSession, HttpSession actualSession) { + SortedSet expectedAttributeNames = toSortedSet(expectedSession.getAttributeNames()); + SortedSet actualAttributeNames = toSortedSet(actualSession.getAttributeNames()); + assertEquals(expectedAttributeNames, actualAttributeNames); + for (String attributeName : expectedAttributeNames) { + assertEquals(expectedSession.getAttribute(attributeName), actualSession.getAttribute(attributeName)); + } + } + + private static SortedSet toSortedSet(Enumeration enumeration) { + SortedSet list = new TreeSet(); + while (enumeration.hasMoreElements()) { + list.add(enumeration.nextElement()); + } + return list; + } + +} diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/CustomSessionClass.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/CustomSessionClass.java new file mode 100644 index 0000000..a77c496 --- /dev/null +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/CustomSessionClass.java @@ -0,0 +1,55 @@ +/* + * 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.io.Serializable; + +/** + * Class to test serialization/deserialization of a custom object class in the session data + */ +public class CustomSessionClass implements Serializable { + + private static final long serialVersionUID = -4704592898757232021L; + private final String data; + + public CustomSessionClass(String data) { + this.data = data; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CustomSessionClass other = (CustomSessionClass) obj; + if (data == null) { + if (other.data != null) + return false; + } else if (!data.equals(other.data)) + return false; + return true; + } +} \ No newline at end of file diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperTest.java new file mode 100644 index 0000000..c6f1bea --- /dev/null +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperTest.java @@ -0,0 +1,63 @@ +/* + * 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 static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.amazonaws.services.dynamodb.sessionmanager.converters.LegacyTomcatSessionConverter.LegacySession; +import com.amazonaws.services.dynamodb.sessionmanager.converters.TestSessionFactory; + +public class ExpiredSessionReaperTest { + + @Test + public void isExpired_ActiveSession_ReturnsFalse() { + assertFalse(ExpiredSessionReaper.isExpired(createActiveSession())); + } + + @Test + public void isExpired_ExpiredSession_ReturnsTrue() { + assertTrue(ExpiredSessionReaper.isExpired(createExpiredSession())); + } + + @Test + public void isExpired_ImmortalSession_ReturnsFalse() { + assertFalse(ExpiredSessionReaper.isExpired(createImmortalSession())); + } + + public static LegacySession createActiveSession() { + LegacySession activeSession = new TestSessionFactory().withSessionId("active") + .withLastAccessedTime(System.currentTimeMillis()).createLegacySession(); + return activeSession; + } + + public static LegacySession createExpiredSession() { + LegacySession expiredSession = new TestSessionFactory().withSessionId("expired").withLastAccessedTime(0) + .createLegacySession(); + return expiredSession; + } + + /** + * A negative value for maxInactiveInterval means the session never expires. isExpired should + * always return false no matter what + */ + public static LegacySession createImmortalSession() { + LegacySession immortalSession = new TestSessionFactory().withSessionId("immortal").withMaxInactiveInterval(-1) + .withLastAccessedTime(0).createLegacySession(); + return immortalSession; + } +} diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultSessionConverterTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultSessionConverterTest.java new file mode 100644 index 0000000..d6bb045 --- /dev/null +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultSessionConverterTest.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 static com.amazonaws.services.dynamodb.sessionmanager.CustomAsserts.assertSessionEquals; +import static org.junit.Assert.assertNull; + +import org.apache.catalina.Session; +import org.apache.catalina.session.StandardSession; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests a SessionConverter with Default implementations of TomcatSessionConverter and + * DynamoSessionConverter + */ +public class DefaultSessionConverterTest { + + private static final TestSessionFactory SESSION_TEMPLATE = new TestSessionFactory(); + + private SessionConverter sessionConverter; + private StandardSession session; + + @Before + public void setup() { + sessionConverter = SessionConverter.createDefaultSessionConverter(SESSION_TEMPLATE.getManager(), getClass() + .getClassLoader()); + session = SESSION_TEMPLATE.createStandardSession(); + } + + @Test + public void roundTrip_ReturnsSameSession() throws Exception { + Session roundTripSession = sessionConverter.toSession(sessionConverter.toSessionItem(session)); + assertSessionEquals(session, roundTripSession); + } + + @Test + public void roundTrip_NoSessionData_ReturnsSameSession() throws Exception { + StandardSession session = new TestSessionFactory().withSessionAttributes(null).createStandardSession(); + Session roundTripSession = sessionConverter.toSession(sessionConverter.toSessionItem(session)); + assertSessionEquals(session, roundTripSession); + } + + @Test(expected = SessionConversionException.class) + public void toSessionItem_NullSession_ThrowsSessionConversionException() { + assertNull(sessionConverter.toSessionItem(null)); + } + + @Test(expected = SessionConversionException.class) + public void toSession_NullSessionItem_ThrowsSessionConversionException() { + assertNull(sessionConverter.toSession(null)); + } + + @Test(expected = SessionConversionException.class) + public void toSessionItem_NullManager_ThrowsSessionConversionException() { + session.setManager(null); + sessionConverter.toSessionItem(session); + } + +} diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacySessionConverterTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacySessionConverterTest.java new file mode 100644 index 0000000..6146f55 --- /dev/null +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacySessionConverterTest.java @@ -0,0 +1,95 @@ +/* + * 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 static com.amazonaws.services.dynamodb.sessionmanager.CustomAsserts.assertSessionEquals; +import static org.junit.Assert.assertNull; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.catalina.Session; +import org.apache.catalina.session.StandardSession; +import org.junit.Before; +import org.junit.Test; + +import com.amazonaws.services.dynamodb.sessionmanager.CustomSessionClass; +import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem; + +/** + * Tests a SessionConverter with Legacy implementations of TomcatSessionConverter and + * DynamoSessionConverter + */ +public class LegacySessionConverterTest { + + private static final int MAX_INACTIVE_INTERVAL = 60; + private static final TestSessionFactory SESSION_TEMPLATE = new TestSessionFactory(); + + private SessionConverter sessionConverter; + + @Before + public void setup() { + sessionConverter = SessionConverter.createLegacySessionConverter(SESSION_TEMPLATE.getManager(), getClass() + .getClassLoader(), MAX_INACTIVE_INTERVAL); + } + + /** + * Creates a StandardSession, converts it to a SessionItem and then back again to a + * StandardSession and assert we have the same thing + */ + @Test + public void roundTrip_ReturnsSameSession() throws Exception { + StandardSession session = SESSION_TEMPLATE.createStandardSession(); + Session roundTripSession = sessionConverter.toSession(sessionConverter.toSessionItem(session)); + assertSessionEquals(session, roundTripSession); + } + + @Test + public void roundTrip_NoSessionData_ReturnsSameSession() throws Exception { + StandardSession session = new TestSessionFactory().withSessionAttributes(null).createStandardSession(); + Session roundTripSession = sessionConverter.toSession(sessionConverter.toSessionItem(session)); + assertSessionEquals(session, roundTripSession); + } + + @Test(expected = SessionConversionException.class) + public void toSessionItem_StandardSessionNull_ThrowsSessionConversionException() { + assertNull(sessionConverter.toSessionItem(null)); + } + + @Test(expected = SessionConversionException.class) + public void toSession_SessionItemNull_ThrowsSessionConversionException() { + assertNull(sessionConverter.toSession(null)); + } + + @Test(expected = SessionConversionException.class) + public void toHttpSession_JunkSessionData_ThrowsSessionConversionException() throws IOException { + DynamoSessionItem sessionItem = SESSION_TEMPLATE.createLegacySessionItem(); + sessionItem.setSessionData(ByteBuffer.wrap(new byte[] { 1, 2, 3, 4 })); + sessionConverter.toSession(sessionItem); + } + + /** + * Session Data is expected to be a Map. This tests a session item whose session + * data is just a pojo throws an exception + */ + @Test(expected = SessionConversionException.class) + public void toHttpSession_SessionDataInvalidClass_ThrowsSessionConversionException() throws Exception { + DynamoSessionItem sessionItem = SESSION_TEMPLATE.createLegacySessionItem(); + sessionItem.setSessionData(LegacyDynamoDBSessionItemConverter + .objectToByteBuffer(new CustomSessionClass("data"))); + sessionConverter.toSession(sessionItem); + } + +} diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TestSessionFactory.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TestSessionFactory.java new file mode 100644 index 0000000..18195ef --- /dev/null +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TestSessionFactory.java @@ -0,0 +1,144 @@ +/* + * 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 static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.HashMap; +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 com.amazonaws.services.dynamodb.sessionmanager.CustomSessionClass; +import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem; +import com.amazonaws.services.dynamodb.sessionmanager.converters.LegacyTomcatSessionConverter.LegacySession; + +/** + * Utility class to create new {@link Session} and {@link DynamoSessionItem} object for tests. Has + * suitable defaults for fields that can be changed for individual test classes or cases + */ +public class TestSessionFactory { + + // Initialize fields with some defaults + private String sessionId = "1234"; + private int creationTime = 1234; + private long lastAccessedTime = creationTime; + private int maxInactiveInterval = 30; + private Manager manager = getDefaultManager(); + private Map sessionAttributes = getDefaultSessionAttributes(); + + public String getSessionId() { + return sessionId; + } + + public int getCreationTime() { + return creationTime; + } + + public long getLastAccessedTime() { + return lastAccessedTime; + } + + public int getMaxInactiveInterval() { + return maxInactiveInterval; + } + + public Map getSessionAttributes() { + return sessionAttributes; + } + + public Manager getManager() { + return manager; + } + + public TestSessionFactory withSessionId(String sessionId) { + this.sessionId = sessionId; + return this; + } + + public TestSessionFactory withCreationTime(int creationTime) { + this.creationTime = creationTime; + return this; + } + + public TestSessionFactory withLastAccessedTime(long lastAccessedTime) { + this.lastAccessedTime = lastAccessedTime; + return this; + } + + public TestSessionFactory withMaxInactiveInterval(int maxInactiveInterval) { + this.maxInactiveInterval = maxInactiveInterval; + return this; + } + + public TestSessionFactory withSessionAttributes(Map sessionAttributes) { + this.sessionAttributes = sessionAttributes; + return this; + } + + public TestSessionFactory withManager(Manager manager) { + this.manager = manager; + return this; + } + + public final StandardSession createStandardSession() { + LegacySession session = new LegacySession(null); + session.setValid(true); + session.setId(getSessionId(), false); + session.setCreationTime(getCreationTime()); + session.setMaxInactiveInterval(maxInactiveInterval); + session.setLastAccessedTime(lastAccessedTime); + + Map sessionData = getSessionAttributes(); + if (sessionData != null) { + for (Entry attr : sessionData.entrySet()) { + session.setAttribute(attr.getKey(), attr.getValue(), false); + } + } + session.setManager(getManager()); + return session; + } + + public final LegacySession createLegacySession() { + return (LegacySession) createStandardSession(); + } + + public final DynamoSessionItem createLegacySessionItem() throws IOException { + DynamoSessionItem sessionItem = new DynamoSessionItem(getSessionId()); + sessionItem.setCreatedTime(getCreationTime()); + sessionItem.setLastUpdatedTime(getCreationTime()); + sessionItem.setSessionData(LegacyDynamoDBSessionItemConverter.objectToByteBuffer(getSessionAttributes())); + return sessionItem; + } + + private static Map getDefaultSessionAttributes() { + Map sessionData = new HashMap(); + sessionData.put("someAttribute", new CustomSessionClass("customData")); + return sessionData; + } + + private static Manager getDefaultManager() { + Manager mockManager = mock(Manager.class, RETURNS_DEEP_STUBS); + when(mockManager.getContainer().getLogger().isDebugEnabled()).thenReturn(false); + return mockManager; + } + +} \ No newline at end of file diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChainTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChainTest.java new file mode 100644 index 0000000..b16fe03 --- /dev/null +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChainTest.java @@ -0,0 +1,69 @@ +/* + * 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 static org.junit.Assert.assertEquals; + +import org.apache.catalina.Session; +import org.junit.Test; + +import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem; + +public class TomcatSessionConverterChainTest { + + private static final Session SESSION = new TestSessionFactory().createStandardSession(); + + private class FaultyTomcatSessionConverter implements TomcatSessionConverter { + @Override + public Session toSession(DynamoSessionItem sessionItem) { + throw new SessionConversionException("Unable to convert"); + } + } + + private class WorkingTomcatSessionConverter implements TomcatSessionConverter { + @Override + public Session toSession(DynamoSessionItem sessionItem) { + return SESSION; + } + } + + @Test + public void toSession_FaultyConverterFirst_FallsBackToWorkingConverter() throws Exception { + TomcatSessionConverter converterChain = TomcatSessionConverterChain.wrap(new FaultyTomcatSessionConverter(), + new WorkingTomcatSessionConverter()); + assertEquals(SESSION, converterChain.toSession(null)); + } + + @Test + public void toSession_WorkingConverterFirst_UsesWorkingConverter() throws Exception { + TomcatSessionConverter converterChain = TomcatSessionConverterChain.wrap(new WorkingTomcatSessionConverter(), + new FaultyTomcatSessionConverter()); + assertEquals(SESSION, converterChain.toSession(null)); + } + + @Test(expected = SessionConversionException.class) + public void toSession_AllConvertersFail_ThrowsDynamoSessionConversionException() { + TomcatSessionConverter converterChain = TomcatSessionConverterChain.wrap(new FaultyTomcatSessionConverter(), + new FaultyTomcatSessionConverter()); + converterChain.toSession(null); + } + + @Test(expected = SessionConversionException.class) + public void toSession_NoConverters_ThrowsDynamoSessionConversionException() { + TomcatSessionConverter converterChain = TomcatSessionConverterChain.wrap(); + converterChain.toSession(null); + } + +} From 2de64e49d64cc21b71d7aac1d341f67b20656d7a Mon Sep 17 00:00:00 2001 From: Andrew Shore Date: Thu, 2 Apr 2015 19:05:46 +0000 Subject: [PATCH 12/23] Pull Request #19. Enabling new schema and bumping version to 2.0. --- pom.xml | 2 +- .../DynamoDBSessionManager.java | 58 +++++-------------- .../sessionmanager/util/ValidatorUtils.java | 14 +++++ 3 files changed, 28 insertions(+), 46 deletions(-) diff --git a/pom.xml b/pom.xml index dee08b2..9e063c2 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ aws-dynamodb-session-tomcat jar Amazon DynamoDB Session Manager for Tomcat - 1.0.5 + 2.0.0 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 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 444a792..735d94f 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java @@ -30,8 +30,8 @@ import com.amazonaws.auth.PropertiesCredentials; import com.amazonaws.internal.StaticCredentialsProvider; import com.amazonaws.regions.RegionUtils; +import com.amazonaws.services.dynamodb.sessionmanager.converters.DefaultDynamoSessionItemConverter; 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; @@ -49,9 +49,9 @@ public class DynamoDBSessionManager extends PersistentManagerBase { public static final String DEFAULT_TABLE_NAME = "Tomcat_SessionState"; - private static final String USER_AGENT = "DynamoSessionManager/1.1"; + private static final String USER_AGENT = "DynamoSessionManager/2.0"; private static final String name = "AmazonDynamoDBSessionManager"; - private static final String info = name + "/1.1"; + private static final String info = name + "/2.0"; private String regionId = "us-east-1"; private String endpoint; @@ -87,10 +87,6 @@ public String getName() { return name; } - // - // Context.xml Configuration Members - // - public void setRegionId(String regionId) { this.regionId = regionId; } @@ -135,10 +131,6 @@ public void setProxyPort(Integer proxyPort) { this.proxyPort = proxyPort; } - // - // Private Interface - // - @Override protected void initInternal() throws LifecycleException { this.setDistributable(true); @@ -170,16 +162,16 @@ private AWSCredentialsProvider initCredentials() { if (credentialsInContextConfigAreValid()) { throw new AmazonClientException("Incomplete AWS security credentials specified in context.xml."); } - debug("Using AWS access key ID and secret key from context.xml"); + logger.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 { - debug("Reading security credentials from properties file: " + credentialsFile); + logger.debug("Reading security credentials from properties file: " + credentialsFile); PropertiesCredentials credentials = new PropertiesCredentials(credentialsFile); - debug("Using AWS credentials from file: " + credentialsFile); + logger.debug("Using AWS credentials from file: " + credentialsFile); return new StaticCredentialsProvider(credentials); } catch (Exception e) { throw new AmazonClientException( @@ -191,13 +183,13 @@ private AWSCredentialsProvider initCredentials() { // Fall back to the default credentials chain provider if credentials weren't explicitly set AWSCredentialsProvider defaultChainProvider = new DefaultAWSCredentialsProviderChain(); if (defaultChainProvider.getCredentials() == null) { - debug("Loading security credentials from default credentials provider chain."); + logger.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."); } - debug("Using default AWS credentials provider chain to load credentials"); + logger.debug("Using default AWS credentials provider chain to load credentials"); return defaultChainProvider; } @@ -223,12 +215,12 @@ private ClientConfiguration initClientConfiguration() { // Attempt to use an explicit proxy configuration if (proxyHost != null || proxyPort != null) { - debug("Reading proxy settings from context.xml"); + logger.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"); + logger.debug("Using proxy host and port from context.xml"); clientConfiguration.withProxyHost(proxyHost).withProxyPort(proxyPort); } @@ -260,9 +252,9 @@ private SessionConverter getSessionConverter() { 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()); + // Converter that 'writes' with the new schema but can still read the legacy schema + return new SessionConverter(TomcatSessionConverterChain.wrap(defaultConverter, legacyConverter), + new DefaultDynamoSessionItemConverter()); } /** @@ -283,28 +275,4 @@ private Context getAssociatedContext() { throw new IllegalStateException(e); } } - - // - // Logger Utility Functions - // - - public static void debug(String s) { - logger.debug(s); - } - - public static void warn(String s) { - logger.warn(s); - } - - public static void warn(String s, Exception e) { - logger.warn(s, e); - } - - public static void error(String s) { - logger.error(s); - } - - public static void error(String s, Exception e) { - logger.error(s, e); - } } diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/util/ValidatorUtils.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/util/ValidatorUtils.java index 6523844..70434d6 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/util/ValidatorUtils.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/util/ValidatorUtils.java @@ -1,3 +1,17 @@ +/* + * 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.util; public class ValidatorUtils { From 25b8c9260c23bcaeedbb5745dc45915c6c36f1bd Mon Sep 17 00:00:00 2001 From: Andrew Shore Date: Thu, 30 Apr 2015 20:32:04 +0000 Subject: [PATCH 13/23] Improving logging for TomcatSessionConverterChain. See Issue #24 --- .../converters/TomcatSessionConverterChain.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 index 8838710..5c30bfa 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChain.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChain.java @@ -18,11 +18,15 @@ import java.util.List; import org.apache.catalina.Session; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem; public class TomcatSessionConverterChain implements TomcatSessionConverter { + private static final Log logger = LogFactory.getLog(TomcatSessionConverterChain.class); + private final List tomcatSessionCoverters; private TomcatSessionConverterChain(List tomcatSessionConverters) { @@ -35,11 +39,15 @@ public Session toSession(DynamoSessionItem sessionItem) { try { return converter.toSession(sessionItem); } catch (SessionConversionException e) { + if (logger.isDebugEnabled()) { + logger.debug("Could not convert session with " + converter.getClass().getSimpleName(), e); + } // Try next converter in chain } } throw new SessionConversionException( - "Unable to convert Dynamo storage representation to a Tomcat Session with any converter provided"); + "Unable to convert Dynamo storage representation to a Tomcat Session with any converter provided. " + + "Turn on debug logging to get more detailed information about the actual exception encountered"); } public static TomcatSessionConverter wrap(TomcatSessionConverter... converters) { From ddcbde0605a49bee92261af305cde65c56e62716 Mon Sep 17 00:00:00 2001 From: Shore Date: Mon, 21 Sep 2015 14:55:37 -0700 Subject: [PATCH 14/23] Removing legacy code and adding integration tests --- pom.xml | 28 ++- .../DynamoDBSessionManager.java | 11 +- .../sessionmanager/DynamoSessionItem.java | 40 ---- .../LegacyDynamoDBSessionItemConverter.java | 72 ------- .../LegacyTomcatSessionConverter.java | 110 ---------- .../converters/SessionConverter.java | 11 - .../TomcatSessionConverterChain.java | 60 ------ ...DynamoDBSessionManagerIntegrationTest.java | 190 ++++++++++++++++++ .../ExpiredSessionReaperIntegrationTest.java | 62 ++++++ .../ExpiredSessionReaperTest.java | 20 +- .../SessionStorageIntegrationTest.java | 78 +++++++ .../SessionStorageIntegrationTestBase.java | 80 ++++++++ .../LegacySessionConverterTest.java | 95 --------- .../converters/TestSessionFactory.java | 42 ++-- .../TomcatSessionConverterChainTest.java | 69 ------- 15 files changed, 475 insertions(+), 493 deletions(-) delete mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacyDynamoDBSessionItemConverter.java delete mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacyTomcatSessionConverter.java delete mode 100644 src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChain.java create mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java create mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperIntegrationTest.java create mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTest.java create mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTestBase.java delete mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacySessionConverterTest.java delete mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChainTest.java diff --git a/pom.xml b/pom.xml index 9e063c2..5006eec 100644 --- a/pom.xml +++ b/pom.xml @@ -37,12 +37,12 @@ com.amazonaws aws-java-sdk-dynamodb - 1.9.23 + 1.10.20 org.apache.tomcat tomcat-catalina - 7.0.42 + 7.0.64 provided @@ -57,6 +57,30 @@ 4.12 test + + com.amazonaws + aws-java-sdk-test-utils + 1.10.20 + test + + + org.hamcrest + hamcrest-all + 1.3 + test + + + org.apache.tomcat.embed + tomcat-embed-core + 7.0.64 + test + + + org.apache.tomcat.embed + tomcat-embed-jasper + 7.0.64 + test + 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 735d94f..e7460a9 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java @@ -30,11 +30,7 @@ import com.amazonaws.auth.PropertiesCredentials; import com.amazonaws.internal.StaticCredentialsProvider; import com.amazonaws.regions.RegionUtils; -import com.amazonaws.services.dynamodb.sessionmanager.converters.DefaultDynamoSessionItemConverter; -import com.amazonaws.services.dynamodb.sessionmanager.converters.DefaultTomcatSessionConverter; -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; @@ -249,12 +245,7 @@ private DynamoSessionStorage createSessionStorage(AmazonDynamoDBClient dynamoCli private SessionConverter getSessionConverter() { ClassLoader classLoader = getAssociatedContext().getLoader().getClassLoader(); - LegacyTomcatSessionConverter legacyConverter = new LegacyTomcatSessionConverter(this, classLoader, - maxInactiveInterval); - DefaultTomcatSessionConverter defaultConverter = new DefaultTomcatSessionConverter(this, classLoader); - // Converter that 'writes' with the new schema but can still read the legacy schema - return new SessionConverter(TomcatSessionConverterChain.wrap(defaultConverter, legacyConverter), - new DefaultDynamoSessionItemConverter()); + return SessionConverter.createDefaultSessionConverter(this, classLoader); } /** diff --git a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoSessionItem.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoSessionItem.java index 23fd5c8..27ca862 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoSessionItem.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoSessionItem.java @@ -25,16 +25,10 @@ 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() { } @@ -60,38 +54,4 @@ 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/converters/LegacyDynamoDBSessionItemConverter.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacyDynamoDBSessionItemConverter.java deleted file mode 100644 index 4e2f3f3..0000000 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacyDynamoDBSessionItemConverter.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 deleted file mode 100644 index 07787f0..0000000 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacyTomcatSessionConverter.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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/SessionConverter.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/SessionConverter.java index 07ad012..436bb19 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/SessionConverter.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/SessionConverter.java @@ -51,15 +51,4 @@ public static SessionConverter createDefaultSessionConverter(Manager manager, Cl 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/TomcatSessionConverterChain.java b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChain.java deleted file mode 100644 index 5c30bfa..0000000 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChain.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 org.apache.juli.logging.Log; -import org.apache.juli.logging.LogFactory; - -import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem; - -public class TomcatSessionConverterChain implements TomcatSessionConverter { - - private static final Log logger = LogFactory.getLog(TomcatSessionConverterChain.class); - - 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) { - if (logger.isDebugEnabled()) { - logger.debug("Could not convert session with " + converter.getClass().getSimpleName(), e); - } - // Try next converter in chain - } - } - throw new SessionConversionException( - "Unable to convert Dynamo storage representation to a Tomcat Session with any converter provided. " - + "Turn on debug logging to get more detailed information about the actual exception encountered"); - } - - 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/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java new file mode 100644 index 0000000..7274c31 --- /dev/null +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java @@ -0,0 +1,190 @@ +/* + * Copyright 2015 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; + +import org.apache.catalina.Context; +import org.apache.catalina.Session; +import org.apache.catalina.startup.Tomcat; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.amazonaws.AmazonServiceException; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; +import com.amazonaws.services.dynamodbv2.model.DescribeTableRequest; +import com.amazonaws.services.dynamodbv2.model.TableDescription; +import com.amazonaws.test.AWSTestBase; + +public class DynamoDBSessionManagerIntegrationTest extends AWSTestBase { + + private String sessionTableName; + private static AmazonDynamoDBClient dynamo; + private static Tomcat tomcat; + private static Context webapp; + + /** Starts up an embedded Tomcat process for testing. */ + @BeforeClass + public static void setupFixture() throws Exception { + setUpCredentials(); + dynamo = new AmazonDynamoDBClient(credentials); + + String workingDir = System.getProperty("java.io.tmpdir"); + File webappDirectory = Files.createTempDirectory(Paths.get(workingDir), null).toFile(); + webappDirectory.deleteOnExit(); + + tomcat = new Tomcat(); + tomcat.setPort(0); + tomcat.setBaseDir(workingDir); + tomcat.getHost().setAppBase(workingDir); + tomcat.getHost().setAutoDeploy(true); + tomcat.getHost().setDeployOnStartup(true); + webapp = tomcat.addWebapp("/", webappDirectory.getAbsolutePath()); + + tomcat.start(); + } + + @Before + public void setup() { + sessionTableName = "sessions-test-" + System.currentTimeMillis(); + } + + @After + public void tearDown() { + dynamo.deleteTable(sessionTableName); + } + + @AfterClass + public static void tearDownFixture() throws Exception { + tomcat.stop(); + tomcat.destroy(); + } + + /** Tests that we can use explicitly provided credentials. */ + @Test + public void testExplicitCredentials() throws Exception { + assertFalse(doesTableExist(sessionTableName)); + + DynamoDBSessionManager sessionManager = new DynamoDBSessionManager(); + configureWithExplicitCredentials(sessionManager); + + assertTrue(doesTableExist(sessionTableName)); + } + + /** Tests that we can load credentials from a configured properties file. */ + @Test + public void testCredentialsFile() throws Exception { + assertFalse(doesTableExist(sessionTableName)); + + DynamoDBSessionManager sessionManager = new DynamoDBSessionManager(); + sessionManager.setAwsCredentialsFile(System.getProperty("user.home") + "/.aws/awsTestAccount.properties"); + sessionManager.setTable(sessionTableName); + webapp.setManager(sessionManager); + + assertTrue(doesTableExist(sessionTableName)); + } + + /** + * Bug in the deserialization of sessions was causing persisted sessions loaded via the + * processExpires method to replace the active session in memory by incorrectly registering it + * with the manager. This tests makes sure that any sessions loaded by process expires do not + * affect the attributes of active sessions. + * + * @see https://github.com.aws/aws-dynamodb-session-tomcat/pull/19 + */ + @Test + public void swappedOutSessionsDoNotReplaceActiveSessionDuringProcessExpires() throws InterruptedException { + TestDynamoDBSessionManager sessionManager = new TestDynamoDBSessionManager(); + configureWithExplicitCredentials(sessionManager); + + final String sessionId = "1234"; + final int maxIdleBackupSeconds = 5; + final String attrName = "someAttr"; + final String originalAttrValue = "1"; + final String newAttrValue = "2"; + + // Create a session and idle it so it's persisted to Dynamo + sessionManager.setMaxIdleBackup(maxIdleBackupSeconds); + Session newSession = sessionManager.createSession(sessionId); + setSessionAttribute(newSession, attrName, originalAttrValue); + Thread.sleep(TimeUnit.MILLISECONDS.convert(maxIdleBackupSeconds + 1, TimeUnit.SECONDS)); + // Force session manager to persist sessions that have idled + sessionManager.reallyProcessExpires(); + + // Set a new value for the attribute that will only exist in memory + setSessionAttribute(newSession, attrName, newAttrValue); + // Force session manager to load in persisted sessions to prune them if expired + sessionManager.reallyProcessExpires(); + + // The active session in memory should not be affected by the sessions loaded during + // processExpires + assertEquals(newAttrValue, sessionManager.getSessionAttribute(sessionId, attrName)); + } + + private void configureWithExplicitCredentials(DynamoDBSessionManager sessionManager) { + sessionManager.setAwsAccessKey(credentials.getAWSAccessKeyId()); + sessionManager.setAwsSecretKey(credentials.getAWSSecretKey()); + sessionManager.setTable(sessionTableName); + webapp.setManager(sessionManager); + } + + private void setSessionAttribute(Session newSession, String attrName, Object obj) { + newSession.access(); + newSession.getSession().setAttribute(attrName, obj); + newSession.endAccess(); + } + + /** + * Returns true if the specified table exists, and is active and ready for use. + */ + private static boolean doesTableExist(String tableName) { + try { + TableDescription table = dynamo.describeTable(new DescribeTableRequest().withTableName(tableName)) + .getTable(); + return "ACTIVE".equals(table.getTableStatus()); + } catch (AmazonServiceException ase) { + if (ase.getErrorCode().equals("ResourceNotFoundException")) { + return false; + } + throw ase; + } + } + + /** + * Subclassed DynamoDBSessionManager to allow explicit calling of processExpires + */ + private class TestDynamoDBSessionManager extends DynamoDBSessionManager { + + @Override + public void processExpires() { + // Do nothing, processExpires is called internally periodically and we want to prevent + // that so we can call processExpires when we need to in the tests + } + + public void reallyProcessExpires() { + super.processExpires(); + } + } +} diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperIntegrationTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperIntegrationTest.java new file mode 100644 index 0000000..f862146 --- /dev/null +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperIntegrationTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015 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 static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.apache.catalina.Session; +import org.junit.Before; +import org.junit.Test; + +import com.amazonaws.services.dynamodb.sessionmanager.converters.SessionConverter; +import com.amazonaws.services.dynamodb.sessionmanager.converters.TestSessionFactory; +import com.amazonaws.services.dynamodb.sessionmanager.converters.TestSessionFactory.TestStandardSession; + +public class ExpiredSessionReaperIntegrationTest extends SessionStorageIntegrationTestBase { + + private DynamoSessionStorage sessionStorage; + + @Before + public void setup() { + sessionStorage = createSessionStorage(SessionConverter.createDefaultSessionConverter( + new TestSessionFactory().getManager(), getClass().getClassLoader())); + } + + /** + * Integration test for ExpiredSessionReaper. Makes sure expired sessions are deleted from the + * DynamoDB table and non-expired sessions are not deleted + */ + @Test + public void testSessionReaping() { + TestStandardSession activeSession = ExpiredSessionReaperTest.createActiveSession(); + TestStandardSession expiredSession = ExpiredSessionReaperTest.createExpiredSession(); + TestStandardSession immortalSession = ExpiredSessionReaperTest.createImmortalSession(); + saveSessions(sessionStorage, activeSession, expiredSession, immortalSession); + + new ExpiredSessionReaper(sessionStorage).run(); + + assertNotNull(sessionStorage.loadSession(activeSession.getId())); + assertNull(sessionStorage.loadSession(expiredSession.getId())); + assertNotNull(sessionStorage.loadSession(immortalSession.getId())); + } + + private void saveSessions(DynamoSessionStorage sessionStorage, Session... sessions) { + for (Session session : sessions) { + sessionStorage.saveSession(session); + } + } + +} diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperTest.java index c6f1bea..3e1d35b 100644 --- a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperTest.java +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperTest.java @@ -19,8 +19,8 @@ import org.junit.Test; -import com.amazonaws.services.dynamodb.sessionmanager.converters.LegacyTomcatSessionConverter.LegacySession; import com.amazonaws.services.dynamodb.sessionmanager.converters.TestSessionFactory; +import com.amazonaws.services.dynamodb.sessionmanager.converters.TestSessionFactory.TestStandardSession; public class ExpiredSessionReaperTest { @@ -39,15 +39,15 @@ public void isExpired_ImmortalSession_ReturnsFalse() { assertFalse(ExpiredSessionReaper.isExpired(createImmortalSession())); } - public static LegacySession createActiveSession() { - LegacySession activeSession = new TestSessionFactory().withSessionId("active") - .withLastAccessedTime(System.currentTimeMillis()).createLegacySession(); + public static TestStandardSession createActiveSession() { + TestStandardSession activeSession = new TestSessionFactory().withSessionId("active") + .withLastAccessedTime(System.currentTimeMillis()).createTestStandardSession(); return activeSession; } - public static LegacySession createExpiredSession() { - LegacySession expiredSession = new TestSessionFactory().withSessionId("expired").withLastAccessedTime(0) - .createLegacySession(); + public static TestStandardSession createExpiredSession() { + TestStandardSession expiredSession = new TestSessionFactory().withSessionId("expired").withLastAccessedTime(0) + .createTestStandardSession(); return expiredSession; } @@ -55,9 +55,9 @@ public static LegacySession createExpiredSession() { * A negative value for maxInactiveInterval means the session never expires. isExpired should * always return false no matter what */ - public static LegacySession createImmortalSession() { - LegacySession immortalSession = new TestSessionFactory().withSessionId("immortal").withMaxInactiveInterval(-1) - .withLastAccessedTime(0).createLegacySession(); + public static TestStandardSession createImmortalSession() { + TestStandardSession immortalSession = new TestSessionFactory().withSessionId("immortal").withMaxInactiveInterval(-1) + .withLastAccessedTime(0).createTestStandardSession(); return immortalSession; } } diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTest.java new file mode 100644 index 0000000..863f4e0 --- /dev/null +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015 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 static com.amazonaws.services.dynamodb.sessionmanager.CustomAsserts.assertSessionEquals; +import static org.hamcrest.Matchers.emptyIterable; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; + +import org.apache.catalina.Session; +import org.junit.Before; +import org.junit.Test; + +import com.amazonaws.services.dynamodb.sessionmanager.converters.SessionConverter; +import com.amazonaws.services.dynamodb.sessionmanager.converters.TestSessionFactory; + +public class SessionStorageIntegrationTest extends SessionStorageIntegrationTestBase { + + private static DynamoSessionStorage sessionStorage; + private static final TestSessionFactory SESSION_FACTORY = new TestSessionFactory(); + + @Before + public void setup() throws Exception { + sessionStorage = createSessionStorage(SessionConverter.createDefaultSessionConverter( + SESSION_FACTORY.getManager(), getClass().getClassLoader())); + } + + @Test + public void saveSession_ValidSession() { + // First create a new session and persist it to DynamoDB + Session session = SESSION_FACTORY.createStandardSession(); + final String sessionId = session.getId(); + sessionStorage.saveSession(session); + + // Make sure we can load the session we just saved + Session loadedSession = sessionStorage.loadSession(sessionId); + assertSessionEquals(session, loadedSession); + + // Now delete the session we saved and make sure it's gone + sessionStorage.deleteSession(sessionId); + assertNull(sessionStorage.loadSession(sessionId)); + } + + @Test + public void listSessions_NoSessionsInTable_ReturnsEmptyIterable() { + Iterable sessions = sessionStorage.listSessions(); + assertNotNull(sessions); + assertThat(sessions, emptyIterable()); + } + + @Test + public void countSessions_NoSessionsInTable_ReturnsZero() { + assertEquals(0, sessionStorage.count()); + } + + @Test + public void countSessions_OneSessionInTable_ReturnsOne() { + Session session = SESSION_FACTORY.createStandardSession(); + sessionStorage.saveSession(session); + assertEquals(1, sessionStorage.count()); + sessionStorage.deleteSession(session.getId()); + } + +} diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTestBase.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTestBase.java new file mode 100644 index 0000000..6385ca1 --- /dev/null +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTestBase.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015 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 static org.hamcrest.Matchers.empty; +import static org.junit.Assert.assertThat; + +import java.util.List; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import com.amazonaws.services.dynamodb.sessionmanager.converters.SessionConverter; +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.datamodeling.DynamoDBMapper.FailedBatch; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression; +import com.amazonaws.services.dynamodbv2.util.Tables; +import com.amazonaws.test.AWSTestBase; + +/** + * Base class for tests interacting directly with DynamoDB. Creates a unique table per test class + */ +public class SessionStorageIntegrationTestBase extends AWSTestBase { + + private static AmazonDynamoDBClient dynamoClient; + private static DynamoDBMapper dynamoMapper; + private static String tableName; + + @BeforeClass + public static final void baseSetupFixture() throws Exception { + setUpCredentials(); + dynamoClient = new AmazonDynamoDBClient(credentials); + tableName = getUniqueTableName(); + DynamoUtils.createSessionTable(dynamoClient, tableName, 10L, 10L); + Tables.waitForTableToBecomeActive(dynamoClient, tableName); + dynamoMapper = DynamoUtils.createDynamoMapper(dynamoClient, tableName); + } + + /** + * Delete all items in the table before running the next test. Faster than creating a new table + * for every test. + */ + @After + public void baseTearDown() { + List failedBatches = dynamoMapper.batchDelete(dynamoMapper.scan(DynamoSessionItem.class, + new DynamoDBScanExpression())); + // If we for some reason couldn't delete all items bail out so we don't affect other tests + assertThat(failedBatches, empty()); + } + + @AfterClass + public static final void baseTearDownFixture() { + dynamoClient.deleteTable(tableName); + } + + private static String getUniqueTableName() { + return String.format("%s%s-%d", SessionStorageIntegrationTestBase.class.getSimpleName(), "IntegrationTest", + System.currentTimeMillis()); + } + + protected DynamoSessionStorage createSessionStorage(SessionConverter sessionConverter) { + return new DynamoSessionStorage(dynamoMapper, sessionConverter); + } + +} diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacySessionConverterTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacySessionConverterTest.java deleted file mode 100644 index 6146f55..0000000 --- a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/LegacySessionConverterTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 static com.amazonaws.services.dynamodb.sessionmanager.CustomAsserts.assertSessionEquals; -import static org.junit.Assert.assertNull; - -import java.io.IOException; -import java.nio.ByteBuffer; - -import org.apache.catalina.Session; -import org.apache.catalina.session.StandardSession; -import org.junit.Before; -import org.junit.Test; - -import com.amazonaws.services.dynamodb.sessionmanager.CustomSessionClass; -import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem; - -/** - * Tests a SessionConverter with Legacy implementations of TomcatSessionConverter and - * DynamoSessionConverter - */ -public class LegacySessionConverterTest { - - private static final int MAX_INACTIVE_INTERVAL = 60; - private static final TestSessionFactory SESSION_TEMPLATE = new TestSessionFactory(); - - private SessionConverter sessionConverter; - - @Before - public void setup() { - sessionConverter = SessionConverter.createLegacySessionConverter(SESSION_TEMPLATE.getManager(), getClass() - .getClassLoader(), MAX_INACTIVE_INTERVAL); - } - - /** - * Creates a StandardSession, converts it to a SessionItem and then back again to a - * StandardSession and assert we have the same thing - */ - @Test - public void roundTrip_ReturnsSameSession() throws Exception { - StandardSession session = SESSION_TEMPLATE.createStandardSession(); - Session roundTripSession = sessionConverter.toSession(sessionConverter.toSessionItem(session)); - assertSessionEquals(session, roundTripSession); - } - - @Test - public void roundTrip_NoSessionData_ReturnsSameSession() throws Exception { - StandardSession session = new TestSessionFactory().withSessionAttributes(null).createStandardSession(); - Session roundTripSession = sessionConverter.toSession(sessionConverter.toSessionItem(session)); - assertSessionEquals(session, roundTripSession); - } - - @Test(expected = SessionConversionException.class) - public void toSessionItem_StandardSessionNull_ThrowsSessionConversionException() { - assertNull(sessionConverter.toSessionItem(null)); - } - - @Test(expected = SessionConversionException.class) - public void toSession_SessionItemNull_ThrowsSessionConversionException() { - assertNull(sessionConverter.toSession(null)); - } - - @Test(expected = SessionConversionException.class) - public void toHttpSession_JunkSessionData_ThrowsSessionConversionException() throws IOException { - DynamoSessionItem sessionItem = SESSION_TEMPLATE.createLegacySessionItem(); - sessionItem.setSessionData(ByteBuffer.wrap(new byte[] { 1, 2, 3, 4 })); - sessionConverter.toSession(sessionItem); - } - - /** - * Session Data is expected to be a Map. This tests a session item whose session - * data is just a pojo throws an exception - */ - @Test(expected = SessionConversionException.class) - public void toHttpSession_SessionDataInvalidClass_ThrowsSessionConversionException() throws Exception { - DynamoSessionItem sessionItem = SESSION_TEMPLATE.createLegacySessionItem(); - sessionItem.setSessionData(LegacyDynamoDBSessionItemConverter - .objectToByteBuffer(new CustomSessionClass("data"))); - sessionConverter.toSession(sessionItem); - } - -} diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TestSessionFactory.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TestSessionFactory.java index 18195ef..0c69efe 100644 --- a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TestSessionFactory.java +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TestSessionFactory.java @@ -18,7 +18,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @@ -29,7 +28,6 @@ import com.amazonaws.services.dynamodb.sessionmanager.CustomSessionClass; import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem; -import com.amazonaws.services.dynamodb.sessionmanager.converters.LegacyTomcatSessionConverter.LegacySession; /** * Utility class to create new {@link Session} and {@link DynamoSessionItem} object for tests. Has @@ -100,7 +98,7 @@ public TestSessionFactory withManager(Manager manager) { } public final StandardSession createStandardSession() { - LegacySession session = new LegacySession(null); + TestStandardSession session = new TestStandardSession(null); session.setValid(true); session.setId(getSessionId(), false); session.setCreationTime(getCreationTime()); @@ -117,16 +115,8 @@ public final StandardSession createStandardSession() { return session; } - public final LegacySession createLegacySession() { - return (LegacySession) createStandardSession(); - } - - public final DynamoSessionItem createLegacySessionItem() throws IOException { - DynamoSessionItem sessionItem = new DynamoSessionItem(getSessionId()); - sessionItem.setCreatedTime(getCreationTime()); - sessionItem.setLastUpdatedTime(getCreationTime()); - sessionItem.setSessionData(LegacyDynamoDBSessionItemConverter.objectToByteBuffer(getSessionAttributes())); - return sessionItem; + public final TestStandardSession createTestStandardSession() { + return (TestStandardSession) createStandardSession(); } private static Map getDefaultSessionAttributes() { @@ -141,4 +131,28 @@ private static Manager getDefaultManager() { return mockManager; } -} \ No newline at end of file + /** + * Subclassed standard session to allow setting lastAccessedTime through means other than + * readObject + */ + public static class TestStandardSession extends StandardSession { + + private static final long serialVersionUID = 9163946735192227235L; + + public TestStandardSession(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/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChainTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChainTest.java deleted file mode 100644 index b16fe03..0000000 --- a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TomcatSessionConverterChainTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 static org.junit.Assert.assertEquals; - -import org.apache.catalina.Session; -import org.junit.Test; - -import com.amazonaws.services.dynamodb.sessionmanager.DynamoSessionItem; - -public class TomcatSessionConverterChainTest { - - private static final Session SESSION = new TestSessionFactory().createStandardSession(); - - private class FaultyTomcatSessionConverter implements TomcatSessionConverter { - @Override - public Session toSession(DynamoSessionItem sessionItem) { - throw new SessionConversionException("Unable to convert"); - } - } - - private class WorkingTomcatSessionConverter implements TomcatSessionConverter { - @Override - public Session toSession(DynamoSessionItem sessionItem) { - return SESSION; - } - } - - @Test - public void toSession_FaultyConverterFirst_FallsBackToWorkingConverter() throws Exception { - TomcatSessionConverter converterChain = TomcatSessionConverterChain.wrap(new FaultyTomcatSessionConverter(), - new WorkingTomcatSessionConverter()); - assertEquals(SESSION, converterChain.toSession(null)); - } - - @Test - public void toSession_WorkingConverterFirst_UsesWorkingConverter() throws Exception { - TomcatSessionConverter converterChain = TomcatSessionConverterChain.wrap(new WorkingTomcatSessionConverter(), - new FaultyTomcatSessionConverter()); - assertEquals(SESSION, converterChain.toSession(null)); - } - - @Test(expected = SessionConversionException.class) - public void toSession_AllConvertersFail_ThrowsDynamoSessionConversionException() { - TomcatSessionConverter converterChain = TomcatSessionConverterChain.wrap(new FaultyTomcatSessionConverter(), - new FaultyTomcatSessionConverter()); - converterChain.toSession(null); - } - - @Test(expected = SessionConversionException.class) - public void toSession_NoConverters_ThrowsDynamoSessionConversionException() { - TomcatSessionConverter converterChain = TomcatSessionConverterChain.wrap(); - converterChain.toSession(null); - } - -} From 08d9bda7a0a3a35d4ef21b949080fe2b5af6e8eb Mon Sep 17 00:00:00 2001 From: Shore Date: Mon, 21 Sep 2015 22:07:04 -0700 Subject: [PATCH 15/23] Configuration option to delete corrupt or invalid sessions. Issue #30 --- .../DynamoDBSessionManager.java | 17 ++- .../sessionmanager/DynamoDBSessionStore.java | 33 +++++- .../sessionmanager/util/DynamoUtils.java | 5 +- ...DynamoDBSessionManagerIntegrationTest.java | 108 ++++++++++++++++-- .../DynamoDBSessionStoreTest.java | 94 +++++++++++++++ .../ExpiredSessionReaperIntegrationTest.java | 4 +- .../ExpiredSessionReaperTest.java | 4 +- .../SessionStorageIntegrationTest.java | 4 +- .../SessionStorageIntegrationTestBase.java | 4 +- .../DefaultSessionConverterTest.java | 4 +- 10 files changed, 246 insertions(+), 31 deletions(-) create mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStoreTest.java 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 e7460a9..ea4c8ac 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java @@ -60,6 +60,7 @@ public class DynamoDBSessionManager extends PersistentManagerBase { private String tableName = DEFAULT_TABLE_NAME; private String proxyHost; private Integer proxyPort; + private boolean deleteCorruptSessions = false; private static final Log logger = LogFactory.getLog(DynamoDBSessionManager.class); @@ -127,6 +128,10 @@ public void setProxyPort(Integer proxyPort) { this.proxyPort = proxyPort; } + public void setDeleteCorruptSessions(boolean deleteCorruptSessions) { + this.deleteCorruptSessions = deleteCorruptSessions; + } + @Override protected void initInternal() throws LifecycleException { this.setDistributable(true); @@ -134,7 +139,7 @@ protected void initInternal() throws LifecycleException { AmazonDynamoDBClient dynamoClient = createDynamoClient(); initDynamoTable(dynamoClient); DynamoSessionStorage sessionStorage = createSessionStorage(dynamoClient); - setStore(new DynamoDBSessionStore(sessionStorage)); + setStore(new DynamoDBSessionStore(sessionStorage, deleteCorruptSessions)); new ExpiredSessionReaperExecutor(new ExpiredSessionReaper(sessionStorage)); } @@ -172,7 +177,8 @@ private AWSCredentialsProvider initCredentials() { } catch (Exception e) { throw new AmazonClientException( "Unable to read AWS security credentials from file specified in context.xml: " - + credentialsFile, e); + + credentialsFile, + e); } } @@ -180,10 +186,9 @@ private AWSCredentialsProvider initCredentials() { AWSCredentialsProvider defaultChainProvider = new DefaultAWSCredentialsProviderChain(); if (defaultChainProvider.getCredentials() == null) { logger.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."); + throw new AmazonClientException("Unable to 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."); } logger.debug("Using default AWS credentials provider chain to load credentials"); return defaultChainProvider; 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 3b97dc1..c409240 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2015 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. @@ -24,6 +24,7 @@ import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; +import com.amazonaws.services.dynamodb.sessionmanager.converters.SessionConversionException; import com.amazonaws.services.dynamodb.sessionmanager.util.ValidatorUtils; /** @@ -37,10 +38,12 @@ public class DynamoDBSessionStore extends StoreBase { private final Set sessionIds = Collections.synchronizedSet(new HashSet()); private final DynamoSessionStorage sessionStorage; + private final boolean deleteCorruptSessions; - public DynamoDBSessionStore(DynamoSessionStorage sessionStorage) { + public DynamoDBSessionStore(DynamoSessionStorage sessionStorage, boolean deleteCorruptSessions) { ValidatorUtils.nonNull(sessionStorage, "SessionStorage"); this.sessionStorage = sessionStorage; + this.deleteCorruptSessions = deleteCorruptSessions; } @Override @@ -81,7 +84,7 @@ public String[] keys() throws IOException { @Override public Session load(String id) throws ClassNotFoundException, IOException { - Session session = sessionStorage.loadSession(id); + Session session = tryLoadSession(id); if (session == null) { logger.warn("Unable to load session with id " + id); return null; @@ -103,4 +106,28 @@ public void remove(String id) throws IOException { sessionIds.remove(id); } + private Session tryLoadSession(String id) { + try { + return sessionStorage.loadSession(id); + } catch (SessionConversionException e) { + if (deleteCorruptSessions) { + deleteCorruptSession(id, e); + } + } + return null; + } + + /** + * Delete corrupt session from Dynamo if configured to do so. + * + * @param id + * ID of session to delete + * @param e + * Exception that caused the session to fail to deserialize + */ + private void deleteCorruptSession(String id, SessionConversionException e) { + logger.warn("Unable to load session with id " + id + ". Deleting from session store", e); + sessionStorage.deleteSession(id); + } + } 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 54b7423..85a01ee 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 @@ -37,8 +37,9 @@ public static void createSessionTable(AmazonDynamoDBClient dynamo, request.withKeySchema(new KeySchemaElement().withAttributeName(DynamoSessionItem.SESSION_ID_ATTRIBUTE_NAME) .withKeyType(KeyType.HASH)); - request.withAttributeDefinitions(new AttributeDefinition().withAttributeName( - DynamoSessionItem.SESSION_ID_ATTRIBUTE_NAME).withAttributeType(ScalarAttributeType.S)); + request.withAttributeDefinitions( + new AttributeDefinition().withAttributeName(DynamoSessionItem.SESSION_ID_ATTRIBUTE_NAME) + .withAttributeType(ScalarAttributeType.S)); request.setProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(readCapacityUnits) .withWriteCapacityUnits(writeCapacityUnits)); diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java index 7274c31..8f03a94 100644 --- a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java @@ -16,11 +16,15 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.File; +import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.catalina.Context; @@ -34,17 +38,26 @@ import com.amazonaws.AmazonServiceException; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; +import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.amazonaws.services.dynamodbv2.model.DescribeTableRequest; +import com.amazonaws.services.dynamodbv2.model.GetItemResult; +import com.amazonaws.services.dynamodbv2.model.PutItemRequest; import com.amazonaws.services.dynamodbv2.model.TableDescription; import com.amazonaws.test.AWSTestBase; +import com.amazonaws.util.ImmutableMapParameter; public class DynamoDBSessionManagerIntegrationTest extends AWSTestBase { - private String sessionTableName; + private static final String SESSION_ID = "1234"; + private static final int MAX_IDLE_BACKUP_SECONDS = 1; + private static final String ATTR_NAME = "someAttr"; + private static AmazonDynamoDBClient dynamo; private static Tomcat tomcat; private static Context webapp; + private String sessionTableName; + /** Starts up an embedded Tomcat process for testing. */ @BeforeClass public static void setupFixture() throws Exception { @@ -106,6 +119,83 @@ public void testCredentialsFile() throws Exception { assertTrue(doesTableExist(sessionTableName)); } + /** + * Tests the roundtrip journey of a session from memory to Dynamo and back to memory. + */ + @Test + public void sessionSwappedOutToDynamo_IsUnchangedWhenSwappedBackIn() throws Exception { + final String attrValue = "SOME_VALUE"; + + TestDynamoDBSessionManager sessionManager = new TestDynamoDBSessionManager(); + sessionManager.setDeleteCorruptSessions(true); + // MaxIdleSwap needs to be set too as we want it completely out of memory before loading + sessionManager.setMaxIdleBackup(MAX_IDLE_BACKUP_SECONDS); + sessionManager.setMaxIdleSwap(MAX_IDLE_BACKUP_SECONDS); + configureWithExplicitCredentials(sessionManager); + + Session originalSession = sessionManager.createSession(SESSION_ID); + final long originalCreationTime = originalSession.getCreationTime(); + originalSession.getSession().setAttribute(ATTR_NAME, attrValue); + + // Make sure it's out of Tomcat's in memory store and persisted to Dynamo + Thread.sleep(TimeUnit.MILLISECONDS.convert(MAX_IDLE_BACKUP_SECONDS + 1, TimeUnit.SECONDS)); + sessionManager.reallyProcessExpires(); + + // Force session manager to load back in non-expired sessions into memory and validate + // nothing important has changed + sessionManager.load(); + assertEquals(attrValue, sessionManager.getSession(SESSION_ID).get(ATTR_NAME)); + assertEquals(originalCreationTime, sessionManager.getCreationTimestamp(SESSION_ID)); + } + + @Test + public void deleteCorruptSessionsEnabled_DeletesNonSerializableSessions() throws InterruptedException { + GetItemResult result = saveAndTamperWithSession(true); + assertNull(result.getItem()); + } + + @Test + public void deleteCorruptSessionsDisabled_DoesNotDeleteNonSerializableSessions() throws InterruptedException { + GetItemResult result = saveAndTamperWithSession(false); + assertNotNull(result.getItem()); + } + + /** + * Creates a new Session, makes sure it's backed up in Dynamo, tampers with that session to + * corrupt it, forces the session manager to load it back in and then finally returns the + * session if it still exists in Dynamo + * + * @param deleteCorruptSessions + * Whether to configure the session manager to delete corrupt sessions or not + * @return DynamoDB record after sessions have been loaded back in, if it exists. If + * deleteCorruptSessions is true this 'should' return null, if deleteCorruptSessions is + * false this 'should' return a non null item. + * @throws InterruptedException + */ + private GetItemResult saveAndTamperWithSession(boolean deleteCorruptSessions) throws InterruptedException { + TestDynamoDBSessionManager sessionManager = new TestDynamoDBSessionManager(); + sessionManager.setDeleteCorruptSessions(deleteCorruptSessions); + sessionManager.setMaxIdleBackup(MAX_IDLE_BACKUP_SECONDS); + configureWithExplicitCredentials(sessionManager); + sessionManager.createSession(SESSION_ID); + + // Make sure it's persisted to Dynamo first before corrupting + Thread.sleep(TimeUnit.MILLISECONDS.convert(MAX_IDLE_BACKUP_SECONDS + 1, TimeUnit.SECONDS)); + sessionManager.reallyProcessExpires(); + + // Corrupt the session persisted in Dynamo + Map attributes = ImmutableMapParameter.of(DynamoSessionItem.SESSION_ID_ATTRIBUTE_NAME, + new AttributeValue(SESSION_ID), DynamoSessionItem.SESSION_DATA_ATTRIBUTE_NAME, + new AttributeValue().withB(ByteBuffer.wrap(new byte[] { 1, 3, 45, 2, 24, 92 }))); + dynamo.putItem(new PutItemRequest(sessionTableName, attributes)); + + // Force a load of sessions so that corrupt sessions are evaluated by the session store + sessionManager.reallyProcessExpires(); + + return dynamo.getItem(sessionTableName, + ImmutableMapParameter.of(DynamoSessionItem.SESSION_ID_ATTRIBUTE_NAME, new AttributeValue(SESSION_ID))); + } + /** * Bug in the deserialization of sessions was causing persisted sessions loaded via the * processExpires method to replace the active session in memory by incorrectly registering it @@ -119,28 +209,25 @@ public void swappedOutSessionsDoNotReplaceActiveSessionDuringProcessExpires() th TestDynamoDBSessionManager sessionManager = new TestDynamoDBSessionManager(); configureWithExplicitCredentials(sessionManager); - final String sessionId = "1234"; - final int maxIdleBackupSeconds = 5; - final String attrName = "someAttr"; final String originalAttrValue = "1"; final String newAttrValue = "2"; // Create a session and idle it so it's persisted to Dynamo - sessionManager.setMaxIdleBackup(maxIdleBackupSeconds); - Session newSession = sessionManager.createSession(sessionId); - setSessionAttribute(newSession, attrName, originalAttrValue); - Thread.sleep(TimeUnit.MILLISECONDS.convert(maxIdleBackupSeconds + 1, TimeUnit.SECONDS)); + sessionManager.setMaxIdleBackup(MAX_IDLE_BACKUP_SECONDS); + Session newSession = sessionManager.createSession(SESSION_ID); + setSessionAttribute(newSession, ATTR_NAME, originalAttrValue); + Thread.sleep(TimeUnit.MILLISECONDS.convert(MAX_IDLE_BACKUP_SECONDS + 1, TimeUnit.SECONDS)); // Force session manager to persist sessions that have idled sessionManager.reallyProcessExpires(); // Set a new value for the attribute that will only exist in memory - setSessionAttribute(newSession, attrName, newAttrValue); + setSessionAttribute(newSession, ATTR_NAME, newAttrValue); // Force session manager to load in persisted sessions to prune them if expired sessionManager.reallyProcessExpires(); // The active session in memory should not be affected by the sessions loaded during // processExpires - assertEquals(newAttrValue, sessionManager.getSessionAttribute(sessionId, attrName)); + assertEquals(newAttrValue, sessionManager.getSessionAttribute(SESSION_ID, ATTR_NAME)); } private void configureWithExplicitCredentials(DynamoDBSessionManager sessionManager) { @@ -187,4 +274,5 @@ public void reallyProcessExpires() { super.processExpires(); } } + } diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStoreTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStoreTest.java new file mode 100644 index 0000000..b99d7b3 --- /dev/null +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStoreTest.java @@ -0,0 +1,94 @@ +package com.amazonaws.services.dynamodb.sessionmanager; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.catalina.Manager; +import org.apache.catalina.session.StandardSession; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.amazonaws.services.dynamodb.sessionmanager.converters.SessionConversionException; +import com.amazonaws.services.dynamodb.sessionmanager.converters.TestSessionFactory; + +public class DynamoDBSessionStoreTest { + + @Mock + private DynamoSessionStorage storage; + + private StandardSession session; + + private Manager manager; + + private DynamoDBSessionStore store; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + TestSessionFactory factory = new TestSessionFactory(); + manager = factory.getManager(); + session = factory.createStandardSession(); + } + + @Test + public void whenDeleteCorruptSessionsIsTrue_CorruptSessionsAreDeleted() throws Exception { + buildSessionStore(true); + saveAndLoadCorruptSession(); + assertSessionIsDeleted(); + } + + @Test + public void whenDeleteCorruptSessionsIsTrue_ValidSessionsAreNotDeleted() throws Exception { + buildSessionStore(true); + saveAndLoadValidSession(); + assertSessionIsNotDeleted(); + } + + @Test + public void whenDeleteCorruptSessionsIsFalse_ValidSessionsAreNotDeleted() throws Exception { + buildSessionStore(false); + saveAndLoadValidSession(); + assertSessionIsNotDeleted(); + } + + @Test + public void whenDeleteCorruptSessionsIsFalse_CorruptSessionsAreNotDeleted() throws Exception { + buildSessionStore(false); + saveAndLoadCorruptSession(); + assertSessionIsNotDeleted(); + } + + private void assertSessionIsDeleted() { + verify(storage).deleteSession(session.getId()); + } + + private void assertSessionIsNotDeleted() { + verify(storage, never()).deleteSession(session.getId()); + } + + /** + * Simulates the successful saving of a session and failure to load a corrupted session + */ + private void saveAndLoadCorruptSession() throws Exception { + store.save(session); + when(storage.loadSession(session.getId())).thenThrow(new SessionConversionException("")); + store.load(session.getId()); + } + + /** + * Simulates the successful saving and loading of a session + */ + private void saveAndLoadValidSession() throws Exception { + store.save(session); + when(storage.loadSession(session.getId())).thenReturn(session); + store.load(session.getId()); + } + + private void buildSessionStore(boolean deleteCorruptSessions) { + this.store = new DynamoDBSessionStore(storage, deleteCorruptSessions); + this.store.setManager(manager); + } +} diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperIntegrationTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperIntegrationTest.java index f862146..9f2fe14 100644 --- a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperIntegrationTest.java +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperIntegrationTest.java @@ -31,8 +31,8 @@ public class ExpiredSessionReaperIntegrationTest extends SessionStorageIntegrati @Before public void setup() { - sessionStorage = createSessionStorage(SessionConverter.createDefaultSessionConverter( - new TestSessionFactory().getManager(), getClass().getClassLoader())); + sessionStorage = createSessionStorage(SessionConverter + .createDefaultSessionConverter(new TestSessionFactory().getManager(), getClass().getClassLoader())); } /** diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperTest.java index 3e1d35b..da8b6c5 100644 --- a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperTest.java +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/ExpiredSessionReaperTest.java @@ -56,8 +56,8 @@ public static TestStandardSession createExpiredSession() { * always return false no matter what */ public static TestStandardSession createImmortalSession() { - TestStandardSession immortalSession = new TestSessionFactory().withSessionId("immortal").withMaxInactiveInterval(-1) - .withLastAccessedTime(0).createTestStandardSession(); + TestStandardSession immortalSession = new TestSessionFactory().withSessionId("immortal") + .withMaxInactiveInterval(-1).withLastAccessedTime(0).createTestStandardSession(); return immortalSession; } } diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTest.java index 863f4e0..2cc02a5 100644 --- a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTest.java +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTest.java @@ -35,8 +35,8 @@ public class SessionStorageIntegrationTest extends SessionStorageIntegrationTest @Before public void setup() throws Exception { - sessionStorage = createSessionStorage(SessionConverter.createDefaultSessionConverter( - SESSION_FACTORY.getManager(), getClass().getClassLoader())); + sessionStorage = createSessionStorage(SessionConverter + .createDefaultSessionConverter(SESSION_FACTORY.getManager(), getClass().getClassLoader())); } @Test diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTestBase.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTestBase.java index 6385ca1..1ae21f5 100644 --- a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTestBase.java +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/SessionStorageIntegrationTestBase.java @@ -57,8 +57,8 @@ public static final void baseSetupFixture() throws Exception { */ @After public void baseTearDown() { - List failedBatches = dynamoMapper.batchDelete(dynamoMapper.scan(DynamoSessionItem.class, - new DynamoDBScanExpression())); + List failedBatches = dynamoMapper + .batchDelete(dynamoMapper.scan(DynamoSessionItem.class, new DynamoDBScanExpression())); // If we for some reason couldn't delete all items bail out so we don't affect other tests assertThat(failedBatches, empty()); } diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultSessionConverterTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultSessionConverterTest.java index d6bb045..0f5a1c0 100644 --- a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultSessionConverterTest.java +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/DefaultSessionConverterTest.java @@ -35,8 +35,8 @@ public class DefaultSessionConverterTest { @Before public void setup() { - sessionConverter = SessionConverter.createDefaultSessionConverter(SESSION_TEMPLATE.getManager(), getClass() - .getClassLoader()); + sessionConverter = SessionConverter.createDefaultSessionConverter(SESSION_TEMPLATE.getManager(), + getClass().getClassLoader()); session = SESSION_TEMPLATE.createStandardSession(); } From 896ac5dc0de44b5d53db39d0f69d51e57aee1dfa Mon Sep 17 00:00:00 2001 From: Shore Date: Thu, 24 Sep 2015 21:46:36 -0700 Subject: [PATCH 16/23] Updating version to 2.0.1 --- pom.xml | 2 +- .../dynamodb/sessionmanager/DynamoDBSessionManager.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 5006eec..9877a23 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ aws-dynamodb-session-tomcat jar Amazon DynamoDB Session Manager for Tomcat - 2.0.0 + 2.0.1 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 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 ea4c8ac..3a77736 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java @@ -45,9 +45,9 @@ public class DynamoDBSessionManager extends PersistentManagerBase { public static final String DEFAULT_TABLE_NAME = "Tomcat_SessionState"; - private static final String USER_AGENT = "DynamoSessionManager/2.0"; + private static final String USER_AGENT = "DynamoSessionManager/2.0.1"; private static final String name = "AmazonDynamoDBSessionManager"; - private static final String info = name + "/2.0"; + private static final String info = name + "/2.0.1"; private String regionId = "us-east-1"; private String endpoint; From f92f200f97353181b42f6086a9ba093f91e90119 Mon Sep 17 00:00:00 2001 From: Andrew Shore Date: Thu, 24 Mar 2016 17:24:27 -0700 Subject: [PATCH 17/23] Updating SDK dependency --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 9877a23..0108075 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ aws-dynamodb-session-tomcat jar Amazon DynamoDB Session Manager for Tomcat - 2.0.1 + 2.0.2 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 @@ -37,7 +37,7 @@ com.amazonaws aws-java-sdk-dynamodb - 1.10.20 + 1.10.63 org.apache.tomcat @@ -60,7 +60,7 @@ com.amazonaws aws-java-sdk-test-utils - 1.10.20 + 1.10.63 test From 9c3d76e7faa4ea1441634ac3e4dbb220d9553ef5 Mon Sep 17 00:00:00 2001 From: Andrew Shore Date: Thu, 3 Dec 2015 14:12:28 -0800 Subject: [PATCH 18/23] Making SDK dependency optional so it's not included transitively since we shade it into an uber jar --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 0108075..e2bbe2f 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,7 @@ com.amazonaws aws-java-sdk-dynamodb 1.10.63 + true org.apache.tomcat From 399499f069071ffe7c80918cbed4d5bf69a94080 Mon Sep 17 00:00:00 2001 From: Maczam Date: Sat, 14 Feb 2015 08:55:57 +0800 Subject: [PATCH 19/23] add idea gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 80e0ab8..ebc86da 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ .settings/org.eclipse.core.resources.prefs .settings/org.eclipse.jdt.core.prefs .settings/org.eclipse.m2e.core.prefs -dependency-reduced-pom.xml +*.iml +.idea/ From 2cf7400e1d967e9ebbfa6200c67b4337d4d7ef82 Mon Sep 17 00:00:00 2001 From: Andrew Shore Date: Wed, 17 Aug 2016 20:31:41 -0700 Subject: [PATCH 20/23] Removing maxInactive as it's been ignored since Tomcat 6 --- pom.xml | 2 +- .../dynamodb/sessionmanager/DynamoDBSessionManager.java | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index e2bbe2f..1218882 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ aws-dynamodb-session-tomcat jar Amazon DynamoDB Session Manager for Tomcat - 2.0.2 + 2.0.3 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 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 3a77736..69cb81a 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java @@ -67,9 +67,6 @@ public class DynamoDBSessionManager extends PersistentManagerBase { public DynamoDBSessionManager() { setSaveOnRestart(true); - // MaxInactiveInterval controls when sessions are removed from the store - setMaxInactiveInterval(60 * 60 * 2); // 2 hours - // MaxIdleBackup controls when sessions are persisted to the store setMaxIdleBackup(30); // 30 seconds } @@ -258,7 +255,7 @@ private SessionConverter getSessionConverter() { * 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 From d89984970a3711438144ef30f8cea520c0d2b38d Mon Sep 17 00:00:00 2001 From: Andrew Shore Date: Mon, 22 Aug 2016 10:41:09 -0700 Subject: [PATCH 21/23] More fixes to support Tomcat 8. This release drops support for Tomcat 7. --- pom.xml | 13 ++- .../DynamoDBSessionManager.java | 38 ++------- .../sessionmanager/DynamoDBSessionStore.java | 1 - ...DynamoDBSessionManagerIntegrationTest.java | 84 ++++--------------- .../DynamoDBSessionStoreIntegrationTest.java | 60 +++++++++++++ .../converters/TestSessionFactory.java | 2 +- 6 files changed, 89 insertions(+), 109 deletions(-) create mode 100644 src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStoreIntegrationTest.java diff --git a/pom.xml b/pom.xml index 1218882..bfb84d6 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ aws-dynamodb-session-tomcat jar Amazon DynamoDB Session Manager for Tomcat - 2.0.3 + 2.0.4 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 @@ -43,7 +43,7 @@ org.apache.tomcat tomcat-catalina - 7.0.64 + 8.0.1 provided @@ -73,13 +73,13 @@ org.apache.tomcat.embed tomcat-embed-core - 7.0.64 + 8.0.1 test org.apache.tomcat.embed tomcat-embed-jasper - 7.0.64 + 8.0.1 test @@ -102,8 +102,8 @@ maven-compiler-plugin 2.3 - 1.6 - 1.6 + 1.7 + 1.7 UTF-8 @@ -220,7 +220,6 @@ 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 69cb81a..4b0086b 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManager.java @@ -14,14 +14,6 @@ */ package com.amazonaws.services.dynamodb.sessionmanager; -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; @@ -37,6 +29,13 @@ import com.amazonaws.services.dynamodbv2.util.Tables; import com.amazonaws.util.StringUtils; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.session.PersistentManagerBase; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + +import java.io.File; + /** * Tomcat persistent session manager implementation that uses Amazon DynamoDB to store HTTP session * data. @@ -71,7 +70,6 @@ public DynamoDBSessionManager() { setMaxIdleBackup(30); // 30 seconds } - @Override public String getInfo() { return info; } @@ -131,8 +129,6 @@ public void setDeleteCorruptSessions(boolean deleteCorruptSessions) { @Override protected void initInternal() throws LifecycleException { - this.setDistributable(true); - AmazonDynamoDBClient dynamoClient = createDynamoClient(); initDynamoTable(dynamoClient); DynamoSessionStorage sessionStorage = createSessionStorage(dynamoClient); @@ -246,26 +242,8 @@ private DynamoSessionStorage createSessionStorage(AmazonDynamoDBClient dynamoCli } private SessionConverter getSessionConverter() { - ClassLoader classLoader = getAssociatedContext().getLoader().getClassLoader(); + ClassLoader classLoader = getContext().getLoader().getClassLoader(); return SessionConverter.createDefaultSessionConverter(this, classLoader); } - /** - * 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); - } - } } 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 c409240..b548b65 100644 --- a/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java +++ b/src/main/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStore.java @@ -46,7 +46,6 @@ public DynamoDBSessionStore(DynamoSessionStorage sessionStorage, boolean deleteC this.deleteCorruptSessions = deleteCorruptSessions; } - @Override public String getInfo() { return info; } diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java index 8f03a94..9976e1c 100644 --- a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java @@ -14,18 +14,11 @@ */ package com.amazonaws.services.dynamodb.sessionmanager; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Map; -import java.util.concurrent.TimeUnit; +import com.amazonaws.AmazonServiceException; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; +import com.amazonaws.services.dynamodbv2.model.DescribeTableRequest; +import com.amazonaws.services.dynamodbv2.model.TableDescription; +import com.amazonaws.test.AWSTestBase; import org.apache.catalina.Context; import org.apache.catalina.Session; @@ -36,15 +29,14 @@ import org.junit.BeforeClass; import org.junit.Test; -import com.amazonaws.AmazonServiceException; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; -import com.amazonaws.services.dynamodbv2.model.AttributeValue; -import com.amazonaws.services.dynamodbv2.model.DescribeTableRequest; -import com.amazonaws.services.dynamodbv2.model.GetItemResult; -import com.amazonaws.services.dynamodbv2.model.PutItemRequest; -import com.amazonaws.services.dynamodbv2.model.TableDescription; -import com.amazonaws.test.AWSTestBase; -import com.amazonaws.util.ImmutableMapParameter; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class DynamoDBSessionManagerIntegrationTest extends AWSTestBase { @@ -148,61 +140,13 @@ public void sessionSwappedOutToDynamo_IsUnchangedWhenSwappedBackIn() throws Exce assertEquals(originalCreationTime, sessionManager.getCreationTimestamp(SESSION_ID)); } - @Test - public void deleteCorruptSessionsEnabled_DeletesNonSerializableSessions() throws InterruptedException { - GetItemResult result = saveAndTamperWithSession(true); - assertNull(result.getItem()); - } - - @Test - public void deleteCorruptSessionsDisabled_DoesNotDeleteNonSerializableSessions() throws InterruptedException { - GetItemResult result = saveAndTamperWithSession(false); - assertNotNull(result.getItem()); - } - - /** - * Creates a new Session, makes sure it's backed up in Dynamo, tampers with that session to - * corrupt it, forces the session manager to load it back in and then finally returns the - * session if it still exists in Dynamo - * - * @param deleteCorruptSessions - * Whether to configure the session manager to delete corrupt sessions or not - * @return DynamoDB record after sessions have been loaded back in, if it exists. If - * deleteCorruptSessions is true this 'should' return null, if deleteCorruptSessions is - * false this 'should' return a non null item. - * @throws InterruptedException - */ - private GetItemResult saveAndTamperWithSession(boolean deleteCorruptSessions) throws InterruptedException { - TestDynamoDBSessionManager sessionManager = new TestDynamoDBSessionManager(); - sessionManager.setDeleteCorruptSessions(deleteCorruptSessions); - sessionManager.setMaxIdleBackup(MAX_IDLE_BACKUP_SECONDS); - configureWithExplicitCredentials(sessionManager); - sessionManager.createSession(SESSION_ID); - - // Make sure it's persisted to Dynamo first before corrupting - Thread.sleep(TimeUnit.MILLISECONDS.convert(MAX_IDLE_BACKUP_SECONDS + 1, TimeUnit.SECONDS)); - sessionManager.reallyProcessExpires(); - - // Corrupt the session persisted in Dynamo - Map attributes = ImmutableMapParameter.of(DynamoSessionItem.SESSION_ID_ATTRIBUTE_NAME, - new AttributeValue(SESSION_ID), DynamoSessionItem.SESSION_DATA_ATTRIBUTE_NAME, - new AttributeValue().withB(ByteBuffer.wrap(new byte[] { 1, 3, 45, 2, 24, 92 }))); - dynamo.putItem(new PutItemRequest(sessionTableName, attributes)); - - // Force a load of sessions so that corrupt sessions are evaluated by the session store - sessionManager.reallyProcessExpires(); - - return dynamo.getItem(sessionTableName, - ImmutableMapParameter.of(DynamoSessionItem.SESSION_ID_ATTRIBUTE_NAME, new AttributeValue(SESSION_ID))); - } - /** * Bug in the deserialization of sessions was causing persisted sessions loaded via the * processExpires method to replace the active session in memory by incorrectly registering it * with the manager. This tests makes sure that any sessions loaded by process expires do not * affect the attributes of active sessions. * - * @see https://github.com.aws/aws-dynamodb-session-tomcat/pull/19 + * @see PR #19 */ @Test public void swappedOutSessionsDoNotReplaceActiveSessionDuringProcessExpires() throws InterruptedException { diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStoreIntegrationTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStoreIntegrationTest.java new file mode 100644 index 0000000..3858965 --- /dev/null +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionStoreIntegrationTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2011-2016 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 com.amazonaws.services.dynamodb.sessionmanager.converters.SessionConversionException; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class DynamoDBSessionStoreIntegrationTest { + + private static final String SESSION_ID = "1234"; + + @Mock + private DynamoSessionStorage storage; + + @Test + public void loadCorruptSession_DeletesSessionWhenDeleteCorruptSessionsEnabled() throws + Exception { + stubLoadCorruptSession(); + final DynamoDBSessionStore sessionStore = new DynamoDBSessionStore(storage, true); + assertNull(sessionStore.load(SESSION_ID)); + verify(storage, times(1)).deleteSession(SESSION_ID); + } + + @Test + public void loadCorruptSession_DoesNotDeletesSessionWhenDeleteCorruptSessionsDisabled() throws + Exception { + stubLoadCorruptSession(); + final DynamoDBSessionStore sessionStore = new DynamoDBSessionStore(storage, false); + assertNull(sessionStore.load(SESSION_ID)); + verify(storage, never()).deleteSession(SESSION_ID); + } + + private void stubLoadCorruptSession() { + when(storage.loadSession(SESSION_ID)) + .thenThrow(new SessionConversionException("Unable to convert session")); + } +} diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TestSessionFactory.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TestSessionFactory.java index 0c69efe..94dc562 100644 --- a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TestSessionFactory.java +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/converters/TestSessionFactory.java @@ -127,7 +127,7 @@ private static Map getDefaultSessionAttributes() { private static Manager getDefaultManager() { Manager mockManager = mock(Manager.class, RETURNS_DEEP_STUBS); - when(mockManager.getContainer().getLogger().isDebugEnabled()).thenReturn(false); + when(mockManager.getContext().getLogger().isDebugEnabled()).thenReturn(false); return mockManager; } From 595e7a9d6262b7ce0f80e4ad67c23df9d4440d89 Mon Sep 17 00:00:00 2001 From: anselmopfeifer Date: Thu, 22 Sep 2016 20:08:09 -0400 Subject: [PATCH 22/23] Update DynamoDBSessionManagerIntegrationTest.java issues URL setting --- .../sessionmanager/DynamoDBSessionManagerIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java index 9976e1c..96358cf 100644 --- a/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java +++ b/src/test/java/com/amazonaws/services/dynamodb/sessionmanager/DynamoDBSessionManagerIntegrationTest.java @@ -146,7 +146,7 @@ public void sessionSwappedOutToDynamo_IsUnchangedWhenSwappedBackIn() throws Exce * with the manager. This tests makes sure that any sessions loaded by process expires do not * affect the attributes of active sessions. * - * @see PR #19 + * @see PR #19 */ @Test public void swappedOutSessionsDoNotReplaceActiveSessionDuringProcessExpires() throws InterruptedException { From eb79e7c5ba4e157d92b5a728d25abeda51e99dc6 Mon Sep 17 00:00:00 2001 From: Henri Yandell Date: Tue, 6 Feb 2018 13:45:56 -0800 Subject: [PATCH 23/23] Adding archive note --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 349b2a4..cf32f82 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +**This project has been archived.** + +[https://github.com/magro/memcached-session-manager](https://github.com/magro/memcached-session-manager) is a viable alternative to this project and supports non-sticky +sessions and realtime session persistence. It can be used with [Amazon Elasticache](https://aws.amazon.com/elasticache/). +You may also fork this implementation and maintain your own version of the DynamoDB Session Manager. + Amazon DynamoDB Session Manager for Apache Tomcat =================================================