diff --git a/jetty-bom/pom.xml b/jetty-bom/pom.xml
index 8444f223516..5d1487936ce 100644
--- a/jetty-bom/pom.xml
+++ b/jetty-bom/pom.xml
@@ -232,6 +232,11 @@
jetty-infinispan
9.4.7-SNAPSHOT
+
+ org.eclipse.jetty
+ jetty-hazelcast
+ 9.4.6-SNAPSHOT
+
org.eclipse.jetty
jetty-io
diff --git a/jetty-hazelcast/pom.xml b/jetty-hazelcast/pom.xml
new file mode 100644
index 00000000000..d5062f6d23b
--- /dev/null
+++ b/jetty-hazelcast/pom.xml
@@ -0,0 +1,165 @@
+
+
+
+ jetty-project
+ org.eclipse.jetty
+ 9.4.6-SNAPSHOT
+
+
+ 4.0.0
+ jetty-hazelcast
+ Jetty :: Hazelcast Session Manager
+
+
+ 3.8.2
+ ${project.groupId}.session
+
+
+
+
+ com.hazelcast
+ hazelcast
+ ${hazelcast.version}
+ test-jar
+
+
+ com.hazelcast
+ hazelcast-client
+ ${hazelcast.version}
+
+
+ com.hazelcast
+ hazelcast
+ ${hazelcast.version}
+
+
+ org.eclipse.jetty
+ jetty-server
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-webapp
+ ${project.version}
+ test
+
+
+ org.eclipse.jetty.websocket
+ websocket-servlet
+ ${project.version}
+ test
+
+
+ org.eclipse.jetty.websocket
+ websocket-server
+ ${project.version}
+ test
+
+
+ org.eclipse.jetty.toolchain
+ jetty-test-helper
+ test
+
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ true
+
+
+
+ manifest
+
+
+
+
+ org.eclipse.jetty.hazelcast.session.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}";
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ build-deps-file
+ generate-resources
+
+ list
+
+
+ false
+ ${project.build.directory}/deps.txt
+ true
+ org.eclipse.jetty
+ true
+ runtime
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+
+
+ process-deps
+ process-resources
+
+ run
+
+
+
+
+
+
+
+
+ process-mod
+ process-resources
+
+ run
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ package
+
+ single
+
+
+
+ src/main/assembly/config.xml
+
+
+
+
+
+
+
+
+
diff --git a/jetty-hazelcast/src/main/assembly/config.xml b/jetty-hazelcast/src/main/assembly/config.xml
new file mode 100644
index 00000000000..45e740841f1
--- /dev/null
+++ b/jetty-hazelcast/src/main/assembly/config.xml
@@ -0,0 +1,27 @@
+
+
+ config
+ false
+
+ jar
+
+
+
+ src/main/config-template
+
+
+ **
+
+
+ **/session-store-hazelcast.mod
+
+
+
+ target
+ modules
+
+ session-store-hazelcast.mod
+
+
+
+
diff --git a/jetty-hazelcast/src/main/config-template/etc/sessions/hazelcast/session-store.xml b/jetty-hazelcast/src/main/config-template/etc/sessions/hazelcast/session-store.xml
new file mode 100644
index 00000000000..ecd6efb1ea0
--- /dev/null
+++ b/jetty-hazelcast/src/main/config-template/etc/sessions/hazelcast/session-store.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jetty-hazelcast/src/main/config-template/modules/session-store-hazelcast.mod b/jetty-hazelcast/src/main/config-template/modules/session-store-hazelcast.mod
new file mode 100644
index 00000000000..5b70060bf23
--- /dev/null
+++ b/jetty-hazelcast/src/main/config-template/modules/session-store-hazelcast.mod
@@ -0,0 +1,36 @@
+[description]
+Enables Hazelcast session management.
+
+[tags]
+session
+hazelcast
+
+[provides]
+session-store
+
+[depends]
+annotations
+webapp
+sessions
+
+[lib]
+lib/hazelcast/*.jar
+lib/jetty-hazelcast-session-manager-${jetty.version}.jar
+
+[xml]
+etc/sessions/hazelcast/session-store.xml
+
+[license]
+Hazelcast is an open source project hosted on Github and released under the Apache 2.0 license.
+http://hazelcast.org/
+http://www.apache.org/licenses/LICENSE-2.0.html
+
+[ini]
+## Hide the hazelcast libraries from deployed webapps
+jetty.webapp.addServerClasses+=,${jetty.base.uri}/lib/hazelcast/
+
+[ini-template]
+
+## Hazelcast Session config
+#jetty.session.gracePeriod.seconds=3600
+# FIXME add some configuration
diff --git a/jetty-hazelcast/src/main/java/org/eclipse/jetty/hazelcast/session/HazelcastSessionDataStore.java b/jetty-hazelcast/src/main/java/org/eclipse/jetty/hazelcast/session/HazelcastSessionDataStore.java
new file mode 100644
index 00000000000..9aaccaf21b2
--- /dev/null
+++ b/jetty-hazelcast/src/main/java/org/eclipse/jetty/hazelcast/session/HazelcastSessionDataStore.java
@@ -0,0 +1,211 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session;
+
+import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.core.IMap;
+import org.eclipse.jetty.server.session.AbstractSessionDataStore;
+import org.eclipse.jetty.server.session.SessionContext;
+import org.eclipse.jetty.server.session.SessionData;
+import org.eclipse.jetty.server.session.SessionDataMap;
+import org.eclipse.jetty.server.session.SessionDataStore;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * Session data stored in Hazelcast
+ */
+@ManagedObject
+public class HazelcastSessionDataStore
+ extends AbstractSessionDataStore
+ implements SessionDataStore
+{
+
+ private final static Logger LOG = Log.getLogger( "org.eclipse.jetty.server.session");
+
+ private HazelcastInstance hazelcastInstance;
+
+ private String jettySessionMapName;
+
+ private IMap sessionDataMap;
+
+ public HazelcastSessionDataStore()
+ {
+ // no op
+ }
+
+ public HazelcastSessionDataStore( HazelcastInstance hazelcastInstance, String jettySessionMapName )
+ {
+ this.hazelcastInstance = hazelcastInstance;
+ this.jettySessionMapName = jettySessionMapName;
+ this.sessionDataMap = hazelcastInstance.getMap( getJettySessionMapName() );
+ }
+
+ public HazelcastInstance getHazelcastInstance()
+ {
+ return hazelcastInstance;
+ }
+
+ public void setHazelcastInstance( HazelcastInstance hazelcastInstance )
+ {
+ this.hazelcastInstance = hazelcastInstance;
+ }
+
+ public String getJettySessionMapName()
+ {
+ return jettySessionMapName;
+ }
+
+ public void setJettySessionMapName( String jettySessionMapName )
+ {
+ this.jettySessionMapName = jettySessionMapName;
+ }
+
+
+ @Override
+ public SessionData load( String id )
+ throws Exception
+ {
+ return sessionDataMap == null ? null : sessionDataMap.get( id );
+ }
+
+ @Override
+ public boolean delete( String id )
+ throws Exception
+ {
+ return sessionDataMap == null ? false : sessionDataMap.remove( id ) != null;
+ }
+
+ @Override
+ public void initialize( SessionContext context )
+ throws Exception
+ {
+ _context = context;
+ if (this.sessionDataMap == null)
+ {
+ this.sessionDataMap = getHazelcastInstance().getMap( getJettySessionMapName() );
+ }
+ }
+
+ @Override
+ public void store( String id, SessionData data )
+ throws Exception
+ {
+ this.sessionDataMap.put( id, data );
+ }
+
+ @Override
+ public void doStore( String id, SessionData data, long lastSaveTime )
+ throws Exception
+ {
+ this.sessionDataMap.put( id, data);
+ }
+
+ @Override
+ public boolean isPassivating()
+ {
+ return false;
+ }
+
+ @Override
+ public Set doGetExpired( Set candidates )
+ {
+ if (candidates == null || candidates.isEmpty())
+ {
+ return Collections.emptySet();
+ }
+ long now = System.currentTimeMillis();
+ return candidates.stream().filter( candidate -> {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug( "Checking expiry for candidate {}", candidate );
+ }
+ try
+ {
+ SessionData sd = load(candidate);
+
+ //if the session no longer exists
+ if (sd == null)
+ {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug( "Session {} does not exist in infinispan", candidate );
+ }
+ return true;
+ }
+ else
+ {
+ if (_context.getWorkerName().equals(sd.getLastNode()))
+ {
+ //we are its manager, add it to the expired set if it is expired now
+ if ((sd.getExpiry() > 0 ) && sd.getExpiry() <= now)
+ {
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug( "Session {} managed by {} is expired", candidate, _context.getWorkerName() );
+ }
+ return true;
+ }
+ }
+ else
+ {
+ //if we are not the session's manager, only expire it iff:
+ // this is our first expiryCheck and the session expired a long time ago
+ //or
+ //the session expired at least one graceperiod ago
+ if (_lastExpiryCheckTime <=0)
+ {
+ if ((sd.getExpiry() > 0 ) && sd.getExpiry() < (now - (1000L * (3 * _gracePeriodSec))))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if ((sd.getExpiry() > 0 ) && sd.getExpiry() < (now - (1000L * _gracePeriodSec)))
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Error checking if candidate {} is expired so expire it", candidate, e);
+ return true;
+ }
+ return false;
+ } ).collect( Collectors.toSet() );
+ }
+
+ @Override
+ public boolean exists( String id )
+ throws Exception
+ {
+ return this.sessionDataMap.containsKey( id );
+ }
+}
diff --git a/jetty-hazelcast/src/main/java/org/eclipse/jetty/hazelcast/session/HazelcastSessionDataStoreFactory.java b/jetty-hazelcast/src/main/java/org/eclipse/jetty/hazelcast/session/HazelcastSessionDataStoreFactory.java
new file mode 100644
index 00000000000..874ada29f1f
--- /dev/null
+++ b/jetty-hazelcast/src/main/java/org/eclipse/jetty/hazelcast/session/HazelcastSessionDataStoreFactory.java
@@ -0,0 +1,176 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session;
+
+import com.hazelcast.client.HazelcastClient;
+import com.hazelcast.client.config.ClientConfig;
+import com.hazelcast.client.config.XmlClientConfigBuilder;
+import com.hazelcast.config.Config;
+import com.hazelcast.config.MapConfig;
+import com.hazelcast.config.XmlConfigBuilder;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import org.eclipse.jetty.server.session.AbstractSessionDataStoreFactory;
+import org.eclipse.jetty.server.session.SessionDataMap;
+import org.eclipse.jetty.server.session.SessionDataStore;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+import org.eclipse.jetty.server.session.SessionHandler;
+
+import java.io.IOException;
+
+/**
+ * Factory to construct {@link HazelcastSessionDataStore}
+ */
+public class HazelcastSessionDataStoreFactory
+ extends AbstractSessionDataStoreFactory
+ implements SessionDataStoreFactory
+{
+
+ public static final String DEFAULT_HAZELCAST_INSTANCE_NAME = "JETTY_DISTRIBUTED_SESSION_INSTANCE";
+
+ public static final String DEFAULT_HAZELCAST_MAP_NAME = "jetty-distributed-session-map";
+
+ private boolean onlyClient;
+
+ private String configurationLocation;
+
+ private String jettySessionMapName = DEFAULT_HAZELCAST_MAP_NAME;
+
+ private HazelcastInstance hazelcastInstance;
+
+ private MapConfig mapConfig;
+
+
+ @Override
+ public SessionDataStore getSessionDataStore( SessionHandler handler )
+ throws Exception
+ {
+ HazelcastSessionDataStore hazelcastSessionDataStore = new HazelcastSessionDataStore();
+
+ if ( hazelcastInstance == null )
+ {
+ try
+ {
+ if ( onlyClient )
+ {
+ if ( configurationLocation == null )
+ {
+ hazelcastSessionDataStore.setHazelcastInstance(
+ HazelcastClient.newHazelcastClient( new ClientConfig() ) );
+ }
+ else
+ {
+ hazelcastSessionDataStore.setHazelcastInstance( HazelcastClient.newHazelcastClient(
+ new XmlClientConfigBuilder( configurationLocation ).build() ) );
+ }
+
+ }
+ else
+ {
+ Config config;
+ if ( configurationLocation == null )
+ {
+ config = new Config();
+ // configure a default Map if null
+ if ( mapConfig == null )
+ {
+ mapConfig = new MapConfig();
+ mapConfig.setName( jettySessionMapName );
+ }
+ else
+ {
+ // otherwise we reuse the name
+ jettySessionMapName = mapConfig.getName();
+ }
+ config.addMapConfig( mapConfig );
+ }
+ else
+ {
+ config = new XmlConfigBuilder( configurationLocation ).build();
+ }
+ config.setInstanceName( DEFAULT_HAZELCAST_INSTANCE_NAME );
+ hazelcastSessionDataStore.setHazelcastInstance( Hazelcast.getOrCreateHazelcastInstance( config ) );
+ }
+ }
+ catch ( IOException e )
+ {
+ throw new RuntimeException( e.getMessage(), e );
+ }
+ }
+ else
+ {
+ hazelcastSessionDataStore.setHazelcastInstance( hazelcastInstance );
+ }
+ hazelcastSessionDataStore.setJettySessionMapName( jettySessionMapName );
+ // initialize the map
+ hazelcastSessionDataStore.getHazelcastInstance().getMap( jettySessionMapName );
+
+ return hazelcastSessionDataStore;
+ }
+
+ public boolean isOnlyClient()
+ {
+ return onlyClient;
+ }
+
+ public void setOnlyClient( boolean onlyClient )
+ {
+ this.onlyClient = onlyClient;
+ }
+
+ public String getConfigurationLocation()
+ {
+ return configurationLocation;
+ }
+
+ public void setConfigurationLocation( String configurationLocation )
+ {
+ this.configurationLocation = configurationLocation;
+ }
+
+ public String getJettySessionMapName()
+ {
+ return jettySessionMapName;
+ }
+
+ public void setJettySessionMapName( String jettySessionMapName )
+ {
+ this.jettySessionMapName = jettySessionMapName;
+ }
+
+ public HazelcastInstance getHazelcastInstance()
+ {
+ return hazelcastInstance;
+ }
+
+ public void setHazelcastInstance( HazelcastInstance hazelcastInstance )
+ {
+ this.hazelcastInstance = hazelcastInstance;
+ }
+
+ public MapConfig getMapConfig()
+ {
+ return mapConfig;
+ }
+
+ public void setMapConfig( MapConfig mapConfig )
+ {
+ this.mapConfig = mapConfig;
+ }
+}
diff --git a/jetty-hazelcast/src/test/java/org/eclipse/jetty/hazelcast/session/TestHazelcastSessions.java b/jetty-hazelcast/src/test/java/org/eclipse/jetty/hazelcast/session/TestHazelcastSessions.java
new file mode 100644
index 00000000000..0435d391ae2
--- /dev/null
+++ b/jetty-hazelcast/src/test/java/org/eclipse/jetty/hazelcast/session/TestHazelcastSessions.java
@@ -0,0 +1,203 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session;
+
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.session.AbstractSessionCache;
+import org.eclipse.jetty.server.session.CachingSessionDataStore;
+import org.eclipse.jetty.server.session.DefaultSessionCache;
+import org.eclipse.jetty.server.session.NullSessionDataStore;
+import org.eclipse.jetty.server.session.Session;
+import org.eclipse.jetty.server.session.SessionContext;
+import org.eclipse.jetty.server.session.SessionData;
+import org.eclipse.jetty.server.session.SessionDataMap;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import static org.junit.Assert.*;
+
+public class TestHazelcastSessions
+{
+ public static class TestServlet
+ extends HttpServlet
+ {
+
+ @Override
+ protected void doGet( HttpServletRequest req, HttpServletResponse resp )
+ throws ServletException, IOException
+ {
+ String arg = req.getParameter( "action" );
+ if ( arg == null )
+ {
+ return;
+ }
+ HttpSession s = null;
+ if ( "set".equals( arg ) )
+ {
+ s = req.getSession( true );
+ assertNotNull( s );
+ s.setAttribute( "val", req.getParameter( "value" ) );
+ }
+ else if ( "get".equals( arg ) )
+ {
+ s = req.getSession( false );
+ System.err.println( "GET: s=" + s + ",id=" + (s != null ? s.getId() : "" ) );
+ }
+ else if ( "del".equals( arg ) )
+ {
+ s = req.getSession();
+ assertNotNull( s );
+ s.invalidate();
+ s = null;
+ }
+
+ resp.setContentType( "text/html" );
+ PrintWriter w = resp.getWriter();
+ if ( s == null )
+ {
+ w.write( "No session" );
+ }
+ else
+ {
+ w.write( (String) s.getAttribute( "val" ) );
+ }
+ }
+
+ }
+
+ private HazelcastSessionDataStore hazelcastSessionDataStore;
+
+ private Server server;
+
+ private ServerConnector serverConnector;
+
+ String contextPath = "/";
+
+ @Before
+ public void initialize()
+ throws Exception
+ {
+
+ server = new Server();
+ serverConnector = new ServerConnector( server, new HttpConnectionFactory() );
+ server.addConnector( serverConnector );
+
+ ServletContextHandler context = new ServletContextHandler( ServletContextHandler.SESSIONS );
+ context.setContextPath( contextPath );
+ context.setResourceBase( System.getProperty( "java.io.tmpdir" ) );
+ server.setHandler( context );
+
+ SessionContext sessionContext = new SessionContext( "foo", null );
+
+ HazelcastSessionDataStoreFactory hazelcastSessionDataStoreFactory = new HazelcastSessionDataStoreFactory();
+ hazelcastSessionDataStore = (HazelcastSessionDataStore) hazelcastSessionDataStoreFactory.getSessionDataStore(
+ context.getSessionHandler() );
+ hazelcastSessionDataStore.initialize( sessionContext );
+
+ DefaultSessionCache defaultSessionCache = new DefaultSessionCache( context.getSessionHandler() );
+ defaultSessionCache.setSessionDataStore( hazelcastSessionDataStore );
+ context.getSessionHandler().setSessionCache( defaultSessionCache );
+ // Add a test servlet
+ context.addServlet( new ServletHolder( new TestServlet() ), contextPath );
+
+ server.start();
+ }
+
+ @After
+ public void shutdown()
+ throws Exception
+ {
+ hazelcastSessionDataStore.getHazelcastInstance().shutdown();
+ server.stop();
+ }
+
+ @Test
+ public void testHazelcast()
+ throws Exception
+ {
+
+ int port = serverConnector.getLocalPort();
+ HttpClient client = new HttpClient();
+ client.start();
+ try
+ {
+ int value = 42;
+ ContentResponse response =
+ client.GET( "http://localhost:" + port + contextPath + "?action=set&value=" + value );
+ assertEquals( HttpServletResponse.SC_OK, response.getStatus() );
+ String sessionCookie = response.getHeaders().get( "Set-Cookie" );
+ assertTrue( sessionCookie != null );
+ // Mangle the cookie, replacing Path with $Path, etc.
+ sessionCookie = sessionCookie.replaceFirst( "(\\W)(P|p)ath=", "$1\\$Path=" );
+
+ String resp = response.getContentAsString();
+ assertEquals( resp.trim(), String.valueOf( value ) );
+
+ // Be sure the session value is still there
+ Request request = client.newRequest( "http://localhost:" + port + contextPath + "?action=get" );
+ request.header( "Cookie", sessionCookie );
+ response = request.send();
+ assertEquals( HttpServletResponse.SC_OK, response.getStatus() );
+
+ resp = response.getContentAsString();
+ assertEquals( String.valueOf( value ), resp.trim() );
+
+ //Delete the session
+ request = client.newRequest( "http://localhost:" + port + contextPath + "?action=del" );
+ request.header( "Cookie", sessionCookie );
+ response = request.send();
+ assertEquals( HttpServletResponse.SC_OK, response.getStatus() );
+
+ //Check that the session is gone
+ request = client.newRequest( "http://localhost:" + port + contextPath + "?action=get" );
+ request.header( "Cookie", sessionCookie );
+ response = request.send();
+ assertEquals( HttpServletResponse.SC_OK, response.getStatus() );
+ resp = response.getContentAsString();
+ assertEquals( "No session", resp.trim() );
+
+
+
+ }
+ finally
+ {
+ client.stop();
+ }
+
+ }
+
+}
diff --git a/jetty-home/pom.xml b/jetty-home/pom.xml
index ec37181e6e7..177475eeeeb 100644
--- a/jetty-home/pom.xml
+++ b/jetty-home/pom.xml
@@ -562,6 +562,11 @@
jetty-infinispan
${project.version}
+
+ org.eclipse.jetty
+ jetty-hazelcast
+ ${project.version}
+
org.eclipse.jetty.gcloud
jetty-gcloud-session-manager
diff --git a/pom.xml b/pom.xml
index 60e1000d768..4b7b68f15f6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,6 +79,7 @@
jetty-infinispan
jetty-gcloud
jetty-memcached
+ jetty-hazelcast
jetty-unixsocket
tests
examples
diff --git a/tests/test-sessions/pom.xml b/tests/test-sessions/pom.xml
index 281f705d55d..962172f91c5 100644
--- a/tests/test-sessions/pom.xml
+++ b/tests/test-sessions/pom.xml
@@ -21,5 +21,6 @@
test-infinispan-sessions
test-gcloud-sessions
test-memcached-sessions
+ test-hazelcast-sessions
diff --git a/tests/test-sessions/test-hazelcast-sessions/pom.xml b/tests/test-sessions/test-hazelcast-sessions/pom.xml
new file mode 100644
index 00000000000..8f357e30ba4
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/pom.xml
@@ -0,0 +1,94 @@
+
+
+ 4.0.0
+
+ org.eclipse.jetty.tests
+ test-sessions-parent
+ 9.4.6-SNAPSHOT
+
+ test-hazelcast-sessions
+ Jetty Tests :: Sessions :: Hazelcast
+ http://www.eclipse.org/jetty
+
+ ${project.groupId}.sessions.hazelcast
+
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ unpack
+ generate-test-resources
+
+ unpack
+
+
+
+
+ org.eclipse.jetty.toolchain
+ jetty-test-policy
+ ${jetty-test-policy-version}
+ jar
+ true
+ **/*.keystore,**/*.pem
+ ${jetty.test.policy.loc}
+
+
+
+
+
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-server
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-webapp
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-client
+ ${project.version}
+
+
+ org.eclipse.jetty.tests
+ test-sessions-common
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-hazelcast
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-jmx
+ ${project.version}
+ true
+
+
+ org.eclipse.jetty.toolchain
+ jetty-test-helper
+ test
+
+
+
+
+
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredLastAccessTimeTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredLastAccessTimeTest.java
new file mode 100644
index 00000000000..aabffb8dfda
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredLastAccessTimeTest.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session;
+
+import org.eclipse.jetty.server.session.AbstractClusteredLastAccessTimeTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+
+public class ClusteredLastAccessTimeTest
+ extends AbstractClusteredLastAccessTimeTest
+{
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ return factory;
+ }
+
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredOrphanedSessionTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredOrphanedSessionTest.java
new file mode 100644
index 00000000000..e5a0be0a09a
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredOrphanedSessionTest.java
@@ -0,0 +1,44 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session;
+
+import org.eclipse.jetty.server.session.AbstractClusteredOrphanedSessionTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+
+/**
+ * ClusteredOrphanedSessionTest
+ */
+public class ClusteredOrphanedSessionTest
+ extends AbstractClusteredOrphanedSessionTest
+{
+
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ return factory;
+ }
+
+
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredSessionMigrationTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredSessionMigrationTest.java
new file mode 100644
index 00000000000..b6a310f22ff
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredSessionMigrationTest.java
@@ -0,0 +1,41 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session;
+
+import org.eclipse.jetty.server.session.AbstractClusteredSessionMigrationTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+
+/**
+ * ClusteredSessionMigrationTest
+ */
+public class ClusteredSessionMigrationTest
+ extends AbstractClusteredSessionMigrationTest
+{
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ return factory;
+ }
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredSessionScavengingTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredSessionScavengingTest.java
new file mode 100644
index 00000000000..0d626c33fa7
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ClusteredSessionScavengingTest.java
@@ -0,0 +1,41 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session;
+
+import org.eclipse.jetty.server.session.AbstractClusteredSessionScavengingTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+
+/**
+ * ClusteredSessionScavengingTest
+ */
+public class ClusteredSessionScavengingTest
+ extends AbstractClusteredSessionScavengingTest
+{
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ return factory;
+ }
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ModifyMaxInactiveIntervalTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ModifyMaxInactiveIntervalTest.java
new file mode 100644
index 00000000000..5570d0946ba
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/ModifyMaxInactiveIntervalTest.java
@@ -0,0 +1,42 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session;
+
+import org.eclipse.jetty.server.session.AbstractModifyMaxInactiveIntervalTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+
+/**
+ * ModifyMaxInactiveIntervalTest
+ */
+public class ModifyMaxInactiveIntervalTest
+ extends AbstractModifyMaxInactiveIntervalTest
+{
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ return factory;
+ }
+
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/NonClusteredSessionScavengingTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/NonClusteredSessionScavengingTest.java
new file mode 100644
index 00000000000..8c024268b0f
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/NonClusteredSessionScavengingTest.java
@@ -0,0 +1,69 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session;
+
+import org.eclipse.jetty.server.session.AbstractNonClusteredSessionScavengingTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+
+import static org.junit.Assert.*;
+
+/**
+ * NonClusteredSessionScavengingTest
+ */
+public class NonClusteredSessionScavengingTest
+ extends AbstractNonClusteredSessionScavengingTest
+{
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractNonClusteredSessionScavengingTest#assertSession(java.lang.String, boolean)
+ */
+ @Override
+ public void assertSession( String id, boolean exists )
+ {
+ assertNotNull( _dataStore );
+
+ try
+ {
+ boolean inmap = _dataStore.exists( id );
+ if ( exists )
+ {
+ assertTrue( inmap );
+ }
+ else
+ {
+ assertFalse( inmap );
+ }
+ }
+ catch ( Exception e )
+ {
+ fail( e.getMessage() );
+ }
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ return factory;
+ }
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/SessionExpiryTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/SessionExpiryTest.java
new file mode 100644
index 00000000000..ae911a0614b
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/SessionExpiryTest.java
@@ -0,0 +1,40 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session;
+
+import org.eclipse.jetty.server.session.AbstractSessionExpiryTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+import org.junit.Test;
+
+public class SessionExpiryTest
+ extends AbstractSessionExpiryTest
+{
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ return factory;
+ }
+
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/SessionInvalidateCreateScavengeTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/SessionInvalidateCreateScavengeTest.java
new file mode 100644
index 00000000000..c958fb6e6c5
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/SessionInvalidateCreateScavengeTest.java
@@ -0,0 +1,41 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session;
+
+import org.eclipse.jetty.server.session.AbstractSessionInvalidateCreateScavengeTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+
+/**
+ * SessionInvalidateCreateScavengeTest
+ */
+public class SessionInvalidateCreateScavengeTest
+ extends AbstractSessionInvalidateCreateScavengeTest
+{
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ return factory;
+ }
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientLastAccessTimeTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientLastAccessTimeTest.java
new file mode 100644
index 00000000000..57740fd92be
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientLastAccessTimeTest.java
@@ -0,0 +1,68 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session.client;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.config.MapConfig;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStoreFactory;
+import org.eclipse.jetty.server.session.AbstractClusteredLastAccessTimeTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+import org.junit.After;
+import org.junit.Before;
+
+public class ClientLastAccessTimeTest
+ extends AbstractClusteredLastAccessTimeTest
+{
+
+ private static final String MAP_NAME = "jetty_foo_session";
+
+ private HazelcastInstance hazelcastInstance;
+
+ @Before
+ public void startHazelcast()
+ throws Exception
+ {
+ Config config = new Config().addMapConfig( new MapConfig().setName( MAP_NAME ) ) //
+ .setInstanceName( "beer" );
+ // start Hazelcast instance
+ hazelcastInstance = Hazelcast.getOrCreateHazelcastInstance( config );
+ }
+
+ @After
+ public void stopHazelcast()
+ throws Exception
+ {
+ hazelcastInstance.shutdown();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ factory.setOnlyClient( true );
+ factory.setJettySessionMapName( MAP_NAME );
+ return factory;
+ }
+
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientModifyMaxInactiveIntervalTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientModifyMaxInactiveIntervalTest.java
new file mode 100644
index 00000000000..f74d05746c9
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientModifyMaxInactiveIntervalTest.java
@@ -0,0 +1,43 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session.client;
+
+import org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStoreFactory;
+import org.eclipse.jetty.server.session.AbstractModifyMaxInactiveIntervalTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+
+/**
+ * ModifyMaxInactiveIntervalTest
+ */
+public class ClientModifyMaxInactiveIntervalTest
+ extends AbstractModifyMaxInactiveIntervalTest
+{
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ return factory;
+ }
+
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientNonClusteredSessionScavengingTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientNonClusteredSessionScavengingTest.java
new file mode 100644
index 00000000000..2e925837404
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientNonClusteredSessionScavengingTest.java
@@ -0,0 +1,96 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session.client;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.config.MapConfig;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStoreFactory;
+import org.eclipse.jetty.server.session.AbstractNonClusteredSessionScavengingTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+import org.junit.After;
+import org.junit.Before;
+
+import static org.junit.Assert.*;
+
+public class ClientNonClusteredSessionScavengingTest
+ extends AbstractNonClusteredSessionScavengingTest
+{
+
+ /**
+ * @see AbstractNonClusteredSessionScavengingTest#assertSession(String, boolean)
+ */
+ @Override
+ public void assertSession( String id, boolean exists )
+ {
+ assertNotNull( _dataStore );
+
+ try
+ {
+ boolean inmap = _dataStore.exists( id );
+ if ( exists )
+ {
+ assertTrue( inmap );
+ }
+ else
+ {
+ assertFalse( inmap );
+ }
+ }
+ catch ( Exception e )
+ {
+ fail( e.getMessage() );
+ }
+ }
+ private static final String MAP_NAME = "jetty_foo_session";
+
+ private HazelcastInstance hazelcastInstance;
+
+ @Before
+ public void startHazelcast()
+ throws Exception
+ {
+ Config config = new Config().addMapConfig( new MapConfig().setName( MAP_NAME ) ) //
+ .setInstanceName( "beer" );
+ // start Hazelcast instance
+ hazelcastInstance = Hazelcast.getOrCreateHazelcastInstance( config );
+ }
+
+ @After
+ public void stopHazelcast()
+ throws Exception
+ {
+ hazelcastInstance.shutdown();
+ }
+
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ factory.setOnlyClient( true );
+ factory.setJettySessionMapName( MAP_NAME );
+ return factory;
+ }
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientOrphanedSessionTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientOrphanedSessionTest.java
new file mode 100644
index 00000000000..8ce86855918
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientOrphanedSessionTest.java
@@ -0,0 +1,69 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session.client;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.config.MapConfig;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStoreFactory;
+import org.eclipse.jetty.server.session.AbstractClusteredOrphanedSessionTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+import org.junit.After;
+import org.junit.Before;
+
+public class ClientOrphanedSessionTest
+ extends AbstractClusteredOrphanedSessionTest
+{
+
+ private static final String MAP_NAME = "jetty_foo_session";
+
+ private HazelcastInstance hazelcastInstance;
+
+ @Before
+ public void startHazelcast()
+ throws Exception
+ {
+ Config config = new Config().addMapConfig( new MapConfig().setName( MAP_NAME ) ) //
+ .setInstanceName( "beer" );
+ // start Hazelcast instance
+ hazelcastInstance = Hazelcast.getOrCreateHazelcastInstance( config );
+ }
+
+ @After
+ public void stopHazelcast()
+ throws Exception
+ {
+ hazelcastInstance.shutdown();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ factory.setOnlyClient( true );
+ factory.setJettySessionMapName( MAP_NAME );
+ return factory;
+ }
+
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionExpiryTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionExpiryTest.java
new file mode 100644
index 00000000000..57f0e4a7496
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionExpiryTest.java
@@ -0,0 +1,71 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session.client;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.config.MapConfig;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStoreFactory;
+import org.eclipse.jetty.server.session.AbstractSessionExpiryTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ClientSessionExpiryTest
+ extends AbstractSessionExpiryTest
+{
+
+ private static final String MAP_NAME = "jetty_foo_session";
+
+ private HazelcastInstance hazelcastInstance;
+
+ @Before
+ public void startHazelcast()
+ throws Exception
+ {
+ Config config = new Config().addMapConfig( new MapConfig().setName( MAP_NAME ) ) //
+ .setInstanceName( "beer" );
+ // start Hazelcast instance
+ hazelcastInstance = Hazelcast.getOrCreateHazelcastInstance( config );
+ }
+
+ @After
+ public void stopHazelcast()
+ throws Exception
+ {
+ hazelcastInstance.shutdown();
+ }
+
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ factory.setOnlyClient( true );
+ factory.setJettySessionMapName( MAP_NAME );
+ return factory;
+ }
+
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionInvalidateCreateScavengeTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionInvalidateCreateScavengeTest.java
new file mode 100644
index 00000000000..b07e3f90d3e
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionInvalidateCreateScavengeTest.java
@@ -0,0 +1,68 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session.client;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.config.MapConfig;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStoreFactory;
+import org.eclipse.jetty.server.session.AbstractSessionInvalidateCreateScavengeTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+import org.junit.After;
+import org.junit.Before;
+
+public class ClientSessionInvalidateCreateScavengeTest
+ extends AbstractSessionInvalidateCreateScavengeTest
+{
+ private static final String MAP_NAME = "jetty_foo_session";
+
+ private HazelcastInstance hazelcastInstance;
+
+ @Before
+ public void startHazelcast()
+ throws Exception
+ {
+ Config config = new Config().addMapConfig( new MapConfig().setName( MAP_NAME ) ) //
+ .setInstanceName( "beer" );
+ // start Hazelcast instance
+ hazelcastInstance = Hazelcast.getOrCreateHazelcastInstance( config );
+ }
+
+ @After
+ public void stopHazelcast()
+ throws Exception
+ {
+ hazelcastInstance.shutdown();
+ }
+
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ factory.setOnlyClient( true );
+ factory.setJettySessionMapName( MAP_NAME );
+ return factory;
+ }
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionMigrationTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionMigrationTest.java
new file mode 100644
index 00000000000..8780c879cbc
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionMigrationTest.java
@@ -0,0 +1,71 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session.client;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.config.MapConfig;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStoreFactory;
+import org.eclipse.jetty.server.session.AbstractClusteredSessionMigrationTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * ClusteredSessionMigrationTest
+ */
+public class ClientSessionMigrationTest
+ extends AbstractClusteredSessionMigrationTest
+{
+ private static final String MAP_NAME = "jetty_foo_session";
+
+ private HazelcastInstance hazelcastInstance;
+
+ @Before
+ public void startHazelcast()
+ throws Exception
+ {
+ Config config = new Config().addMapConfig( new MapConfig().setName( MAP_NAME ) ) //
+ .setInstanceName( "beer" );
+ // start Hazelcast instance
+ hazelcastInstance = Hazelcast.getOrCreateHazelcastInstance( config );
+ }
+
+ @After
+ public void stopHazelcast()
+ throws Exception
+ {
+ hazelcastInstance.shutdown();
+ }
+
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ factory.setOnlyClient( true );
+ factory.setJettySessionMapName( MAP_NAME );
+ return factory;
+ }
+}
diff --git a/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionScavengingTest.java b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionScavengingTest.java
new file mode 100644
index 00000000000..a452278df03
--- /dev/null
+++ b/tests/test-sessions/test-hazelcast-sessions/src/test/java/org/eclipse/jetty/hazelcast/session/client/ClientSessionScavengingTest.java
@@ -0,0 +1,69 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 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.hazelcast.session.client;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.config.MapConfig;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStoreFactory;
+import org.eclipse.jetty.server.session.AbstractClusteredSessionScavengingTest;
+import org.eclipse.jetty.server.session.SessionDataStoreFactory;
+import org.junit.After;
+import org.junit.Before;
+
+public class ClientSessionScavengingTest
+ extends AbstractClusteredSessionScavengingTest
+{
+
+ private static final String MAP_NAME = "jetty_foo_session";
+
+ private HazelcastInstance hazelcastInstance;
+
+ @Before
+ public void startHazelcast()
+ throws Exception
+ {
+ Config config = new Config().addMapConfig( new MapConfig().setName( MAP_NAME ) ) //
+ .setInstanceName( "beer" );
+ // start Hazelcast instance
+ hazelcastInstance = Hazelcast.getOrCreateHazelcastInstance( config );
+ }
+
+ @After
+ public void stopHazelcast()
+ throws Exception
+ {
+ hazelcastInstance.shutdown();
+ }
+
+
+ /**
+ * @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
+ */
+ @Override
+ public SessionDataStoreFactory createSessionDataStoreFactory()
+ {
+ HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
+ factory.setOnlyClient( true );
+ factory.setJettySessionMapName( MAP_NAME );
+ return factory;
+ }
+}