Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Wilkins2015-10-12 21:37:42 +0000
committerGreg Wilkins2015-10-12 21:37:42 +0000
commitff8c932b0f92acc2fc6b2ec4227f2645a50b504f (patch)
tree03ebc96c7e62d2f5ec8efd12f009973eae2b307e
parentb0682ed4233cef221544feb9bec58560cdb73538 (diff)
parent84ef692be5e3ec9a0e6c0383a6b602bf9237919c (diff)
downloadorg.eclipse.jetty.project-ff8c932b0f92acc2fc6b2ec4227f2645a50b504f.tar.gz
org.eclipse.jetty.project-ff8c932b0f92acc2fc6b2ec4227f2645a50b504f.tar.xz
org.eclipse.jetty.project-ff8c932b0f92acc2fc6b2ec4227f2645a50b504f.zip
Merge branch 'jetty-9.3.x' of ssh://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project into jetty-9.3.x
-rw-r--r--jetty-distribution/pom.xml5
-rw-r--r--jetty-gcloud/gcloud-session-manager/pom.xml75
-rw-r--r--jetty-gcloud/gcloud-session-manager/src/main/config/etc/jetty-gcloud-sessions.xml43
-rw-r--r--jetty-gcloud/gcloud-session-manager/src/main/config/modules/gcloud-sessions.mod75
-rw-r--r--jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudConfiguration.java184
-rw-r--r--jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionIdManager.java323
-rw-r--r--jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionManager.java1298
-rw-r--r--jetty-gcloud/gcloud-session-manager/src/test/java/org/eclipse/jetty/gcloud/session/GCloudSessionTester.java75
-rw-r--r--jetty-gcloud/pom.xml23
-rw-r--r--jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java30
-rw-r--r--pom.xml1
-rw-r--r--tests/test-sessions/pom.xml1
-rw-r--r--tests/test-sessions/test-gcloud-sessions/pom.xml110
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ClientCrossContextSessionTest.java70
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ForwardedSessionTest.java62
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/GCloudSessionTestSupport.java372
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/GCloudTestServer.java97
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ImmortalSessionTest.java70
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/InvalidationSessionTest.java82
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/LastAccessTimeTest.java70
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/LocalSessionScavengingTest.java71
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/NewSessionTest.java74
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/OrphanedSessionTest.java75
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ReentrantRequestSessionTest.java72
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/RemoveSessionTest.java75
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SameNodeLoadTest.java71
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ServerCrossContextSessionTest.java71
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionExpiryTest.java102
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionInvalidateAndCreateTest.java73
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionMigrationTest.java72
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionRenewTest.java71
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionValueSavingTest.java72
-rw-r--r--tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/StopSessionManagerPreserveSessionTest.java99
-rw-r--r--tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractInvalidationSessionTest.java5
34 files changed, 4055 insertions, 14 deletions
diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml
index 71f25a4677..c472bd4246 100644
--- a/jetty-distribution/pom.xml
+++ b/jetty-distribution/pom.xml
@@ -779,6 +779,11 @@
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>org.eclipse.jetty.gcloud</groupId>
+ <artifactId>gcloud-session-manager</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-nosql</artifactId>
<version>${project.version}</version>
diff --git a/jetty-gcloud/gcloud-session-manager/pom.xml b/jetty-gcloud/gcloud-session-manager/pom.xml
new file mode 100644
index 0000000000..f83e49d5a0
--- /dev/null
+++ b/jetty-gcloud/gcloud-session-manager/pom.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.eclipse.jetty.gcloud</groupId>
+ <artifactId>gcloud-parent</artifactId>
+ <version>9.3.4-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>gcloud-session-manager</artifactId>
+ <name>Jetty :: GCloud :: Session Manager</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.gcloud</groupId>
+ <artifactId>gcloud-java-datastore</artifactId>
+ <version>${gcloud.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-webapp</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>websocket-servlet</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>websocket-server</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-test-helper</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <properties>
+ <bundle-symbolic-name>${project.groupId}.session</bundle-symbolic-name>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ <configuration>
+ <instructions>
+ <Export-Package>org.eclipse.jetty.gcloud.session.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}";</Export-Package>
+ </instructions>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/jetty-gcloud/gcloud-session-manager/src/main/config/etc/jetty-gcloud-sessions.xml b/jetty-gcloud/gcloud-session-manager/src/main/config/etc/jetty-gcloud-sessions.xml
new file mode 100644
index 0000000000..75caeb1f9e
--- /dev/null
+++ b/jetty-gcloud/gcloud-session-manager/src/main/config/etc/jetty-gcloud-sessions.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+ <!-- ============================================================================================== -->
+ <!-- GCloud configuration from property file -->
+ <!-- Note: passwords stored in the property file can use jetty obfuscation (see -->
+ <!-- https://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html -->
+ <!-- ============================================================================================== -->
+ <Call id="gconf" class="org.eclipse.jetty.gcloud.session.GCloudConfiguration" name="fromFile">
+ <Arg><Property name="jetty.base" default="."/>/<Property name="jetty.gcloudSession.configFile" default="etc/gcloud.props"/></Arg>
+ </Call>
+
+ <!-- ============================================================================================== -->
+ <!-- Alternate GCloud configuration from properties -->
+ <!-- Note: passwords can use jetty obfuscation (see -->
+ <!-- https://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html -->
+ <!-- ============================================================================================== -->
+<!--
+ <New id="gconf" class="org.eclipse.jetty.gcloud.session.GCloudConfiguration">
+ <Set name="projectId"><Property name="jetty.gcloudSession.projectId"/></Set>
+ <Set name="p12File"><Property name="jetty.gcloudSession.p12File"/></Set>
+ <Set name="serviceAccount"><Property name="jetty.gcloudSession.serviceAccount"/></Set>
+ <Set name="password"><Property name="jetty.gcloudSession.password"/></Set>
+ </New>
+-->
+
+
+ <!-- ===================================================================== -->
+ <!-- Configure a GCloudSessionIdManager -->
+ <!-- ===================================================================== -->
+ <Set name="sessionIdManager">
+ <New id="idMgr" class="org.eclipse.jetty.gcloud.session.GCloudSessionIdManager">
+ <Arg>
+ <Ref id="Server"/>
+ </Arg>
+ <Set name="workerName"><Property name="jetty.gcloudSession.workerName" default="node1"/></Set>
+ <Set name="config"><Ref id="gconf"/></Set>
+ </New>
+ </Set>
+
+</Configure>
diff --git a/jetty-gcloud/gcloud-session-manager/src/main/config/modules/gcloud-sessions.mod b/jetty-gcloud/gcloud-session-manager/src/main/config/modules/gcloud-sessions.mod
new file mode 100644
index 0000000000..df1a13e655
--- /dev/null
+++ b/jetty-gcloud/gcloud-session-manager/src/main/config/modules/gcloud-sessions.mod
@@ -0,0 +1,75 @@
+#
+# Jetty GCloudDatastore Session Manager module
+#
+
+[depend]
+annotations
+webapp
+
+[files]
+
+maven://com.google.gcloud/gcloud-java-datastore/0.0.7|lib/gcloud/gcloud-java-datastore-0.0.7.jar
+maven://com.google.gcloud/gcloud-java-core/0.0.7|lib/gcloud/gcloud-java-core-0.0.7.jar
+maven://com.google.auth/google-auth-library-credentials/0.1.0|lib/gcloud/google-auth-library-credentials-0.1.0.jar
+maven://com.google.auth/google-auth-library-oauth2-http/0.1.0|lib/gcloud/google-auth-library-oauth2-http-0.1.0.jar
+maven://com.google.http-client/google-http-client-jackson2/1.19.0|lib/gcloud/google-http-client-jackson2-1.19.0.jar
+maven://com.fasterxml.jackson.core/jackson-core/2.1.3|lib/gcloud/jackson-core-2.1.3.jar
+maven://com.google.http-client/google-http-client/1.20.0|lib/gcloud/google-http-client-1.20.0.jar
+maven://com.google.code.findbugs/jsr305/1.3.9|lib/gcloud/jsr305-1.3.9.jar
+maven://org.apache.httpcomponents/httpclient/4.0.1|lib/gcloud/httpclient-4.0.1.jar
+maven://org.apache.httpcomponents/httpcore/4.0.1|lib/gcloud/httpcore-4.0.1.jar
+maven://commons-logging/commons-logging/1.1.1|lib/gcloud/commons-logging-1.1.1.jar
+maven://commons-codec/commons-codec/1.3|lib/gcloud/commons-codec-1.3.jar
+maven://com.google.oauth-client/google-oauth-client/1.20.0|lib/gcloud//google-oauth-client-1.20.0.jar
+maven://com.google.guava/guava/18.0|lib/gcloud/guava-18.0.jar
+maven://com.google.api-client/google-api-client-appengine/1.20.0|lib/gcloud/google-api-client-appengine-1.20.0.jar
+maven://com.google.oauth-client/google-oauth-client-appengine/1.20.0|lib/gcloud/google-oauth-client-appengine-1.20.0.jar
+maven://com.google.oauth-client/google-oauth-client-servlet/1.20.0|lib/gcloud/google-oauth-client-servlet-1.20.0.jar
+maven://com.google.http-client/google-http-client-jdo/1.20.0|lib/gcloud/google-http-client-jdo-1.20.0.jar
+maven://com.google.api-client/google-api-client-servlet/1.20.0|lib/gcloud/google-api-client-servlet-1.20.0.jar
+maven://javax.jdo/jdo2-api/2.3-eb|lib/gcloud/jdo2-api-2.3-eb.jar
+maven://javax.transaction/transaction-api/1.1|lib/gcloud/transaction-api-1.1.jar
+maven://com.google.http-client/google-http-client-appengine/1.20.0|lib/gcloud/google-http-client-appengine-1.20.0.jar
+maven://com.google.http-client/google-http-client-jackson/1.20.0|lib/gcloud/google-http-client-jackson-1.20.0.jar
+maven://org.codehaus.jackson/jackson-core-asl/1.9.11|lib/gcloud/jackson-core-asl-1.9.11.jar
+maven://joda-time/joda-time/2.8.2|lib/gcloud/joda-time-2.8.2.jar
+maven://org.json/json/20090211|lib/gcloud/json-20090211.jar
+maven://com.google.apis/google-api-services-datastore-protobuf/v1beta2-rev1-2.1.2|lib/gcloud/google-api-services-datastore-protobuf-v1beta2-rev1-2.1.2.jar
+maven://com.google.protobuf/protobuf-java/2.5.0|lib/gcloud/protobuf-java-2.5.0.jar
+maven://com.google.http-client/google-http-client-protobuf/1.15.0-rc|lib/gcloud/google-http-client-protobuf-1.15.0-rc.jar
+maven://com.google.api-client/google-api-client/1.15.0-rc|lib/gcloud/google-api-client-1.15.0-rc.jar
+maven://com.google.apis/google-api-services-datastore/v1beta2-rev23-1.19.0|lib/gcloud/google-api-services-datastore-v1beta2-rev23-1.19.0.jar
+
+[lib]
+lib/gcloud-session-manager-${jetty.version}.jar
+lib/gcloud/*.jar
+
+[xml]
+etc/jetty-gcloud-sessions.xml
+
+[license]
+GCloudDatastore is an open source project hosted on Github and released under the Apache 2.0 license.
+https://github.com/GoogleCloudPlatform/gcloud-java
+http://www.apache.org/licenses/LICENSE-2.0.html
+
+[ini-template]
+## GCloudDatastore Session config
+
+## Unique identifier for this node in the cluster
+# jetty.gcloudSession.workerName=node1
+
+## Name of properties files containing gcloud config
+#jetty.gcloudSession.configFilet=etc/gcloud.props
+
+##Alternative to properties file, individual properties
+## the gcloud projectId
+#jetty.gcloudSession.projectId=
+
+## the p12 file associated with the project
+#jetty.gcloudSession.p12File=
+
+## the serviceAccount for the Datastore
+#jetty.gcloudSession.serviceAccount=
+
+## the password (can be obfuscated)
+#jetty.gcloudSession.password=
diff --git a/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudConfiguration.java b/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudConfiguration.java
new file mode 100644
index 0000000000..c1a95961d4
--- /dev/null
+++ b/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudConfiguration.java
@@ -0,0 +1,184 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.security.Password;
+
+import com.google.gcloud.AuthCredentials;
+import com.google.gcloud.datastore.DatastoreOptions;
+
+
+
+/**
+ * GCloudConfiguration
+ *
+ *
+ */
+public class GCloudConfiguration
+{
+ public static final String PROJECT_ID = "projectId";
+ public static final String P12 = "p12";
+ public static final String PASSWORD = "password";
+ public static final String SERVICE_ACCOUNT = "serviceAccount";
+
+ private String _projectId;
+ private File _p12File;
+ private String _serviceAccount;
+ private String _password;
+ private AuthCredentials _authCredentials;
+ private DatastoreOptions _options;
+
+ /**
+ * Generate a configuration from a properties file
+ *
+ * @param propsFile
+ * @return
+ * @throws IOException
+ */
+ public static GCloudConfiguration fromFile(String propsFile)
+ throws IOException
+ {
+ if (propsFile == null)
+ throw new IllegalArgumentException ("Null properties file");
+
+ File f = new File(propsFile);
+ if (!f.exists())
+ throw new IllegalArgumentException("No such file "+f.getAbsolutePath());
+ Properties props = new Properties();
+ try (FileInputStream is=new FileInputStream(f))
+ {
+ props.load(is);
+ }
+
+ GCloudConfiguration config = new GCloudConfiguration();
+ config.setProjectId(props.getProperty(PROJECT_ID));
+ config.setP12File(props.getProperty(P12));
+ config.setPassword(props.getProperty(PASSWORD));
+ config.setServiceAccount(props.getProperty(SERVICE_ACCOUNT));
+ return config;
+ }
+
+
+
+ public String getProjectId()
+ {
+ return _projectId;
+ }
+
+ public File getP12File()
+ {
+ return _p12File;
+ }
+
+ public String getServiceAccount()
+ {
+ return _serviceAccount;
+ }
+
+
+ public void setProjectId(String projectId)
+ {
+ checkForModification();
+ _projectId = projectId;
+ }
+
+ public void setP12File (String file)
+ {
+ checkForModification();
+ _p12File = new File(file);
+ }
+
+
+ public void setServiceAccount (String serviceAccount)
+ {
+ checkForModification();
+ _serviceAccount = serviceAccount;
+ }
+
+
+ public void setPassword (String pwd)
+ {
+ checkForModification();
+ Password p = new Password(pwd);
+ _password = p.toString();
+ }
+
+
+ public DatastoreOptions getDatastoreOptions ()
+ throws Exception
+ {
+ if (_options == null)
+ {
+ _options = DatastoreOptions.builder()
+ .projectId(_projectId)
+ .authCredentials(getAuthCredentials())
+ .build();
+ }
+ return _options;
+ }
+
+ /**
+ * @return
+ * @throws Exception
+ */
+ public AuthCredentials getAuthCredentials()
+ throws Exception
+ {
+ if (_authCredentials == null)
+ {
+ if (_password == null)
+ throw new IllegalStateException("No password");
+ if (_projectId == null)
+ throw new IllegalStateException("No project id");
+
+ if (_projectId == null)
+ throw new IllegalStateException("No project id");
+
+ if (_p12File == null || !_p12File.exists())
+ throw new IllegalStateException("No p12 file: "+(_p12File==null?"null":_p12File.getAbsolutePath()));
+
+ if (_serviceAccount == null)
+ throw new IllegalStateException("No service account");
+
+ char[] pwdChars = _password.toCharArray();
+ KeyStore keystore = KeyStore.getInstance("PKCS12");
+ keystore.load(new FileInputStream(getP12File()), pwdChars);
+ PrivateKey privateKey = (PrivateKey) keystore.getKey("privatekey", pwdChars);
+ _authCredentials = AuthCredentials.createFor(getServiceAccount(), privateKey);
+ }
+ return _authCredentials;
+ }
+
+ /**
+ * @throws IllegalStateException
+ */
+ protected void checkForModification () throws IllegalStateException
+ {
+ if (_authCredentials != null || _options != null)
+ throw new IllegalStateException("Cannot modify auth configuration after datastore initialized");
+ }
+}
diff --git a/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionIdManager.java b/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionIdManager.java
new file mode 100644
index 0000000000..e8a32a5e3b
--- /dev/null
+++ b/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionIdManager.java
@@ -0,0 +1,323 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.gcloud.session;
+
+import java.util.Random;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.server.session.AbstractSessionIdManager;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+import com.google.gcloud.datastore.Datastore;
+import com.google.gcloud.datastore.DatastoreFactory;
+import com.google.gcloud.datastore.Entity;
+import com.google.gcloud.datastore.Key;
+import com.google.gcloud.datastore.KeyFactory;
+
+
+
+/**
+ * GCloudSessionIdManager
+ *
+ *
+ *
+ */
+public class GCloudSessionIdManager extends AbstractSessionIdManager
+{
+ private final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
+ public static final int DEFAULT_IDLE_EXPIRY_MULTIPLE = 2;
+ public static final String KIND = "GCloudSessionId";
+ private Server _server;
+ private Datastore _datastore;
+ private KeyFactory _keyFactory;
+ private GCloudConfiguration _config;
+
+
+
+
+ /**
+ * @param server
+ */
+ public GCloudSessionIdManager(Server server)
+ {
+ super();
+ _server = server;
+ }
+
+ /**
+ * @param server
+ * @param random
+ */
+ public GCloudSessionIdManager(Server server, Random random)
+ {
+ super(random);
+ _server = server;
+ }
+
+
+
+ /**
+ * Start the id manager.
+ * @see org.eclipse.jetty.server.session.AbstractSessionIdManager#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ if (_config == null)
+ throw new IllegalStateException("No gcloud configuration specified");
+
+
+ _datastore = DatastoreFactory.instance().get(_config.getDatastoreOptions());
+ _keyFactory = _datastore.newKeyFactory().kind(KIND);
+
+ super.doStart();
+ }
+
+
+
+ /**
+ * Stop the id manager
+ * @see org.eclipse.jetty.server.session.AbstractSessionIdManager#doStop()
+ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ }
+
+
+
+
+
+ /**
+ * Check to see if the given session id is being
+ * used by a session in any context.
+ *
+ * This method will consult the cluster.
+ *
+ * @see org.eclipse.jetty.server.SessionIdManager#idInUse(java.lang.String)
+ */
+ @Override
+ public boolean idInUse(String id)
+ {
+ if (id == null)
+ return false;
+
+ String clusterId = getClusterId(id);
+
+ //ask the cluster - this should also tickle the idle expiration timer on the sessionid entry
+ //keeping it valid
+ try
+ {
+ return exists(clusterId);
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Problem checking inUse for id="+clusterId, e);
+ return false;
+ }
+
+ }
+
+ /**
+ * Remember a new in-use session id.
+ *
+ * This will save the in-use session id to the cluster.
+ *
+ * @see org.eclipse.jetty.server.SessionIdManager#addSession(javax.servlet.http.HttpSession)
+ */
+ @Override
+ public void addSession(HttpSession session)
+ {
+ if (session == null)
+ return;
+
+ //insert into the store
+ insert (((AbstractSession)session).getClusterId());
+ }
+
+
+
+
+ public GCloudConfiguration getConfig()
+ {
+ return _config;
+ }
+
+ public void setConfig(GCloudConfiguration config)
+ {
+ _config = config;
+ }
+
+
+
+ /**
+ * Remove a session id from the list of in-use ids.
+ *
+ * This will remvove the corresponding session id from the cluster.
+ *
+ * @see org.eclipse.jetty.server.SessionIdManager#removeSession(javax.servlet.http.HttpSession)
+ */
+ @Override
+ public void removeSession(HttpSession session)
+ {
+ if (session == null)
+ return;
+
+ //delete from the cache
+ delete (((AbstractSession)session).getClusterId());
+ }
+
+ /**
+ * Remove a session id. This compels all other contexts who have a session
+ * with the same id to also remove it.
+ *
+ * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
+ */
+ @Override
+ public void invalidateAll(String id)
+ {
+ //delete the session id from list of in-use sessions
+ delete (id);
+
+
+ //tell all contexts that may have a session object with this id to
+ //get rid of them
+ Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+ for (int i=0; contexts!=null && i<contexts.length; i++)
+ {
+ SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+ if (sessionHandler != null)
+ {
+ SessionManager manager = sessionHandler.getSessionManager();
+
+ if (manager != null && manager instanceof GCloudSessionManager)
+ {
+ ((GCloudSessionManager)manager).invalidateSession(id);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Change a session id.
+ *
+ * Typically this occurs when a previously existing session has passed through authentication.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionIdManager#renewSessionId(java.lang.String, java.lang.String, javax.servlet.http.HttpServletRequest)
+ */
+ @Override
+ public void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request)
+ {
+ //generate a new id
+ String newClusterId = newSessionId(request.hashCode());
+
+ delete(oldClusterId);
+ insert(newClusterId);
+
+
+ //tell all contexts to update the id
+ Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+ for (int i=0; contexts!=null && i<contexts.length; i++)
+ {
+ SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+ if (sessionHandler != null)
+ {
+ SessionManager manager = sessionHandler.getSessionManager();
+
+ if (manager != null && manager instanceof GCloudSessionManager)
+ {
+ ((GCloudSessionManager)manager).renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
+ }
+ }
+ }
+
+ }
+
+
+
+ /**
+ * Ask the datastore if a particular id exists.
+ *
+ * @param id
+ * @return
+ */
+ protected boolean exists (String id)
+ {
+ if (_datastore == null)
+ throw new IllegalStateException ("No DataStore");
+ Key key = _keyFactory.newKey(id);
+ return _datastore.get(key) != null;
+ }
+
+
+ /**
+ * Put a session id into the cluster.
+ *
+ * @param id
+ */
+ protected void insert (String id)
+ {
+ if (_datastore == null)
+ throw new IllegalStateException ("No DataStore");
+
+ Entity entity = Entity.builder(makeKey(id))
+ .set("id", id).build();
+ _datastore.put(entity);
+ }
+
+
+
+
+ /**
+ * Remove a session id from the cluster.
+ *
+ * @param id
+ */
+ protected void delete (String id)
+ {
+ if (_datastore == null)
+ throw new IllegalStateException ("No DataStore");
+
+ _datastore.delete(makeKey(id));
+ }
+
+
+
+ /**
+ * Generate a unique key from the session id.
+ *
+ * @param id
+ * @return
+ */
+ protected Key makeKey (String id)
+ {
+ return _keyFactory.newKey(id);
+ }
+}
diff --git a/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionManager.java b/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionManager.java
new file mode 100644
index 0000000000..b6b39a9bba
--- /dev/null
+++ b/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionManager.java
@@ -0,0 +1,1298 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.gcloud.session;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.server.session.AbstractSessionManager;
+import org.eclipse.jetty.server.session.MemSession;
+import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+import com.google.gcloud.datastore.Blob;
+import com.google.gcloud.datastore.Datastore;
+import com.google.gcloud.datastore.DatastoreFactory;
+import com.google.gcloud.datastore.Entity;
+import com.google.gcloud.datastore.GqlQuery;
+import com.google.gcloud.datastore.Key;
+import com.google.gcloud.datastore.KeyFactory;
+import com.google.gcloud.datastore.Query;
+import com.google.gcloud.datastore.Query.ResultType;
+import com.google.gcloud.datastore.QueryResults;
+
+
+
+/**
+ * GCloudSessionManager
+ *
+ *
+ */
+public class GCloudSessionManager extends AbstractSessionManager
+{
+ private final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
+
+
+ public static final String KIND = "GCloudSession";
+ public static final int DEFAULT_MAX_QUERY_RESULTS = 100;
+ public static final long DEFAULT_SCAVENGE_SEC = 600;
+
+ /**
+ * Sessions known to this node held in memory
+ */
+ private ConcurrentHashMap<String, GCloudSessionManager.Session> _sessions;
+
+
+ /**
+ * The length of time a session can be in memory without being checked against
+ * the cluster. A value of 0 indicates that the session is never checked against
+ * the cluster - the current node is considered to be the master for the session.
+ *
+ */
+ private long _staleIntervalSec = 0;
+
+ protected Scheduler.Task _task; //scavenge task
+ protected Scheduler _scheduler;
+ protected Scavenger _scavenger;
+ protected long _scavengeIntervalMs = 1000L * DEFAULT_SCAVENGE_SEC; //10mins
+ protected boolean _ownScheduler;
+
+ private Datastore _datastore;
+ private KeyFactory _keyFactory;
+
+
+ private SessionEntityConverter _converter;
+
+
+ private int _maxResults = DEFAULT_MAX_QUERY_RESULTS;
+
+
+ /**
+ * Scavenger
+ *
+ */
+ protected class Scavenger implements Runnable
+ {
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ scavenge();
+ }
+ finally
+ {
+ if (_scheduler != null && _scheduler.isRunning())
+ _task = _scheduler.schedule(this, _scavengeIntervalMs, TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+
+ /**
+ * SessionEntityConverter
+ *
+ *
+ */
+ public class SessionEntityConverter
+ {
+ public final String CLUSTERID = "clusterId";
+ public final String CONTEXTPATH = "contextPath";
+ public final String VHOST = "vhost";
+ public final String ACCESSED = "accessed";
+ public final String LASTACCESSED = "lastAccessed";
+ public final String CREATETIME = "createTime";
+ public final String COOKIESETTIME = "cookieSetTime";
+ public final String LASTNODE = "lastNode";
+ public final String EXPIRY = "expiry";
+ public final String MAXINACTIVE = "maxInactive";
+ public final String ATTRIBUTES = "attributes";
+
+
+
+ public Entity entityFromSession (Session session, Key key) throws Exception
+ {
+ if (session == null)
+ return null;
+
+ Entity entity = null;
+
+ //serialize the attribute map
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(session.getAttributeMap());
+ oos.flush();
+
+ //turn a session into an entity
+ entity = Entity.builder(key)
+ .set(CLUSTERID, session.getId())
+ .set(CONTEXTPATH, session.getContextPath())
+ .set(VHOST, session.getVHost())
+ .set(ACCESSED, session.getAccessed())
+ .set(LASTACCESSED, session.getLastAccessedTime())
+ .set(CREATETIME, session.getCreationTime())
+ .set(COOKIESETTIME, session.getCookieSetTime())
+ .set(LASTNODE,session.getLastNode())
+ .set(EXPIRY, session.getExpiry())
+ .set(MAXINACTIVE, session.getMaxInactiveInterval())
+ .set(ATTRIBUTES, Blob.copyFrom(baos.toByteArray())).build();
+
+ return entity;
+ }
+
+ public Session sessionFromEntity (Entity entity) throws Exception
+ {
+ if (entity == null)
+ return null;
+
+ final AtomicReference<Session> reference = new AtomicReference<Session>();
+ final AtomicReference<Exception> exception = new AtomicReference<Exception>();
+ Runnable load = new Runnable()
+ {
+ public void run ()
+ {
+ try
+ {
+ //turn an entity into a Session
+ String clusterId = entity.getString(CLUSTERID);
+ String contextPath = entity.getString(CONTEXTPATH);
+ String vhost = entity.getString(VHOST);
+ long accessed = entity.getLong(ACCESSED);
+ long lastAccessed = entity.getLong(LASTACCESSED);
+ long createTime = entity.getLong(CREATETIME);
+ long cookieSetTime = entity.getLong(COOKIESETTIME);
+ String lastNode = entity.getString(LASTNODE);
+ long expiry = entity.getLong(EXPIRY);
+ long maxInactive = entity.getLong(MAXINACTIVE);
+ Blob blob = (Blob) entity.getBlob(ATTRIBUTES);
+
+ Session session = new Session (clusterId, createTime, accessed, maxInactive);
+ session.setLastNode(lastNode);
+ session.setContextPath(contextPath);
+ session.setVHost(vhost);
+ session.setCookieSetTime(cookieSetTime);
+ session.setLastAccessedTime(lastAccessed);
+ session.setLastNode(lastNode);
+ session.setExpiry(expiry);
+ try (ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(blob.asInputStream()))
+ {
+ Object o = ois.readObject();
+ session.addAttributes((Map<String,Object>)o);
+ }
+ reference.set(session);
+ }
+ catch (Exception e)
+ {
+ exception.set(e);
+ }
+ }
+ };
+
+ if (_context==null)
+ load.run();
+ else
+ _context.getContextHandler().handle(null,load);
+
+
+ if (exception.get() != null)
+ {
+ exception.get().printStackTrace();
+ throw exception.get();
+ }
+
+ return reference.get();
+ }
+ }
+
+ /*
+ * Every time a Session is put into the cache one of these objects
+ * is created to copy the data out of the in-memory session, and
+ * every time an object is read from the cache one of these objects
+ * a fresh Session object is created based on the data held by this
+ * object.
+ */
+ public class SerializableSessionData implements Serializable
+ {
+ /**
+ *
+ */
+ private static final long serialVersionUID = -7779120106058533486L;
+ String clusterId;
+ String contextPath;
+ String vhost;
+ long accessed;
+ long lastAccessed;
+ long createTime;
+ long cookieSetTime;
+ String lastNode;
+ long expiry;
+ long maxInactive;
+ Map<String, Object> attributes;
+
+ public SerializableSessionData()
+ {
+
+ }
+
+
+ public SerializableSessionData(Session s)
+ {
+ clusterId = s.getClusterId();
+ contextPath = s.getContextPath();
+ vhost = s.getVHost();
+ accessed = s.getAccessed();
+ lastAccessed = s.getLastAccessedTime();
+ createTime = s.getCreationTime();
+ cookieSetTime = s.getCookieSetTime();
+ lastNode = s.getLastNode();
+ expiry = s.getExpiry();
+ maxInactive = s.getMaxInactiveInterval();
+ attributes = s.getAttributeMap(); // TODO pointer, not a copy
+ }
+
+ private void writeObject(java.io.ObjectOutputStream out) throws IOException
+ {
+ out.writeUTF(clusterId); //session id
+ out.writeUTF(contextPath); //context path
+ out.writeUTF(vhost); //first vhost
+
+ out.writeLong(accessed);//accessTime
+ out.writeLong(lastAccessed); //lastAccessTime
+ out.writeLong(createTime); //time created
+ out.writeLong(cookieSetTime);//time cookie was set
+ out.writeUTF(lastNode); //name of last node managing
+
+ out.writeLong(expiry);
+ out.writeLong(maxInactive);
+ out.writeObject(attributes);
+ }
+
+ private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
+ {
+ clusterId = in.readUTF();
+ contextPath = in.readUTF();
+ vhost = in.readUTF();
+
+ accessed = in.readLong();//accessTime
+ lastAccessed = in.readLong(); //lastAccessTime
+ createTime = in.readLong(); //time created
+ cookieSetTime = in.readLong();//time cookie was set
+ lastNode = in.readUTF(); //last managing node
+ expiry = in.readLong();
+ maxInactive = in.readLong();
+ attributes = (HashMap<String,Object>)in.readObject();
+ }
+
+ }
+
+
+
+ /**
+ * Session
+ *
+ * Representation of a session in local memory.
+ */
+ public class Session extends MemSession
+ {
+
+ private ReentrantLock _lock = new ReentrantLock();
+
+ /**
+ * The (canonical) context path for with which this session is associated
+ */
+ private String _contextPath;
+
+
+
+ /**
+ * The time in msec since the epoch at which this session should expire
+ */
+ private long _expiryTime;
+
+
+ /**
+ * Time in msec since the epoch at which this session was last read from cluster
+ */
+ private long _lastSyncTime;
+
+
+ /**
+ * The workername of last node known to be managing the session
+ */
+ private String _lastNode;
+
+
+ /**
+ * If dirty, session needs to be (re)sent to cluster
+ */
+ protected boolean _dirty=false;
+
+
+
+
+ /**
+ * Any virtual hosts for the context with which this session is associated
+ */
+ private String _vhost;
+
+
+ /**
+ * Count of how many threads are active in this session
+ */
+ private AtomicInteger _activeThreads = new AtomicInteger(0);
+
+
+
+
+ /**
+ * A new session.
+ *
+ * @param request
+ */
+ protected Session (HttpServletRequest request)
+ {
+ super(GCloudSessionManager.this,request);
+ long maxInterval = getMaxInactiveInterval();
+ _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+ _lastNode = getSessionIdManager().getWorkerName();
+ setVHost(GCloudSessionManager.getVirtualHost(_context));
+ setContextPath(GCloudSessionManager.getContextPath(_context));
+ _activeThreads.incrementAndGet(); //access will not be called on a freshly created session so increment here
+ }
+
+
+
+
+ /**
+ * A restored session.
+ *
+ * @param sessionId
+ * @param created
+ * @param accessed
+ * @param maxInterval
+ */
+ protected Session (String sessionId, long created, long accessed, long maxInterval)
+ {
+ super(GCloudSessionManager.this, created, accessed, sessionId);
+ _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+ }
+
+ /**
+ * Called on entry to the session.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSession#access(long)
+ */
+ @Override
+ protected boolean access(long time)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Access session({}) for context {} on worker {}", getId(), getContextPath(), getSessionIdManager().getWorkerName());
+ try
+ {
+
+ long now = System.currentTimeMillis();
+ //lock so that no other thread can call access or complete until the first one has refreshed the session object if necessary
+ _lock.lock();
+ //a request thread is entering
+ if (_activeThreads.incrementAndGet() == 1)
+ {
+ //if the first thread, check that the session in memory is not stale, if we're checking for stale sessions
+ if (getStaleIntervalSec() > 0 && (now - getLastSyncTime()) >= (getStaleIntervalSec() * 1000L))
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Acess session({}) for context {} on worker {} stale session. Reloading.", getId(), getContextPath(), getSessionIdManager().getWorkerName());
+ refresh();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+
+ if (super.access(time))
+ {
+ int maxInterval=getMaxInactiveInterval();
+ _expiryTime = (maxInterval <= 0 ? 0 : (time + maxInterval*1000L));
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Exit from session
+ * @see org.eclipse.jetty.server.session.AbstractSession#complete()
+ */
+ @Override
+ protected void complete()
+ {
+ super.complete();
+
+ //lock so that no other thread that might be calling access can proceed until this complete is done
+ _lock.lock();
+
+ try
+ {
+ //if this is the last request thread to be in the session
+ if (_activeThreads.decrementAndGet() == 0)
+ {
+ try
+ {
+ //an invalid session will already have been removed from the
+ //local session map and deleted from the cluster. If its valid save
+ //it to the cluster.
+ //TODO consider doing only periodic saves if only the last access
+ //time to the session changes
+ if (isValid())
+ {
+ //if session still valid && its dirty or stale or never been synced, write it to the cluster
+ //otherwise, we just keep the updated last access time in memory
+ if (_dirty || getLastSyncTime() == 0 || isStale(System.currentTimeMillis()))
+ {
+ willPassivate();
+ save(this);
+ didActivate();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Problem saving session({})",getId(), e);
+ }
+ finally
+ {
+ _dirty = false;
+ }
+ }
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ /** Test if the session is stale
+ * @param atTime
+ * @return
+ */
+ protected boolean isStale (long atTime)
+ {
+ return (getStaleIntervalSec() > 0) && (atTime - getLastSyncTime() >= (getStaleIntervalSec()*1000L));
+ }
+
+
+ /** Test if the session is dirty
+ * @return
+ */
+ protected boolean isDirty ()
+ {
+ return _dirty;
+ }
+
+ /**
+ * Expire the session.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSession#timeout()
+ */
+ @Override
+ protected void timeout()
+ {
+ if (LOG.isDebugEnabled()) LOG.debug("Timing out session {}", getId());
+ super.timeout();
+ }
+
+
+
+ /**
+ * Reload the session from the cluster. If the node that
+ * last managed the session from the cluster is ourself,
+ * then the session does not need refreshing.
+ * NOTE: this method MUST be called with sufficient locks
+ * in place to prevent 2 or more concurrent threads from
+ * simultaneously updating the session.
+ */
+ private void refresh ()
+ throws Exception
+ {
+ //get fresh copy from the cluster
+ Session fresh = load(makeKey(getClusterId(), _context));
+
+ //if the session no longer exists, invalidate
+ if (fresh == null)
+ {
+ invalidate();
+ return;
+ }
+
+ //cluster copy assumed to be the same as we were the last
+ //node to manage it
+ if (fresh.getLastNode().equals(getLastNode()))
+ return;
+
+ setLastNode(getSessionIdManager().getWorkerName());
+
+ //prepare for refresh
+ willPassivate();
+
+ //if fresh has no attributes, remove them
+ if (fresh.getAttributes() == 0)
+ this.clearAttributes();
+ else
+ {
+ //reconcile attributes
+ for (String key:fresh.getAttributeMap().keySet())
+ {
+ Object freshvalue = fresh.getAttribute(key);
+
+ //session does not already contain this attribute, so bind it
+ if (getAttribute(key) == null)
+ {
+ doPutOrRemove(key,freshvalue);
+ bindValue(key,freshvalue);
+ }
+ else //session already contains this attribute, update its value
+ {
+ doPutOrRemove(key,freshvalue);
+ }
+
+ }
+ // cleanup, remove values from session, that don't exist in data anymore:
+ for (String key : getNames())
+ {
+ if (fresh.getAttribute(key) == null)
+ {
+ Object oldvalue = getAttribute(key);
+ doPutOrRemove(key,null);
+ unbindValue(key,oldvalue);
+ }
+ }
+ }
+ //finish refresh
+ didActivate();
+ }
+
+
+ public void setExpiry (long expiry)
+ {
+ _expiryTime = expiry;
+ }
+
+
+ public long getExpiry ()
+ {
+ return _expiryTime;
+ }
+
+ public boolean isExpiredAt (long time)
+ {
+ if (_expiryTime <= 0)
+ return false; //never expires
+
+ return (_expiryTime <= time);
+ }
+
+ public void swapId (String newId, String newNodeId)
+ {
+ //TODO probably synchronize rather than use the access/complete lock?
+ _lock.lock();
+ setClusterId(newId);
+ setNodeId(newNodeId);
+ _lock.unlock();
+ }
+
+ @Override
+ public void setAttribute (String name, Object value)
+ {
+ Object old = changeAttribute(name, value);
+ if (value == null && old == null)
+ return; //if same as remove attribute but attribute was already removed, no change
+
+ _dirty = true;
+ }
+
+
+ public String getContextPath()
+ {
+ return _contextPath;
+ }
+
+
+ public void setContextPath(String contextPath)
+ {
+ this._contextPath = contextPath;
+ }
+
+
+ public String getVHost()
+ {
+ return _vhost;
+ }
+
+
+ public void setVHost(String vhost)
+ {
+ this._vhost = vhost;
+ }
+
+ public String getLastNode()
+ {
+ return _lastNode;
+ }
+
+
+ public void setLastNode(String lastNode)
+ {
+ _lastNode = lastNode;
+ }
+
+
+ public long getLastSyncTime()
+ {
+ return _lastSyncTime;
+ }
+
+
+ public void setLastSyncTime(long lastSyncTime)
+ {
+ _lastSyncTime = lastSyncTime;
+ }
+
+ }
+
+
+
+
+ /**
+ * Start the session manager.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart()
+ */
+ @Override
+ public void doStart() throws Exception
+ {
+ if (_sessionIdManager == null)
+ throw new IllegalStateException("No session id manager defined");
+
+ GCloudConfiguration config = ((GCloudSessionIdManager)_sessionIdManager).getConfig();
+ if (config == null)
+ throw new IllegalStateException("No gcloud configuration");
+
+
+ _datastore = DatastoreFactory.instance().get(config.getDatastoreOptions());
+ _keyFactory = _datastore.newKeyFactory().kind(KIND);
+ _converter = new SessionEntityConverter();
+ _sessions = new ConcurrentHashMap<String, Session>();
+
+ //try and use a common scheduler, fallback to own
+ _scheduler = getSessionHandler().getServer().getBean(Scheduler.class);
+ if (_scheduler == null)
+ {
+ _scheduler = new ScheduledExecutorScheduler();
+ _ownScheduler = true;
+ _scheduler.start();
+ }
+ else if (!_scheduler.isStarted())
+ throw new IllegalStateException("Shared scheduler not started");
+
+ setScavengeIntervalSec(getScavengeIntervalSec());
+
+ super.doStart();
+ }
+
+
+ /**
+ * Stop the session manager.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStop()
+ */
+ @Override
+ public void doStop() throws Exception
+ {
+ super.doStop();
+
+ if (_task!=null)
+ _task.cancel();
+ _task=null;
+ if (_ownScheduler && _scheduler !=null)
+ _scheduler.stop();
+ _scheduler = null;
+
+ _sessions.clear();
+ _sessions = null;
+ }
+
+
+
+ /**
+ * Look for sessions in local memory that have expired.
+ */
+ public void scavenge ()
+ {
+ try
+ {
+ //scavenge in the database every so often
+ scavengeGCloudDataStore();
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Problem scavenging", e);
+ }
+ }
+
+
+
+ protected void scavengeGCloudDataStore()
+ throws Exception
+ {
+
+ //query the datastore for sessions that have expired
+ long now = System.currentTimeMillis();
+
+ //give a bit of leeway so we don't immediately something that has only just expired a nanosecond ago
+ now = now - (_scavengeIntervalMs/2);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Scavenging for sessions expired before "+now);
+
+
+ GqlQuery.Builder builder = Query.gqlQueryBuilder(ResultType.ENTITY, "select * from "+KIND+" where expiry < @1 limit "+_maxResults);
+ builder.allowLiteral(true);
+ builder.addBinding(now);
+ Query<Entity> query = builder.build();
+ QueryResults<Entity> results = _datastore.run(query);
+
+ while (results.hasNext())
+ {
+ Entity sessionEntity = results.next();
+ scavengeSession(sessionEntity);
+ }
+
+ }
+
+ /**
+ * Scavenge a session that has expired
+ * @param e
+ * @throws Exception
+ */
+ protected void scavengeSession (Entity e)
+ throws Exception
+ {
+ long now = System.currentTimeMillis();
+ Session session = _converter.sessionFromEntity(e);
+ if (session == null)
+ return;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Scavenging session: {}",session.getId());
+ //if the session isn't in memory already, put it there so we can do a normal timeout call
+ Session memSession = _sessions.putIfAbsent(session.getId(), session);
+ if (memSession == null)
+ {
+ memSession = session;
+ }
+
+ //final check
+ if (memSession.isExpiredAt(now))
+ {
+ if (LOG.isDebugEnabled()) LOG.debug("Session {} is definitely expired", memSession.getId());
+ memSession.timeout();
+ }
+ }
+
+ public long getScavengeIntervalSec ()
+ {
+ return _scavengeIntervalMs/1000;
+ }
+
+
+
+ /**
+ * Set the interval between runs of the scavenger. It should not be run too
+ * often.
+ *
+ *
+ * @param sec
+ */
+ public void setScavengeIntervalSec (long sec)
+ {
+
+ long old_period=_scavengeIntervalMs;
+ long period=sec*1000L;
+
+ _scavengeIntervalMs=period;
+
+ if (_scavengeIntervalMs > 0)
+ {
+ //add a bit of variability into the scavenge time so that not all
+ //nodes with the same scavenge time sync up
+ long tenPercent = _scavengeIntervalMs/10;
+ if ((System.currentTimeMillis()%2) == 0)
+ _scavengeIntervalMs += tenPercent;
+ if (LOG.isDebugEnabled())
+ LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms");
+ }
+ else
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Scavenging disabled");
+ }
+
+
+
+ synchronized (this)
+ {
+ if (_scheduler != null && (period!=old_period || _task==null))
+ {
+ //clean up any previously scheduled scavenger
+ if (_task!=null)
+ _task.cancel();
+
+ //start a new one
+ if (_scavengeIntervalMs > 0)
+ {
+ if (_scavenger == null)
+ _scavenger = new Scavenger();
+
+ _task = _scheduler.schedule(_scavenger,_scavengeIntervalMs,TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+ }
+
+
+ public long getStaleIntervalSec()
+ {
+ return _staleIntervalSec;
+ }
+
+
+ public void setStaleIntervalSec(long staleIntervalSec)
+ {
+ _staleIntervalSec = staleIntervalSec;
+ }
+
+
+ public int getMaxResults()
+ {
+ return _maxResults;
+ }
+
+
+ public void setMaxResults(int maxResults)
+ {
+ if (_maxResults <= 0)
+ _maxResults = DEFAULT_MAX_QUERY_RESULTS;
+ else
+ _maxResults = maxResults;
+ }
+
+
+ /**
+ * Add a new session for the context related to this session manager
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession)
+ */
+ @Override
+ protected void addSession(AbstractSession session)
+ {
+ if (session==null)
+ return;
+
+ if (LOG.isDebugEnabled()) LOG.debug("Adding session({}) to session manager for context {} on worker {}",session.getClusterId(), getContextPath(getContext()),getSessionIdManager().getWorkerName() + " with lastnode="+((Session)session).getLastNode());
+ _sessions.put(session.getClusterId(), (Session)session);
+
+ try
+ {
+ session.willPassivate();
+ save(((GCloudSessionManager.Session)session));
+ session.didActivate();
+
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Unable to store new session id="+session.getId() , e);
+ }
+ }
+
+ /**
+ * Ask the cluster for the session.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String)
+ */
+ @Override
+ public AbstractSession getSession(String idInCluster)
+ {
+ Session session = null;
+
+ //try and find the session in this node's memory
+ Session memSession = (Session)_sessions.get(idInCluster);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("getSession({}) {} in session map",idInCluster,(memSession==null?"not":""));
+
+ long now = System.currentTimeMillis();
+ try
+ {
+ //if the session is not in this node's memory, then load it from the datastore
+ if (memSession == null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("getSession({}): loading session data from cluster", idInCluster);
+
+ session = load(makeKey(idInCluster, _context));
+ if (session != null)
+ {
+ //Check that it wasn't expired
+ if (session.getExpiry() > 0 && session.getExpiry() <= now)
+ {
+ if (LOG.isDebugEnabled()) LOG.debug("getSession ({}): Session expired", idInCluster);
+ //ensure that the session id for the expired session is deleted so that a new session with the
+ //same id cannot be created (because the idInUse() test would succeed)
+ ((GCloudSessionIdManager)getSessionIdManager()).removeSession(session);
+ return null;
+ }
+
+ //Update the last worker node to me
+ session.setLastNode(getSessionIdManager().getWorkerName());
+ //TODO consider saving session here if lastNode was not this node
+
+ //Check that another thread hasn't loaded the same session
+ Session existingSession = _sessions.putIfAbsent(idInCluster, session);
+ if (existingSession != null)
+ {
+ //use the one that the other thread inserted
+ session = existingSession;
+ LOG.debug("getSession({}): using session loaded by another request thread ", idInCluster);
+ }
+ else
+ {
+ //indicate that the session was reinflated
+ session.didActivate();
+ LOG.debug("getSession({}): loaded session from cluster", idInCluster);
+ }
+ return session;
+ }
+ else
+ {
+ //The requested session does not exist anywhere in the cluster
+ LOG.debug("getSession({}): No session in cluster matching",idInCluster);
+ return null;
+ }
+ }
+ else
+ {
+ //The session exists in this node's memory
+ LOG.debug("getSession({}): returning session from local memory ", memSession.getClusterId());
+ return memSession;
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Unable to load session="+idInCluster, e);
+ return null;
+ }
+ }
+
+
+
+ /**
+ * The session manager is stopping.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#shutdownSessions()
+ */
+ @Override
+ protected void shutdownSessions() throws Exception
+ {
+ Set<String> keys = new HashSet<String>(_sessions.keySet());
+ for (String key:keys)
+ {
+ Session session = _sessions.remove(key); //take the session out of the session list
+ //If the session is dirty, then write it to the cluster.
+ //If the session is simply stale do NOT write it to the cluster, as some other node
+ //may have started managing that session - this means that the last accessed/expiry time
+ //will not be updated, meaning it may look like it can expire sooner than it should.
+ try
+ {
+ if (session.isDirty())
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Saving dirty session {} before exiting ", session.getId());
+ save(session);
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+
+
+ @Override
+ protected AbstractSession newSession(HttpServletRequest request)
+ {
+ return new Session(request);
+ }
+
+ /**
+ * Remove a session from local memory, and delete it from
+ * the cluster cache.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String)
+ */
+ @Override
+ protected boolean removeSession(String idInCluster)
+ {
+ Session session = (Session)_sessions.remove(idInCluster);
+ try
+ {
+ if (session != null)
+ {
+ delete(session);
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Problem deleting session id="+idInCluster, e);
+ }
+ return session!=null;
+ }
+
+
+
+
+ @Override
+ public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+ {
+ Session session = null;
+ try
+ {
+ //take the session with that id out of our managed list
+ session = (Session)_sessions.remove(oldClusterId);
+ if (session != null)
+ {
+ //TODO consider transactionality and ramifications if the session is live on another node
+ delete(session); //delete the old session from the cluster
+ session.swapId(newClusterId, newNodeId); //update the session
+ _sessions.put(newClusterId, session); //put it into managed list under new key
+ save(session); //put the session under the new id into the cluster
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+
+ super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
+ }
+
+
+ /**
+ * Load a session from the clustered cache.
+ *
+ * @param key
+ * @return
+ */
+ protected Session load (Key key)
+ throws Exception
+ {
+ if (_datastore == null)
+ throw new IllegalStateException("No DataStore");
+
+ if (LOG.isDebugEnabled()) LOG.debug("Loading session {} from DataStore", key);
+
+ Entity entity = _datastore.get(key);
+ if (entity == null)
+ {
+ if (LOG.isDebugEnabled()) LOG.debug("No session {} in DataStore ",key);
+ return null;
+ }
+ else
+ {
+ Session session = _converter.sessionFromEntity(entity);
+ session.setLastSyncTime(System.currentTimeMillis());
+ return session;
+ }
+ }
+
+
+
+ /**
+ * Save or update the session to the cluster cache
+ *
+ * @param session
+ * @throws Exception
+ */
+ protected void save (GCloudSessionManager.Session session)
+ throws Exception
+ {
+ if (_datastore == null)
+ throw new IllegalStateException("No DataStore");
+
+ if (LOG.isDebugEnabled()) LOG.debug("Writing session {} to DataStore", session.getId());
+
+ Entity entity = _converter.entityFromSession(session, makeKey(session, _context));
+ _datastore.put(entity);
+ session.setLastSyncTime(System.currentTimeMillis());
+ }
+
+
+
+ /**
+ * Remove the session from the cluster cache.
+ *
+ * @param session
+ */
+ protected void delete (GCloudSessionManager.Session session)
+ {
+ if (_datastore == null)
+ throw new IllegalStateException("No DataStore");
+ if (LOG.isDebugEnabled()) LOG.debug("Removing session {} from DataStore", session.getId());
+ _datastore.delete(makeKey(session, _context));
+ }
+
+
+ /**
+ * Invalidate a session for this context with the given id
+ *
+ * @param idInCluster
+ */
+ public void invalidateSession (String idInCluster)
+ {
+ Session session = (Session)_sessions.get(idInCluster);
+
+ if (session != null)
+ {
+ session.invalidate();
+ }
+ }
+
+
+ /**
+ * Make a unique key for this session.
+ * As the same session id can be used across multiple contexts, to
+ * make it unique, the key must be composed of:
+ * <ol>
+ * <li>the id</li>
+ * <li>the context path</li>
+ * <li>the virtual hosts</li>
+ * </ol>
+ *
+ *TODO consider the difference between getClusterId and getId
+ * @param session
+ * @return
+ */
+ private Key makeKey (Session session, Context context)
+ {
+ return makeKey(session.getId(), context);
+ }
+
+ /**
+ * Make a unique key for this session.
+ * As the same session id can be used across multiple contexts, to
+ * make it unique, the key must be composed of:
+ * <ol>
+ * <li>the id</li>
+ * <li>the context path</li>
+ * <li>the virtual hosts</li>
+ * </ol>
+ *
+ *TODO consider the difference between getClusterId and getId
+ * @param session
+ * @return
+ */
+ private Key makeKey (String id, Context context)
+ {
+ String key = getContextPath(context);
+ key = key + "_" + getVirtualHost(context);
+ key = key+"_"+id;
+ return _keyFactory.newKey(key);
+ }
+
+ /**
+ * Turn the context path into an acceptable string
+ *
+ * @param context
+ * @return
+ */
+ private static String getContextPath (ContextHandler.Context context)
+ {
+ return canonicalize (context.getContextPath());
+ }
+
+ /**
+ * Get the first virtual host for the context.
+ *
+ * Used to help identify the exact session/contextPath.
+ *
+ * @return 0.0.0.0 if no virtual host is defined
+ */
+ private static String getVirtualHost (ContextHandler.Context context)
+ {
+ String vhost = "0.0.0.0";
+
+ if (context==null)
+ return vhost;
+
+ String [] vhosts = context.getContextHandler().getVirtualHosts();
+ if (vhosts==null || vhosts.length==0 || vhosts[0]==null)
+ return vhost;
+
+ return vhosts[0];
+ }
+
+ /**
+ * Make an acceptable name from a context path.
+ *
+ * @param path
+ * @return
+ */
+ private static String canonicalize (String path)
+ {
+ if (path==null)
+ return "";
+
+ return path.replace('/', '_').replace('.','_').replace('\\','_');
+ }
+
+}
diff --git a/jetty-gcloud/gcloud-session-manager/src/test/java/org/eclipse/jetty/gcloud/session/GCloudSessionTester.java b/jetty-gcloud/gcloud-session-manager/src/test/java/org/eclipse/jetty/gcloud/session/GCloudSessionTester.java
new file mode 100644
index 0000000000..fe596e24ba
--- /dev/null
+++ b/jetty-gcloud/gcloud-session-manager/src/test/java/org/eclipse/jetty/gcloud/session/GCloudSessionTester.java
@@ -0,0 +1,75 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.gcloud.session;
+
+
+
+
+import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+public class GCloudSessionTester
+{
+ public static void main( String[] args ) throws Exception
+ {
+ if (args.length < 4)
+ System.err.println("Usage: GCloudSessionTester projectid p12file password serviceaccount");
+
+ System.setProperty("org.eclipse.jetty.server.session.LEVEL", "DEBUG");
+
+ Server server = new Server(8080);
+ HashLoginService loginService = new HashLoginService();
+ loginService.setName( "Test Realm" );
+ loginService.setConfig( "../../jetty-distribution/target/distribution/demo-base/resources/realm.properties" );
+ server.addBean( loginService );
+
+ GCloudConfiguration config = new GCloudConfiguration();
+ config.setProjectId(args[0]);
+ config.setP12File(args[1]);
+ config.setPassword(args[2]);
+ config.setServiceAccount(args[3]);
+
+ GCloudSessionIdManager idmgr = new GCloudSessionIdManager(server);
+ idmgr.setConfig(config);
+ idmgr.setWorkerName("w1");
+ server.setSessionIdManager(idmgr);
+
+
+ WebAppContext webapp = new WebAppContext();
+ webapp.setContextPath("/");
+ webapp.setWar("../../jetty-distribution/target/distribution/demo-base/webapps/test.war");
+ webapp.addAliasCheck(new AllowSymLinkAliasChecker());
+ GCloudSessionManager mgr = new GCloudSessionManager();
+ mgr.setSessionIdManager(idmgr);
+ webapp.setSessionHandler(new SessionHandler(mgr));
+
+ // A WebAppContext is a ContextHandler as well so it needs to be set to
+ // the server so it is aware of where to send the appropriate requests.
+ server.setHandler(webapp);
+
+ // Start things up!
+ server.start();
+
+
+ server.join();
+ }
+}
diff --git a/jetty-gcloud/pom.xml b/jetty-gcloud/pom.xml
new file mode 100644
index 0000000000..af97322fe7
--- /dev/null
+++ b/jetty-gcloud/pom.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>jetty-project</artifactId>
+ <groupId>org.eclipse.jetty</groupId>
+ <version>9.3.4-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.eclipse.jetty.gcloud</groupId>
+ <artifactId>gcloud-parent</artifactId>
+ <packaging>pom</packaging>
+ <name>Jetty :: GCloud</name>
+
+ <properties>
+ <gcloud.version>0.0.8</gcloud.version>
+ </properties>
+
+ <modules>
+ <module>gcloud-session-manager</module>
+ </modules>
+
+</project>
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
index 42b7086d4c..1ae25d8a66 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
@@ -73,6 +73,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
private static final Logger LOG = Log.getLogger(ServletHolder.class);
private int _initOrder = -1;
private boolean _initOnStartup=false;
+ private boolean _initialized = false;
private Map<String, String> _roleMap;
private String _forcedPath;
private String _runAsRole;
@@ -81,7 +82,6 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
private ServletRegistration.Dynamic _registration;
private JspContainer _jspContainer;
-
private transient Servlet _servlet;
private transient Config _config;
private transient long _unavailable;
@@ -396,21 +396,24 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
public void initialize ()
throws Exception
{
- super.initialize();
- if (_extInstance || _initOnStartup)
- {
- try
+ if(!_initialized){
+ super.initialize();
+ if (_extInstance || _initOnStartup)
{
- initServlet();
- }
- catch(Exception e)
- {
- if (_servletHandler.isStartWithUnavailable())
- LOG.ignore(e);
- else
- throw e;
+ try
+ {
+ initServlet();
+ }
+ catch(Exception e)
+ {
+ if (_servletHandler.isStartWithUnavailable())
+ LOG.ignore(e);
+ else
+ throw e;
+ }
}
}
+ _initialized = true;
}
@@ -443,6 +446,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
_servlet=null;
_config=null;
+ _initialized = false;
}
/* ------------------------------------------------------------ */
diff --git a/pom.xml b/pom.xml
index 6fbae2cb70..6ca00ac28e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -530,6 +530,7 @@
<module>jetty-rewrite</module>
<module>jetty-nosql</module>
<module>jetty-infinispan</module>
+ <module>jetty-gcloud</module>
<module>tests</module>
<module>examples</module>
<module>jetty-quickstart</module>
diff --git a/tests/test-sessions/pom.xml b/tests/test-sessions/pom.xml
index b2e8e22767..23fed9a0b4 100644
--- a/tests/test-sessions/pom.xml
+++ b/tests/test-sessions/pom.xml
@@ -35,5 +35,6 @@
<module>test-jdbc-sessions</module>
<module>test-mongodb-sessions</module>
<module>test-infinispan-sessions</module>
+ <module>test-gcloud-sessions</module>
</modules>
</project>
diff --git a/tests/test-sessions/test-gcloud-sessions/pom.xml b/tests/test-sessions/test-gcloud-sessions/pom.xml
new file mode 100644
index 0000000000..1c487b7f3b
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/pom.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+// ========================================================================
+// Copyright (c) Webtide LLC
+//
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.eclipse.jetty.tests</groupId>
+ <artifactId>test-sessions-parent</artifactId>
+ <version>9.3.4-SNAPSHOT</version>
+ </parent>
+ <artifactId>test-gcloud-sessions</artifactId>
+ <name>Jetty Tests :: Sessions :: GCloud</name>
+ <url>http://www.eclipse.org/jetty</url>
+ <properties>
+ <bundle-symbolic-name>${project.groupId}.sessions.gcloud</bundle-symbolic-name>
+ </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <!-- DO NOT DEPLOY (or Release) -->
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skipTests>true</skipTests>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-webapp</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.tests</groupId>
+ <artifactId>test-sessions-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.gcloud</groupId>
+ <artifactId>gcloud-session-manager</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-test-helper</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <profiles>
+ <profile>
+ <id>gcloud</id>
+ <activation>
+ <property>
+ <name>gcloud.enabled</name>
+ <value>true</value>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skipTests>false</skipTests>
+ <systemPropertyVariables>
+ <test.projectId>jetty9-work</test.projectId>
+ <test.port>8088</test.port>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ClientCrossContextSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ClientCrossContextSessionTest.java
new file mode 100644
index 0000000000..5d05a3f698
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ClientCrossContextSessionTest.java
@@ -0,0 +1,70 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractClientCrossContextSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * ClientCrossContextSessionTest
+ *
+ *
+ */
+public class ClientCrossContextSessionTest extends AbstractClientCrossContextSessionTest
+{
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractClientCrossContextSessionTest#createServer(int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port)
+ {
+ return new GCloudTestServer(port, _testSupport.getConfiguration());
+ }
+
+ @Test
+ @Override
+ public void testCrossContextDispatch() throws Exception
+ {
+ super.testCrossContextDispatch();
+ }
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ForwardedSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ForwardedSessionTest.java
new file mode 100644
index 0000000000..20109ae51f
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ForwardedSessionTest.java
@@ -0,0 +1,62 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractForwardedSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+/**
+ * ForwardedSessionTest
+ *
+ *
+ */
+public class ForwardedSessionTest extends AbstractForwardedSessionTest
+{
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractForwardedSessionTest#createServer(int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port)
+ {
+ return new GCloudTestServer(port, _testSupport.getConfiguration());
+ }
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/GCloudSessionTestSupport.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/GCloudSessionTestSupport.java
new file mode 100644
index 0000000000..adfdf9b7a5
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/GCloudSessionTestSupport.java
@@ -0,0 +1,372 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.resource.JarResource;
+import org.eclipse.jetty.util.resource.Resource;
+
+import com.google.api.client.util.Strings;
+import com.google.gcloud.datastore.Key;
+import com.google.gcloud.datastore.Datastore;
+import com.google.gcloud.datastore.DatastoreFactory;
+import com.google.gcloud.datastore.DatastoreOptions;
+import com.google.gcloud.datastore.Entity;
+import com.google.gcloud.datastore.GqlQuery;
+import com.google.gcloud.datastore.ProjectionEntity;
+import com.google.gcloud.datastore.Query;
+import com.google.gcloud.datastore.Query.ResultType;
+import com.google.gcloud.datastore.QueryResults;
+import com.google.gcloud.datastore.StructuredQuery;
+import com.google.gcloud.datastore.StructuredQuery.Projection;
+
+/**
+ * GCloudSessionTestSupport
+ *
+ *
+ */
+public class GCloudSessionTestSupport
+{
+
+ /**
+ * GCloudTestConfiguration
+ *
+ * Specialization of GCloudConfiguration for gcd test environment
+ *
+ */
+ public class GCloudTestConfiguration extends GCloudConfiguration
+ {
+ int _port;
+
+ public GCloudTestConfiguration(String projectId, int port)
+ {
+ setProjectId(projectId);
+ _port = port;
+ }
+
+
+ @Override
+ public DatastoreOptions getDatastoreOptions() throws Exception
+ {
+ return DatastoreOptions.builder()
+ .projectId(_projectId)
+ .host("http://localhost:" + _port)
+ .build();
+ }
+ }
+
+
+ private static class ProcessOutputReader implements Runnable
+ {
+ private InputStream _is;
+ private String _startupSentinel;
+ private BufferedReader _reader;
+
+ public ProcessOutputReader (InputStream is, String startupSentinel)
+ throws Exception
+ {
+ _is = is;
+ _startupSentinel = startupSentinel;
+ _reader = new BufferedReader(new InputStreamReader(_is));
+ if (!Strings.isNullOrEmpty(_startupSentinel))
+ {
+ String line;
+ while ((line = _reader.readLine()) != (null) && !line.contains(_startupSentinel))
+ {
+ //System.err.println(line);
+ }
+ }
+ }
+
+
+ public void run()
+ {
+ String line;
+ try
+ {
+ while ((line = _reader.readLine()) != (null))
+ {
+ }
+ }
+ catch (IOException ignore)
+ {
+ /* ignore */
+ }
+ finally
+ {
+ IO.close(_reader);
+ }
+ }
+ }
+
+
+ public static String DEFAULT_PROJECTID = "jetty9-work";
+ public static int DEFAULT_PORT = 8088;
+ public static String DEFAULT_GCD_ZIP = "gcd-v1beta2-rev1-2.1.2b.zip";
+ public static String DEFAULT_GCD_UNPACKED = "gcd-v1beta2-rev1-2.1.2b";
+ public static String DEFAULT_DOWNLOAD_URL = "http://storage.googleapis.com/gcd/tools/";
+
+ String _projectId;
+ int _port;
+ File _datastoreDir;
+ File _gcdInstallDir;
+ File _gcdUnpackedDir;
+ Datastore _ds;
+
+ public GCloudSessionTestSupport (String projectId, int port, File gcdInstallDir)
+ {
+ _projectId = projectId;
+ if (_projectId == null)
+ _projectId = DEFAULT_PROJECTID;
+ _port = port;
+ if (_port <= 0)
+ _port = DEFAULT_PORT;
+
+ _gcdInstallDir = gcdInstallDir;
+ if (_gcdInstallDir == null)
+ _gcdInstallDir = new File (System.getProperty("java.io.tmpdir"));
+ }
+
+ public GCloudSessionTestSupport ()
+ {
+ this(null,0, null);
+ }
+
+ public GCloudConfiguration getConfiguration ()
+ {
+ return new GCloudTestConfiguration(_projectId, _port);
+ }
+
+
+ public void setUp()
+ throws Exception
+ {
+ downloadGCD();
+ createDatastore();
+ startDatastore();
+ }
+
+
+ public void downloadGCD()
+ throws Exception
+ {
+ File zipFile = new File (_gcdInstallDir, DEFAULT_GCD_ZIP);
+ _gcdUnpackedDir = new File (_gcdInstallDir, DEFAULT_GCD_UNPACKED);
+ File gcdSh = new File (_gcdUnpackedDir, "gcd.sh");
+ if (gcdSh.exists())
+ return;
+
+
+ if (_gcdInstallDir.exists() && !zipFile.exists())
+ {
+ //download it
+ ReadableByteChannel rbc = Channels.newChannel(new URL(DEFAULT_DOWNLOAD_URL+DEFAULT_GCD_ZIP).openStream());
+ try (FileOutputStream fos = new FileOutputStream(zipFile))
+ {
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ }
+ }
+
+ if (zipFile.exists())
+ {
+ //unpack it
+ Resource zipResource = JarResource.newJarResource(Resource.newResource(zipFile));
+ zipResource.copyTo(_gcdInstallDir);
+ }
+
+ System.err.println("GCD downloaded and unpacked");
+ }
+
+
+
+ public void createDatastore ()
+ throws Exception
+ {
+
+ _datastoreDir = Files.createTempDirectory("gcloud-sessions").toFile();
+ _datastoreDir.deleteOnExit();
+
+ ProcessBuilder processBuilder = new ProcessBuilder();
+ processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
+ processBuilder.directory(_datastoreDir);
+ if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows"))
+ {
+ processBuilder.command("cmd", "/C", new File(_gcdUnpackedDir, "gcd.cmd").getAbsolutePath(), "create", "-p", _projectId, _projectId);
+ processBuilder.redirectOutput(new File("NULL:"));
+ }
+ else
+ {
+ processBuilder.redirectOutput(new File("/tmp/run.out"));
+ processBuilder.command("bash", new File(_gcdUnpackedDir, "gcd.sh").getAbsolutePath(), "create", "-p",_projectId, _projectId);
+ }
+
+ Process temp = processBuilder.start();
+ System.err.println("Create outcome: "+temp.waitFor());
+ }
+
+
+ public void startDatastore()
+ throws Exception
+ {
+ //start the datastore for the test
+ ProcessBuilder processBuilder = new ProcessBuilder();
+ processBuilder.directory(_datastoreDir);
+ processBuilder.redirectErrorStream(true);
+ if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows"))
+ {
+ processBuilder.command("cmd", "/C", new File(_gcdUnpackedDir, "gcd.cmd").getAbsolutePath(), "start", "--testing", "--allow_remote_shutdown","--port="+String.valueOf(_port), _projectId);
+ }
+ else
+ {
+ processBuilder.command("bash", new File(_gcdUnpackedDir, "gcd.sh").getAbsolutePath(), "start", "--testing", "--allow_remote_shutdown", "--port="+String.valueOf(_port), _projectId);
+ }
+
+ System.err.println("Starting datastore");
+ Process temp = processBuilder.start();
+ ProcessOutputReader reader = new ProcessOutputReader(temp.getInputStream(), "Dev App Server is now running");
+ Thread readerThread = new Thread(reader, "GCD reader");
+ readerThread.setDaemon(true);
+ readerThread.start();
+ }
+
+ public void stopDatastore()
+ throws Exception
+ {
+ //Send request to terminate test datastore
+ URL url = new URL("http", "localhost", _port, "/_ah/admin/quit");
+ HttpURLConnection con = (HttpURLConnection) url.openConnection();
+ con.setRequestMethod("POST");
+ con.setDoOutput(true);
+ con.setDoInput(true);
+ OutputStream out = con.getOutputStream();
+ out.write("".getBytes());
+ out.flush();
+ InputStream in = con.getInputStream();
+ while (in.read() != -1)
+ {
+ // consume input
+
+ }
+
+ System.err.println("Stop issued");
+ }
+
+
+ public void clearDatastore()
+ {
+ org.eclipse.jetty.util.IO.delete(_datastoreDir);
+ }
+
+ public void tearDown()
+ throws Exception
+ {
+ stopDatastore();
+ clearDatastore();
+ }
+
+ public void ensureDatastore()
+ throws Exception
+ {
+ if (_ds == null)
+ _ds = DatastoreFactory.instance().get(getConfiguration().getDatastoreOptions());
+ }
+ public void listSessions () throws Exception
+ {
+ ensureDatastore();
+ GqlQuery.Builder builder = Query.gqlQueryBuilder(ResultType.ENTITY, "select * from "+GCloudSessionManager.KIND);
+
+ Query<Entity> query = builder.build();
+
+ QueryResults<Entity> results = _ds.run(query);
+ assertNotNull(results);
+ System.err.println("SESSIONS::::::::");
+ while (results.hasNext())
+ {
+
+ Entity e = results.next();
+ System.err.println(e.getString("clusterId")+" expires at "+e.getLong("expiry"));
+ }
+ System.err.println("END OF SESSIONS::::::::");
+ }
+
+ public void assertSessions(int count) throws Exception
+ {
+ ensureDatastore();
+ StructuredQuery<ProjectionEntity> keyOnlyProjectionQuery = Query.projectionEntityQueryBuilder()
+ .kind(GCloudSessionManager.KIND)
+ .projection(Projection.property("__key__"))
+ .limit(100)
+ .build();
+ QueryResults<ProjectionEntity> results = _ds.run(keyOnlyProjectionQuery);
+ assertNotNull(results);
+ int actual = 0;
+ while (results.hasNext())
+ {
+ results.next();
+ ++actual;
+ }
+ assertEquals(count, actual);
+ }
+
+ public void deleteSessions () throws Exception
+ {
+ ensureDatastore();
+ StructuredQuery<ProjectionEntity> keyOnlyProjectionQuery = Query.projectionEntityQueryBuilder()
+ .kind(GCloudSessionManager.KIND)
+ .projection(Projection.property("__key__"))
+ .limit(100)
+ .build();
+ QueryResults<ProjectionEntity> results = _ds.run(keyOnlyProjectionQuery);
+ if (results != null)
+ {
+ List<Key> keys = new ArrayList<Key>();
+
+ while (results.hasNext())
+ {
+ ProjectionEntity pe = results.next();
+ keys.add(pe.key());
+ }
+
+ _ds.delete(keys.toArray(new Key[keys.size()]));
+ }
+
+ assertSessions(0);
+ }
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/GCloudTestServer.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/GCloudTestServer.java
new file mode 100644
index 0000000000..11f2125f68
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/GCloudTestServer.java
@@ -0,0 +1,97 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.eclipse.jetty.server.session.SessionHandler;
+
+import com.google.gcloud.datastore.Datastore;
+import com.google.gcloud.datastore.DatastoreFactory;
+
+/**
+ * GCloudTestServer
+ *
+ *
+ */
+public class GCloudTestServer extends AbstractTestServer
+{
+ static int __workers=0;
+ public static int STALE_INTERVAL_SEC = 1;
+
+
+
+ /**
+ * @param port
+ * @param maxInactivePeriod
+ * @param scavengePeriod
+ * @param sessionIdMgrConfig
+ */
+ public GCloudTestServer(int port, int maxInactivePeriod, int scavengePeriod, GCloudConfiguration config)
+ {
+ super(port, maxInactivePeriod, scavengePeriod, config);
+ }
+
+ /**
+ * @param port
+ * @param configuration
+ */
+ public GCloudTestServer(int port, GCloudConfiguration configuration)
+ {
+ super(port, 30,10, configuration);
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestServer#newSessionIdManager(java.lang.Object)
+ */
+ @Override
+ public SessionIdManager newSessionIdManager(Object config)
+ {
+ GCloudSessionIdManager idManager = new GCloudSessionIdManager(getServer());
+ idManager.setWorkerName("w"+(__workers++));
+ idManager.setConfig((GCloudConfiguration)config);
+ return idManager;
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestServer#newSessionManager()
+ */
+ @Override
+ public SessionManager newSessionManager()
+ {
+ GCloudSessionManager sessionManager = new GCloudSessionManager();
+ sessionManager.setSessionIdManager((GCloudSessionIdManager)_sessionIdManager);
+ sessionManager.setStaleIntervalSec(STALE_INTERVAL_SEC);
+ sessionManager.setScavengeIntervalSec(_scavengePeriod);
+ return sessionManager;
+
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestServer#newSessionHandler(org.eclipse.jetty.server.SessionManager)
+ */
+ @Override
+ public SessionHandler newSessionHandler(SessionManager sessionManager)
+ {
+ return new SessionHandler(sessionManager);
+ }
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ImmortalSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ImmortalSessionTest.java
new file mode 100644
index 0000000000..84c3840d21
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ImmortalSessionTest.java
@@ -0,0 +1,70 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractImmortalSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * ImmortalSessionTest
+ *
+ *
+ */
+public class ImmortalSessionTest extends AbstractImmortalSessionTest
+{
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractImmortalSessionTest#createServer(int, int, int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port, int maxInactiveMs, int scavengeMs)
+ {
+ return new GCloudTestServer(port, port, scavengeMs, _testSupport.getConfiguration());
+ }
+
+ @Test
+ @Override
+ public void testImmortalSession() throws Exception
+ {
+ super.testImmortalSession();
+ }
+
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/InvalidationSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/InvalidationSessionTest.java
new file mode 100644
index 0000000000..6cc875cb71
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/InvalidationSessionTest.java
@@ -0,0 +1,82 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractInvalidationSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+/**
+ * InvalidationSessionTest
+ *
+ *
+ */
+public class InvalidationSessionTest extends AbstractInvalidationSessionTest
+{
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractInvalidationSessionTest#createServer(int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port)
+ {
+ return new GCloudTestServer(port, _testSupport.getConfiguration());
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractInvalidationSessionTest#pause()
+ */
+ @Override
+ public void pause()
+ {
+ //This test moves around a session between 2 nodes. After it is invalidated on the 1st node,
+ //it will still be in the memory of the 2nd node. We need to wait until after the stale time
+ //has expired on node2 for it to reload the session and discover it has been deleted.
+ try
+ {
+ Thread.currentThread().sleep((2*GCloudTestServer.STALE_INTERVAL_SEC)*1000);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ }
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/LastAccessTimeTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/LastAccessTimeTest.java
new file mode 100644
index 0000000000..d4f42fe78d
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/LastAccessTimeTest.java
@@ -0,0 +1,70 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractLastAccessTimeTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * LastAccessTimeTest
+ *
+ *
+ */
+public class LastAccessTimeTest extends AbstractLastAccessTimeTest
+{
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractLastAccessTimeTest#createServer(int, int, int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port, int max, int scavenge)
+ {
+ return new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+ }
+
+ @Test
+ @Override
+ public void testLastAccessTime() throws Exception
+ {
+ super.testLastAccessTime();
+ }
+
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/LocalSessionScavengingTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/LocalSessionScavengingTest.java
new file mode 100644
index 0000000000..ca39661a30
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/LocalSessionScavengingTest.java
@@ -0,0 +1,71 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractLocalSessionScavengingTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * LocalSessionScavengingTest
+ *
+ *
+ */
+public class LocalSessionScavengingTest extends AbstractLocalSessionScavengingTest
+{
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractLocalSessionScavengingTest#createServer(int, int, int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port, int max, int scavenge)
+ {
+ return new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+ }
+
+ @Test
+ @Override
+ public void testLocalSessionsScavenging() throws Exception
+ {
+ super.testLocalSessionsScavenging();
+ }
+
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/NewSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/NewSessionTest.java
new file mode 100644
index 0000000000..1dc256b74b
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/NewSessionTest.java
@@ -0,0 +1,74 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import java.io.File;
+
+import org.eclipse.jetty.server.session.AbstractNewSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * NewSessionTest
+ *
+ *
+ */
+public class NewSessionTest extends AbstractNewSessionTest
+{
+ GCloudSessionTestSupport _testSupport;
+
+ @Before
+ public void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @After
+ public void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractNewSessionTest#createServer(int, int, int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port, int max, int scavenge)
+ {
+ return new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+ }
+
+ @Test
+ public void testNewSession() throws Exception
+ {
+ super.testNewSession();
+ }
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/OrphanedSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/OrphanedSessionTest.java
new file mode 100644
index 0000000000..ffaaaec0da
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/OrphanedSessionTest.java
@@ -0,0 +1,75 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractOrphanedSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * OrphanedSessionTest
+ *
+ *
+ */
+public class OrphanedSessionTest extends AbstractOrphanedSessionTest
+{
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractOrphanedSessionTest#createServer(int, int, int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port, int max, int scavenge)
+ {
+ return new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+ }
+
+ @Test
+ @Override
+ public void testOrphanedSession() throws Exception
+ {
+ super.testOrphanedSession();
+ _testSupport.assertSessions(0);
+ }
+
+
+
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ReentrantRequestSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ReentrantRequestSessionTest.java
new file mode 100644
index 0000000000..e8216187a5
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ReentrantRequestSessionTest.java
@@ -0,0 +1,72 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractReentrantRequestSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * ReentrantRequestSessionTest
+ *
+ *
+ */
+public class ReentrantRequestSessionTest extends AbstractReentrantRequestSessionTest
+{
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractReentrantRequestSessionTest#createServer(int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port)
+ {
+ return new GCloudTestServer(port, _testSupport.getConfiguration());
+ }
+
+ @Test
+ @Override
+ public void testReentrantRequestSession() throws Exception
+ {
+ super.testReentrantRequestSession();
+ }
+
+
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/RemoveSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/RemoveSessionTest.java
new file mode 100644
index 0000000000..5b7f99b3d9
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/RemoveSessionTest.java
@@ -0,0 +1,75 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractRemoveSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * RemoveSessionTest
+ *
+ *
+ */
+public class RemoveSessionTest extends AbstractRemoveSessionTest
+{
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractRemoveSessionTest#createServer(int, int, int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port, int max, int scavenge)
+ {
+ return new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+ }
+
+ @Test
+ @Override
+ public void testRemoveSession() throws Exception
+ {
+ super.testRemoveSession();
+ }
+
+
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SameNodeLoadTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SameNodeLoadTest.java
new file mode 100644
index 0000000000..e230f43d67
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SameNodeLoadTest.java
@@ -0,0 +1,71 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractSameNodeLoadTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * SameNodeLoadTest
+ *
+ *
+ */
+public class SameNodeLoadTest extends AbstractSameNodeLoadTest
+{
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractSameNodeLoadTest#createServer(int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port)
+ {
+ return new GCloudTestServer(port, _testSupport.getConfiguration());
+ }
+
+ @Test
+ @Override
+ public void testLoad() throws Exception
+ {
+ super.testLoad();
+ }
+
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ServerCrossContextSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ServerCrossContextSessionTest.java
new file mode 100644
index 0000000000..59f366b6c7
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ServerCrossContextSessionTest.java
@@ -0,0 +1,71 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractServerCrossContextSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * ServerCrossContextSessionTest
+ *
+ *
+ */
+public class ServerCrossContextSessionTest extends AbstractServerCrossContextSessionTest
+{
+
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractServerCrossContextSessionTest#createServer(int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port)
+ {
+ return new GCloudTestServer(port, _testSupport.getConfiguration());
+ }
+
+ @Test
+ @Override
+ public void testCrossContextDispatch() throws Exception
+ {
+ super.testCrossContextDispatch();
+ }
+
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionExpiryTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionExpiryTest.java
new file mode 100644
index 0000000000..785a2a19ec
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionExpiryTest.java
@@ -0,0 +1,102 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractSessionExpiryTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * SessionExpiryTest
+ *
+ *
+ */
+public class SessionExpiryTest extends AbstractSessionExpiryTest
+{
+
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractSessionExpiryTest#createServer(int, int, int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port, int max, int scavenge)
+ {
+ return new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+ }
+
+ @Test
+ @Override
+ public void testSessionNotExpired() throws Exception
+ {
+ super.testSessionNotExpired();
+ _testSupport.deleteSessions();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractSessionExpiryTest#testSessionExpiry()
+ */
+ @Test
+ @Override
+ public void testSessionExpiry() throws Exception
+ {
+ super.testSessionExpiry();
+ _testSupport.assertSessions(0);
+ }
+
+ @Override
+ public void verifySessionCreated(TestHttpSessionListener listener, String sessionId)
+ {
+ super.verifySessionCreated(listener, sessionId);
+ try{ _testSupport.listSessions(); _testSupport.assertSessions(1);}catch(Exception e) {e.printStackTrace();}
+ }
+
+ @Override
+ public void verifySessionDestroyed(TestHttpSessionListener listener, String sessionId)
+ {
+ super.verifySessionDestroyed(listener, sessionId);
+ try{ _testSupport.listSessions(); _testSupport.assertSessions(0);}catch(Exception e) {e.printStackTrace();}
+ }
+
+
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionInvalidateAndCreateTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionInvalidateAndCreateTest.java
new file mode 100644
index 0000000000..b56a13ea54
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionInvalidateAndCreateTest.java
@@ -0,0 +1,73 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractSessionInvalidateAndCreateTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * SessionInvalidateAndCreateTest
+ *
+ *
+ */
+public class SessionInvalidateAndCreateTest extends AbstractSessionInvalidateAndCreateTest
+{
+
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractSessionInvalidateAndCreateTest#createServer(int, int, int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port, int max, int scavenge)
+ {
+ return new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+ }
+
+ @Test
+ @Override
+ public void testSessionScavenge() throws Exception
+ {
+ super.testSessionScavenge();
+ }
+
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionMigrationTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionMigrationTest.java
new file mode 100644
index 0000000000..5ed5740746
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionMigrationTest.java
@@ -0,0 +1,72 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractSessionMigrationTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * SessionMigrationTest
+ *
+ *
+ */
+public class SessionMigrationTest extends AbstractSessionMigrationTest
+{
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractSessionMigrationTest#createServer(int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port)
+ {
+ return new GCloudTestServer(port, _testSupport.getConfiguration());
+ }
+
+
+ @Test
+ @Override
+ public void testSessionMigration() throws Exception
+ {
+ super.testSessionMigration();
+ }
+
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionRenewTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionRenewTest.java
new file mode 100644
index 0000000000..c7b0c878bd
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionRenewTest.java
@@ -0,0 +1,71 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractSessionRenewTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * SessionRenewTest
+ *
+ *
+ */
+public class SessionRenewTest extends AbstractSessionRenewTest
+{
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractSessionRenewTest#createServer(int, int, int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port, int max, int scavenge)
+ {
+ return new GCloudTestServer(port,max, scavenge, _testSupport.getConfiguration());
+ }
+
+ @Test
+ @Override
+ public void testSessionRenewal() throws Exception
+ {
+ super.testSessionRenewal();
+ }
+
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionValueSavingTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionValueSavingTest.java
new file mode 100644
index 0000000000..54bf8ffac4
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionValueSavingTest.java
@@ -0,0 +1,72 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractSessionValueSavingTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * SessionValueSavingTest
+ *
+ *
+ */
+public class SessionValueSavingTest extends AbstractSessionValueSavingTest
+{
+
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractSessionValueSavingTest#createServer(int, int, int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port, int max, int scavenge)
+ {
+ return new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+ }
+
+ @Test
+ @Override
+ public void testSessionValueSaving() throws Exception
+ {
+ super.testSessionValueSaving();
+ }
+
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/StopSessionManagerPreserveSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/StopSessionManagerPreserveSessionTest.java
new file mode 100644
index 0000000000..97b656fbd2
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/StopSessionManagerPreserveSessionTest.java
@@ -0,0 +1,99 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import static org.junit.Assert.fail;
+import org.eclipse.jetty.server.session.AbstractStopSessionManagerPreserveSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * StopSessionManagerPreserveSessionTest
+ *
+ *
+ */
+public class StopSessionManagerPreserveSessionTest extends AbstractStopSessionManagerPreserveSessionTest
+{
+ static GCloudSessionTestSupport _testSupport;
+
+ @BeforeClass
+ public static void setup () throws Exception
+ {
+ String projectId = System.getProperty("test.projectId", null);
+ String port = System.getProperty("test.port","0");
+ _testSupport = new GCloudSessionTestSupport(projectId,
+ Integer.parseInt(port),
+ null);
+ _testSupport.setUp();
+ }
+
+ @AfterClass
+ public static void teardown () throws Exception
+ {
+ _testSupport.tearDown();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractStopSessionManagerPreserveSessionTest#checkSessionPersisted(boolean)
+ */
+ @Override
+ public void checkSessionPersisted(boolean expected)
+ {
+ try
+ {
+ _testSupport.assertSessions(1);
+ }
+ catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractStopSessionManagerPreserveSessionTest#createServer(int)
+ */
+ @Override
+ public AbstractTestServer createServer(int port)
+ {
+ return new GCloudTestServer(port, _testSupport.getConfiguration());
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractStopSessionManagerPreserveSessionTest#configureSessionManagement(org.eclipse.jetty.servlet.ServletContextHandler)
+ */
+ @Override
+ public void configureSessionManagement(ServletContextHandler context)
+ {
+
+ }
+
+ @Test
+ @Override
+ public void testStopSessionManagerPreserveSession() throws Exception
+ {
+ super.testStopSessionManagerPreserveSession();
+ }
+
+
+}
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractInvalidationSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractInvalidationSessionTest.java
index 0d1ac5ba92..8d2d660404 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractInvalidationSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractInvalidationSessionTest.java
@@ -83,19 +83,22 @@ public abstract class AbstractInvalidationSessionTest
assertTrue(sessionCookie != null);
// Mangle the cookie, replacing Path with $Path, etc.
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
-
+
+
// Be sure the session is also present in node2
Request request2 = client.newRequest(urls[1] + "?action=increment");
request2.header("Cookie", sessionCookie);
ContentResponse response2 = request2.send();
assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
+
// Invalidate on node1
Request request1 = client.newRequest(urls[0] + "?action=invalidate");
request1.header("Cookie", sessionCookie);
response1 = request1.send();
assertEquals(HttpServletResponse.SC_OK, response1.getStatus());
+
pause();

Back to the top