Merge branch `jetty-9.4.x` into `jetty-10.0.x`

Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>

# Conflicts:
#	jetty-infinispan/infinispan-common/src/main/java/org/eclipse/jetty/session/infinispan/WebAppMarshaller.java
#	jetty-infinispan/pom.xml
#	jetty-server/src/main/java/org/eclipse/jetty/server/MultiParts.java
#	jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
This commit is contained in:
Joakim Erdfelt 2019-04-18 08:24:10 -05:00
commit c2e81f5584
75 changed files with 2673 additions and 534 deletions

View File

@ -84,6 +84,7 @@ Opening the `start.d/session-store-hazelcast-remote.ini` will show a list of all
#jetty.session.hazelcast.mapName=jetty_sessions
#jetty.session.hazelcast.onlyClient=true
#jetty.session.hazelcast.configurationLocation=
jetty.session.hazelcast.scavengeZombies=false
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0
----
@ -94,6 +95,8 @@ jetty.session.hazelcast.onlyClient::
Hazelcast instance will be configured in client mode
jetty.session.hazelcast.configurationLocation::
Path to an an Hazelcast xml configuration file
jetty.session.hazelcast.scavengeZombies::
True/False. `False` by default. If `true`, jetty will use hazelcast queries to find sessions that are no longer being used on any jetty node and whose expiry time has passed. If you enable this option, and your session stores attributes that reference classes from inside your webapp, or jetty classes, you will need to ensure that these classes are available on each of your hazelcast instances.
jetty.session.gracePeriod.seconds::
Amount of time, in seconds, to wait for other nodes to be checked to verify an expired session is in fact expired throughout the cluster before closing it.
jetty.session.savePeriod.seconds=0::
@ -106,6 +109,8 @@ Configuring `savePeriod` is useful if your persistence technology is very slow/c
In a clustered environment, there is a risk of the last access time of the session being out-of-date in the shared store for up to `savePeriod` seconds.
This allows the possibility that a node may prematurely expire the session, even though it is in use by another node.
Thorough consideration of the `maxIdleTime` of the session when setting the `savePeriod` is imperative - there is no point in setting a `savePeriod` that is larger than the `maxIdleTime`.
Be aware using the `scavengeZombies` option that if your session attributes contain classes from inside your webapp (or jetty classes) then you will need to put these classes onto the classpath of all of your hazelcast instances.
____
==== Configuring Embedded Hazelcast Clustering
@ -165,15 +170,18 @@ Opening the `start.d/start.d/session-store-hazelcast-embedded.ini` will show a l
#jetty.session.hazelcast.mapName=jetty_sessions
#jetty.session.hazelcast.configurationLocation=
jetty.session.hazelcast.scavengeZombies=false
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0
----
jetty.session.hazelcast.mapName::
Name of the Map in Hazelcast where sessions will be stored.
jetty.session.gracePeriod.seconds::
Amount of time, in seconds, to wait for other nodes to be checked to verify an expired session is in fact expired throughout the cluster before closing it.
jetty.session.hazelcast.configurationLocation::
Path to an an Hazelcast xml configuration file
jetty.session.hazelcast.scavengeZombies::
True/False. `False` by default. If `true`, jetty will use hazelcast queries to find sessions that are no longer being used on any jetty node and whose expiry time has passed. If you enable this option, and your sessions contain attributes that reference classes from inside your webapp (or jetty classes) you will need to ensure that these classes are available on each of your hazelcast instances.
jetty.session.gracePeriod.seconds::
Amount of time, in seconds, to wait for other nodes to be checked to verify an expired session is in fact expired throughout the cluster before closing it.
jetty.session.savePeriod.seconds=0::
By default whenever the last concurrent request leaves a session, that session is always persisted via the `SessionDataStore`, even if the only thing that changed on the session is its updated last access time.
A non-zero value means that the `SessionDataStore` will skip persisting the session if only the access time changed, and it has been less than `savePeriod` seconds since the last time the session was written.
@ -184,4 +192,6 @@ Configuring `savePeriod` is useful if your persistence technology is very slow/c
In a clustered environment, there is a risk of the last access time of the session being out-of-date in the shared store for up to `savePeriod` seconds.
This allows the possibility that a node may prematurely expire the session, even though it is in use by another node.
Thorough consideration of the `maxIdleTime` of the session when setting the `savePeriod` is imperative - there is no point in setting a `savePeriod` that is larger than the `maxIdleTime`.
Be aware using the `scavengeZombies` option that if your session attributes contain classes from inside your webapp (or jetty classes) then you will need to put these classes onto the classpath of all of your hazelcast instances. In the cast of embedded hazelcast, as it is started before your webapp, it will NOT have access to your webapp's classes - you will need to extract these classes and put them onto the jetty server's classpath.
____

View File

@ -24,10 +24,6 @@
When using the Jetty distribution, you will first need to enable the `session-store-infinispan-remote` link:#startup-modules[module] for your link:#startup-base-and-home[Jetty base] using the `--add-to-start` argument on the command line.
____
[IMPORTANT]
If you are running Jetty with JDK 9 or greater, enable `session-store-infinispan-remote-910.mod` instead.
____
[source, screen, subs="{sub-order}"]
----
@ -52,7 +48,7 @@ INFO : server transitively enabled, ini template available with --add-
INFO : sessions transitively enabled, ini template available with --add-to-start=sessions
INFO : session-store-infinispan-remote initialized in ${jetty.base}/start.d/session-store-infinispan-remote.ini
MKDIR : ${jetty.base}/lib/infinispan
DOWNLD: https://repo1.maven.org/maven2/org/infinispan/infinispan-remote/7.1.1.Final/infinispan-remote-7.1.1.Final.jar to ${jetty.base}/lib/infinispan/infinispan-remote-7.1.1.Final.jar
DOWNLD: https://repo1.maven.org/maven2/org/infinispan/infinispan-remote-it/9.4.8.Final/infinispan-remote-it-9.4.8.Final.jar to ${jetty.base}/lib/infinispan/infinispan-remote-it-9.4.8.Final.jar
MKDIR : ${jetty.base}/resources
COPY : ${jetty.home}/modules/session-store-infinispan-remote/resources/hotrod-client.properties to ${jetty.base}/resources/hotrod-client.properties
INFO : Base directory was modified
@ -93,13 +89,17 @@ Opening the `start.d/session-store-infinispan-remote.ini` will show a list of al
jetty.session.infinispan.remoteCacheName::
Name of the cache in Infinispan where sessions will be stored.
jetty.session.infinispan.idleTimeout.seconds::
Amount of time, in seconds, that the system allows the connector to remain idle before closing the connection.
Amount of time, in seconds, that a session entry in infinispan can be idle (ie not read or written) before infinispan will delete its entry.
Usually, you do *not* want to set a value for this, as you want jetty to handle all session expiration (and call any SessionListeners).
However, if there is the possibility that sessions can be left in infinispan but no longer referenced by any jetty node (so called "zombie" or "orphan" sessions), then you might want to use this feature.
You should make sure that the number of seconds you specify is sufficiently large to avoid the situation where a session is still being referenced by jetty, but is rarely accessed and thus deleted by infinispan.
Alternatively, you can enable the `infinispan-remote-query` module, which will allow jetty to search the infinispan session cache to proactively find and properly (ie calling any SessionListeners) scavenge defunct sessions.
jetty.session.gracePeriod.seconds::
Amount of time, in seconds, to wait for other nodes to be checked to verify an expired session is in fact expired throughout the cluster before closing it.
jetty.session.savePeriod.seconds=0::
By default whenever the last concurrent request leaves a session, that session is always persisted via the `SessionDataStore`, even if the only thing that changed on the session is its updated last access time.
A non-zero value means that the `SessionDataStore` will skip persisting the session if only the access time changed, and it has been less than `savePeriod` seconds since the last time the session was written.
+
____
[NOTE]
Configuring `savePeriod` is useful if your persistence technology is very slow/costly for writes.
@ -108,6 +108,19 @@ This allows the possibility that a node may prematurely expire the session, even
Thorough consideration of the `maxIdleTime` of the session when setting the `savePeriod` is imperative - there is no point in setting a `savePeriod` that is larger than the `maxIdleTime`.
____
==== Configuring the Remote Infinispan Query Module
Enabling this module allows jetty to search infinispan for expired sessions that are no longer being referenced by any jetty node.
Note that this is an *additional* module, to be used in conjuction with the `session-store-infinispan-remote` module.
[source, screen, subs="{sub-order}"]
----
java -jar ../start.jar --add-to-start=infinispan-remote-query
----
There are no configuration properties associated with this module.
==== Configuring Embedded Inifinspan Clustering
During testing, it can be helpful to run an in-process instance of Infinispan.
@ -137,7 +150,7 @@ Proceed (y/N)? y
INFO : server initialised (transitively) in ${jetty.base}/start.d/server.ini
INFO : sessions initialised (transitively) in ${jetty.base}/start.d/sessions.ini
INFO : session-store-infinispan-embedded initialised in ${jetty.base}/start.d/session-store-infinispan-embedded.ini
DOWNLOAD: https://repo1.maven.org/maven2/org/infinispan/infinispan-embedded/7.1.1.Final/infinispan-embedded-7.1.1.Final.jar to ${jetty.base}/lib/infinispan/infinispan-embedded-7.1.1.Final.jar
DOWNLOAD: https://repo1.maven.org/maven2/org/infinispan/infinispan-embedded-it/9.4.8.Final/infinispan-embedded-it-9.4.8.Final.jar to ${jetty.base}/lib/infinispan/infinispan-embedded-it-9.4.8.Final.jar
INFO : Base directory was modified
----
@ -180,6 +193,19 @@ This allows the possibility that a node may prematurely expire the session, even
Thorough consideration of the `maxIdleTime` of the session when setting the `savePeriod` is imperative - there is no point in setting a `savePeriod` that is larger than the `maxIdleTime`.
____
==== Configuring Inifinspan Embedded Query
Similarly to the `session-store-infinispan-remote` module, the `session-store-infinispan-embedded` module has an adjunct module `infinispan-embedded-query`, which when enabled, will allow jetty to detect and properly scavenge defunct sessions stranded in infinispan.
[source, screen, subs="{sub-order}"]
----
java -jar ../start.jar --add-to-start=infinispan-embedded-query
----
There are no configuration properties associated with this module.
==== Converting session format for jetty-9.4.13
From jetty-9.4.13 onwards, we have changed the format of the serialized session when using a remote cache (ie using hotrod).

View File

@ -12,7 +12,7 @@
<name>Jetty :: Hazelcast Session Manager</name>
<properties>
<hazelcast.version>3.9.3</hazelcast.version>
<hazelcast.version>3.9.4</hazelcast.version>
<bundle-symbolic-name>${project.groupId}.hazelcast</bundle-symbolic-name>
</properties>

View File

@ -13,6 +13,7 @@
<New id="sessionDataStoreFactory" class="org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStoreFactory">
<Set name="mapName"><Property name="jetty.session.hazelcast.mapName" default="jetty-distributed-session-map" /></Set>
<Set name="hazelcastInstanceName"><Property name="jetty.session.hazelcast.hazelcastInstanceName" default="JETTY_DISTRIBUTED_SESSION_INSTANCE" /></Set>
<Set name="scavengeZombies"><Property name="jetty.session.hazelcast.scavengeZombies" default="false"/></Set>
<Set name="gracePeriodSec"><Property name="jetty.session.gracePeriod.seconds" default="3600" /></Set>
<Set name="savePeriodSec"><Property name="jetty.session.savePeriod.seconds" default="0" /></Set>
</New>

View File

@ -12,6 +12,7 @@
<New id="sessionDataStoreFactory" class="org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStoreFactory">
<Set name="mapName"><Property name="jetty.session.hazelcast.mapName" default="jetty-distributed-session-map" /></Set>
<Set name="hazelcastInstanceName"><Property name="jetty.session.hazelcast.hazelcastInstanceName" default="JETTY_DISTRIBUTED_SESSION_INSTANCE" /></Set>
<Set name="scavengeZombies"><Property name="jetty.session.hazelcast.scavengeZombies" default="false"/></Set>
<Set name="gracePeriodSec"><Property name="jetty.session.gracePeriod.seconds" default="3600" /></Set>
<Set name="savePeriodSec"><Property name="jetty.session.savePeriod.seconds" default="0" /></Set>
<Set name="onlyClient"><Property name="jetty.session.hazelcast.onlyClient" default="true" /></Set>

View File

@ -13,7 +13,7 @@ session-store
sessions
[files]
maven://com.hazelcast/hazelcast/3.9.3|lib/hazelcast/hazelcast-3.9.3.jar
maven://com.hazelcast/hazelcast/3.9.4|lib/hazelcast/hazelcast-3.9.4.jar
[xml]
etc/sessions/hazelcast/default.xml
@ -32,5 +32,6 @@ http://www.apache.org/licenses/LICENSE-2.0.html
jetty.session.hazelcast.mapName=jetty-distributed-session-map
jetty.session.hazelcast.hazelcastInstanceName=JETTY_DISTRIBUTED_SESSION_INSTANCE
#jetty.session.hazelcast.configurationLocation=
jetty.session.hazelcast.scavengeZombies=false
jetty.session.gracePeriod.seconds=3600
jetty.session.savePeriod.seconds=0

View File

@ -13,8 +13,8 @@ session-store
sessions
[files]
maven://com.hazelcast/hazelcast/3.9.3|lib/hazelcast/hazelcast-3.9.3.jar
maven://com.hazelcast/hazelcast-client/3.9.3|lib/hazelcast/hazelcast-client-3.9.3.jar
maven://com.hazelcast/hazelcast/3.9.4|lib/hazelcast/hazelcast-3.9.4.jar
maven://com.hazelcast/hazelcast-client/3.9.4|lib/hazelcast/hazelcast-client-3.9.4.jar
[xml]
etc/sessions/hazelcast/remote.xml
@ -33,6 +33,7 @@ http://www.apache.org/licenses/LICENSE-2.0.html
jetty.session.hazelcast.mapName=jetty-distributed-session-map
jetty.session.hazelcast.hazelcastInstanceName=JETTY_DISTRIBUTED_SESSION_INSTANCE
jetty.session.hazelcast.onlyClient=true
jetty.session.hazelcast.scavengeZombies=false
#jetty.session.hazelcast.configurationLocation=
jetty.session.gracePeriod.seconds=3600
jetty.session.savePeriod.seconds=0

View File

@ -18,11 +18,16 @@
package org.eclipse.jetty.hazelcast.session;
import java.util.Collections;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import com.hazelcast.core.IMap;
import com.hazelcast.query.EntryObject;
import com.hazelcast.query.Predicate;
import com.hazelcast.query.PredicateBuilder;
import org.eclipse.jetty.server.session.AbstractSessionDataStore;
import org.eclipse.jetty.server.session.SessionContext;
import org.eclipse.jetty.server.session.SessionData;
@ -32,8 +37,6 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import com.hazelcast.core.IMap;
/**
* Session data stored in Hazelcast
*/
@ -47,9 +50,35 @@ public class HazelcastSessionDataStore
private IMap<String, SessionData> sessionDataMap;
private boolean _scavengeZombies;
public HazelcastSessionDataStore()
{
// no op
}
/** Control whether or not to execute queries to find
* "zombie" sessions - ie sessions that are no longer
* actively referenced by any jetty instance and should
* be expired.
*
* If you use this feature, be aware that if your session
* stores any attributes that use classes from within your
* webapp, or from within jetty, you will need to make sure
* those classes are available to all of your hazelcast
* instances, whether embedded or remote.
*
* @param scavengeZombies true means unreferenced sessions
* will be actively sought and expired. False means that they
* will remain in hazelcast until some other mechanism removes them.
*/
public void setScavengeZombieSessions (boolean scavengeZombies)
{
_scavengeZombies = scavengeZombies;
}
public boolean isScavengeZombies()
{
return _scavengeZombies;
}
@Override
@ -97,7 +126,9 @@ public class HazelcastSessionDataStore
public void initialize( SessionContext context )
throws Exception
{
_context = context;
super.initialize(context);
if (isScavengeZombies())
sessionDataMap.addIndex("expiry", true);
}
@Override
@ -113,16 +144,13 @@ public class HazelcastSessionDataStore
return true;
}
@Override
public Set<String> doGetExpired( Set<String> candidates )
{
if (candidates == null || candidates.isEmpty())
{
return Collections.emptySet();
}
long now = System.currentTimeMillis();
return candidates.stream().filter( candidate -> {
Set<String> expiredSessionIds = candidates.stream().filter( candidate -> {
if (LOG.isDebugEnabled())
LOG.debug( "Checking expiry for candidate {}", candidate );
@ -184,7 +212,49 @@ public class HazelcastSessionDataStore
}
return false;
} ).collect( Collectors.toSet() );
if (isScavengeZombies())
{
//Now find other sessions in hazelcast that have expired
final AtomicReference<Set<String>> reference = new AtomicReference<>();
final AtomicReference<Exception> exception = new AtomicReference<>();
_context.run(()->
{
try
{
Set<String> ids = new HashSet<>();
EntryObject eo = new PredicateBuilder().getEntryObject();
Predicate<?, ?> predicate = eo.get("expiry").greaterThan(0).and(eo.get("expiry").lessEqual(now));
Collection<SessionData> results = sessionDataMap.values(predicate);
if (results != null)
{
for (SessionData sd: results)
ids.add(sd.getId());
}
reference.set(ids);
}
catch (Exception e)
{
exception.set(e);
}
});
if (exception.get() != null)
{
LOG.warn("Error querying for expired sessions {}", exception.get());
return expiredSessionIds;
}
if (reference.get() != null)
{
expiredSessionIds.addAll(reference.get());
}
}
return expiredSessionIds;
}
@Override
public boolean exists( String id )

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.hazelcast.session;
import java.io.IOException;
import com.hazelcast.client.HazelcastClient;
import com.hazelcast.client.config.ClientConfig;
import com.hazelcast.client.config.XmlClientConfigBuilder;
@ -33,8 +35,6 @@ 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}
*/
@ -54,8 +54,20 @@ public class HazelcastSessionDataStoreFactory
private HazelcastInstance hazelcastInstance;
private MapConfig mapConfig;
private boolean scavengeZombies = false;
public boolean isScavengeZombies()
{
return scavengeZombies;
}
public void setScavengeZombies(boolean scavengeZombies)
{
this.scavengeZombies = scavengeZombies;
}
@Override
public SessionDataStore getSessionDataStore( SessionHandler handler )
throws Exception
@ -122,9 +134,10 @@ public class HazelcastSessionDataStoreFactory
}
}
// initialize the map
hazelcastSessionDataStore.setSessionDataMap(hazelcastInstance.getMap( mapName ) );
hazelcastSessionDataStore.setGracePeriodSec( getGracePeriodSec() );
hazelcastSessionDataStore.setSavePeriodSec( getSavePeriodSec() );
hazelcastSessionDataStore.setSessionDataMap(hazelcastInstance.getMap( mapName ));
hazelcastSessionDataStore.setGracePeriodSec(getGracePeriodSec());
hazelcastSessionDataStore.setSavePeriodSec(getSavePeriodSec());
hazelcastSessionDataStore.setScavengeZombieSessions(scavengeZombies);
return hazelcastSessionDataStore;
}

View File

@ -2,4 +2,4 @@
<cache-container default-cache="jetty-sessions">
<local-cache name="jetty-sessions"/>
</cache-container>
</infinispan>
</infinispan>

View File

@ -432,12 +432,41 @@
</goals>
<configuration>
<includeGroupIds>org.eclipse.jetty,org.eclipse.jetty.websocket</includeGroupIds>
<excludeArtifactIds>infinispan-embedded,infinispan-remote</excludeArtifactIds>
<classifier>config</classifier>
<failOnMissingClassifierArtifact>false</failOnMissingClassifierArtifact>
<excludes>META-INF/**</excludes>
<outputDirectory>${assembly-directory}</outputDirectory>
</configuration>
</execution>
<execution>
<id>unpack-infinispan-config</id>
<phase>generate-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-embedded</artifactId>
<version>${project.version}</version>
<classifier>config</classifier>
<type>jar</type>
</artifactItem>
<artifactItem>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-remote</artifactId>
<version>${project.version}</version>
<classifier>config</classifier>
<type>jar</type>
</artifactItem>
</artifactItems>
<failOnMissingClassifierArtifact>true</failOnMissingClassifierArtifact>
<excludes>META-INF/**</excludes>
<outputDirectory>${assembly-directory}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
@ -681,7 +710,24 @@
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-infinispan</artifactId>
<artifactId>infinispan-embedded</artifactId>
<version>${project.version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-embedded-query</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-remote</artifactId>
<version>${project.version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-remote-query</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>

9
jetty-home/start.ini Normal file
View File

@ -0,0 +1,9 @@
# ---------------------------------------
# Module: session-store-infinispan-embedded
# Enables session data store in a local Infinispan cache
# ---------------------------------------
--module=session-store-infinispan-embedded
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0

View File

@ -0,0 +1,74 @@
<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</groupId>
<artifactId>infinispan-parent</artifactId>
<version>10.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>infinispan-common</artifactId>
<name>Jetty :: Infinispan Session Manager Common</name>
<url>http://www.eclipse.org/jetty</url>
<properties>
<bundle-symbolic-name>${project.groupId}.infinispan.common</bundle-symbolic-name>
</properties>
<build>
<defaultGoal>install</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>config</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId>
<version>${infinispan.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.infinispan.protostream</groupId>
<artifactId>protostream</artifactId>
<version>4.2.2.Final</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-client-hotrod</artifactId>
<version>${infinispan.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-remote-query-client</artifactId>
<version>${infinispan.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,37 @@
<?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">
<Call name="addBean">
<Arg>
<New id="sessionDataStoreFactory" class="org.eclipse.jetty.session.infinispan.InfinispanSessionDataStoreFactory">
<Set name="cache"><Ref refid="cache"/></Set>
<Set name="infinispanIdleTimeoutSec"><Property name="jetty.session.infinispan.idleTimeout.seconds" default="0" /></Set>
<Set name="gracePeriodSec"><Property name="jetty.session.gracePeriod.seconds" default="3600" /></Set>
<Set name="savePeriodSec"><Property name="jetty.session.savePeriod.seconds" default="0" /></Set>
</New>
</Arg>
</Call>
<!-- get QueryManager using the QueryManagerFactory -->
<Ref refid="queryMgrFactory">
<Call id="queryManager" name="getQueryManager">
<Arg>
<Ref refid="cache"/>
</Arg>
</Call>
</Ref>
<!-- set QueryManager in SessionDataStoreFactory -->
<Ref refid="sessionDataStoreFactory">
<Call name="setQueryManager">
<Arg>
<Ref refid="queryManager"/>
</Arg>
</Call>
</Ref>
</Configure>

View File

@ -0,0 +1,20 @@
[description]
Common to all infinispan modules
[tags]
session
[depend]
sessions
[lib]
lib/infinispan-common-${jetty.version}.jar
lib/infinispan/*.jar
[ini]
infinispan.version?=9.1.0.Final
[license]
Infinispan is an open source project hosted on Github and released under the Apache 2.0 license.
http://infinispan.org/
http://www.apache.org/licenses/LICENSE-2.0.html

View File

@ -1,6 +1,6 @@
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// Copyright (c) 1995-2019 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
@ -34,6 +34,7 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.infinispan.commons.api.BasicCache;
/**
* InfinispanSessionDataStore
*
@ -48,10 +49,14 @@ public class InfinispanSessionDataStore extends AbstractSessionDataStore
/**
* Clustered cache of sessions
*/
private BasicCache<String, Object> _cache;
private BasicCache<String, SessionData> _cache;
private int _infinispanIdleTimeoutSec;
private QueryManager _queryManager;
private boolean _passivating;
@ -61,7 +66,7 @@ public class InfinispanSessionDataStore extends AbstractSessionDataStore
*
* @return the cache
*/
public BasicCache<String, Object> getCache()
public BasicCache<String, SessionData> getCache()
{
return _cache;
}
@ -73,13 +78,26 @@ public class InfinispanSessionDataStore extends AbstractSessionDataStore
*
* @param cache the cache
*/
public void setCache (BasicCache<String, Object> cache)
public void setCache (BasicCache<String, SessionData> cache)
{
this._cache = cache;
}
public QueryManager getQueryManager()
{
return _queryManager;
}
public void setQueryManager (QueryManager queryManager)
{
_queryManager = queryManager;
}
/**
* @see org.eclipse.jetty.server.session.SessionDataStore#load(String)
*/
@Override
protected void doStart() throws Exception
{
@ -139,67 +157,92 @@ public class InfinispanSessionDataStore extends AbstractSessionDataStore
@Override
public Set<String> doGetExpired(Set<String> candidates)
{
if (candidates == null || candidates.isEmpty())
return candidates;
long now = System.currentTimeMillis();
Set<String> expired = new HashSet<>();
//TODO if there is NOT an idle timeout set on entries in infinispan, need to check other sessions
//that are not currently in the SessionDataStore (eg they've been passivated)
for (String candidate:candidates)
/*
* 1. Select sessions managed by this node for our context that have expired
*/
if(candidates != null)
{
if (LOG.isDebugEnabled())
LOG.debug("Checking expiry for candidate {}", candidate);
try
for (String candidate:candidates)
{
SessionData sd = load(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)
{
expired.add(candidate);
if (LOG.isDebugEnabled())
LOG.debug("Session {} does not exist in infinispan", candidate);
}
else
{
if (_context.getWorkerName().equals(sd.getLastNode()))
//if the session no longer exists
if (sd == null)
{
//we are its manager, add it to the expired set if it is expired now
if ((sd.getExpiry() > 0 ) && sd.getExpiry() <= now)
{
expired.add(candidate);
if (LOG.isDebugEnabled())
LOG.debug("Session {} managed by {} is expired", candidate, _context.getWorkerName());
}
expired.add(candidate);
if (LOG.isDebugEnabled())
LOG.debug("Session {} does not exist in infinispan", candidate);
}
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 (_context.getWorkerName().equals(sd.getLastNode()))
{
if ((sd.getExpiry() > 0 ) && sd.getExpiry() < (now - (1000L * (3 * _gracePeriodSec))))
//we are its manager, add it to the expired set if it is expired now
if ((sd.getExpiry() > 0 ) && sd.getExpiry() <= now)
{
expired.add(candidate);
if (LOG.isDebugEnabled())
LOG.debug("Session {} managed by {} is expired", candidate, _context.getWorkerName());
}
}
else
{
if ((sd.getExpiry() > 0 ) && sd.getExpiry() < (now - (1000L * _gracePeriodSec)))
expired.add(candidate);
//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))))
expired.add(candidate);
}
else
{
if ((sd.getExpiry() > 0 ) && sd.getExpiry() < (now - (1000L * _gracePeriodSec)))
expired.add(candidate);
}
}
}
}
}
catch (Exception e)
{
LOG.warn("Error checking if candidate {} is expired", candidate, e);
catch (Exception e)
{
LOG.warn("Error checking if candidate {} is expired", candidate, e);
}
}
}
/*
* 2. Select sessions for any node or context that have expired
* at least 1 graceperiod since the last expiry check. If we haven't done previous expiry checks, then check
* those that have expired at least 3 graceperiod ago.
*/
if(_queryManager != null)
{
long upperBound = now;
if (_lastExpiryCheckTime <= 0)
upperBound = (now - (3*(1000L * _gracePeriodSec)));
else
upperBound = _lastExpiryCheckTime - (1000L * _gracePeriodSec);
if (LOG.isDebugEnabled()) LOG.debug("{}- Pass 2: Searching for sessions expired before {}", _context.getWorkerName(), upperBound);
for (String sessionId : _queryManager.queryExpiredSessions(upperBound))
{
expired.add(sessionId);
if (LOG.isDebugEnabled()) LOG.debug ("{}- Found expired sessionId=",_context.getWorkerName(), sessionId);
}
}
return expired;
}
@ -233,8 +276,8 @@ public class InfinispanSessionDataStore extends AbstractSessionDataStore
{
return _passivating;
}
@Override
public boolean exists(String id) throws Exception

View File

@ -20,8 +20,9 @@
package org.eclipse.jetty.session.infinispan;
import org.eclipse.jetty.server.session.AbstractSessionDataStoreFactory;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.server.session.SessionData;
import org.eclipse.jetty.server.session.SessionDataStore;
import org.eclipse.jetty.server.session.SessionHandler;
import org.infinispan.commons.api.BasicCache;
/**
@ -32,8 +33,8 @@ import org.infinispan.commons.api.BasicCache;
public class InfinispanSessionDataStoreFactory extends AbstractSessionDataStoreFactory
{
int _infinispanIdleTimeoutSec;
BasicCache<String, Object> _cache;
BasicCache<String, SessionData> _cache;
protected QueryManager _queryManager;
/**
* @return the infinispanIdleTimeoutSec
@ -62,6 +63,7 @@ public class InfinispanSessionDataStoreFactory extends AbstractSessionDataStoreF
store.setInfinispanIdleTimeoutSec(getInfinispanIdleTimeoutSec());
store.setCache(getCache());
store.setSavePeriodSec(getSavePeriodSec());
store.setQueryManager(getQueryManager());
return store;
}
@ -70,7 +72,7 @@ public class InfinispanSessionDataStoreFactory extends AbstractSessionDataStoreF
*
* @return the cache
*/
public BasicCache<String, Object> getCache()
public BasicCache<String, SessionData> getCache()
{
return _cache;
}
@ -82,10 +84,20 @@ public class InfinispanSessionDataStoreFactory extends AbstractSessionDataStoreF
*
* @param cache the cache
*/
public void setCache (BasicCache<String, Object> cache)
public void setCache (BasicCache<String, SessionData> cache)
{
this._cache = cache;
}
public QueryManager getQueryManager()
{
return _queryManager;
}
public void setQueryManager(QueryManager queryManager)
{
_queryManager = queryManager;
}
}

View File

@ -0,0 +1,36 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.session.infinispan;
import org.eclipse.jetty.server.session.SessionData;
import org.infinispan.commons.api.BasicCache;
/**
* NullQueryManagerFactory
*
* Trivial impl of the QueryManagerFactory that does not support doing queries.
*/
public class NullQueryManagerFactory implements QueryManagerFactory
{
@Override
public QueryManager getQueryManager(BasicCache<String, SessionData> cache)
{
return null;
}
}

View File

@ -0,0 +1,27 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.session.infinispan;
import java.util.Set;
public interface QueryManager
{
Set<String> queryExpiredSessions();
Set<String> queryExpiredSessions(long currentTime);
}

View File

@ -0,0 +1,27 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.session.infinispan;
import org.eclipse.jetty.server.session.SessionData;
import org.infinispan.commons.api.BasicCache;
public interface QueryManagerFactory
{
public QueryManager getQueryManager(BasicCache<String, SessionData> cache);
}

View File

@ -0,0 +1,125 @@
<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</groupId>
<artifactId>infinispan-parent</artifactId>
<version>10.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>infinispan-embedded-query</artifactId>
<name>Jetty :: Infinispan Session Manager Embedded with Querying</name>
<url>http://www.eclipse.org/jetty</url>
<properties>
<bundle-symbolic-name>${project.groupId}.infinispan.embedded.query</bundle-symbolic-name>
</properties>
<build>
<defaultGoal>install</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>build-deps-file</id>
<phase>generate-resources</phase>
<goals>
<goal>list</goal>
</goals>
<configuration>
<appendOutput>false</appendOutput>
<outputFile>${project.build.directory}/deps.txt</outputFile>
<sort>true</sort>
<excludeGroupIds>org.eclipse.jetty,javax.servlet,org.slf4j</excludeGroupIds>
<prependGroupId>true</prependGroupId>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>process-deps</id>
<phase>process-resources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<replaceregexp file="${project.build.directory}/deps.txt"
match=" *(.*):(.*):jar:(.*):.*$"
replace="maven://\1/\2/\3|lib/infinispan/\2-\3.jar"
byline="true"
/>
<replaceregexp file="${project.build.directory}/deps.txt"
match="The following files have been resolved:"
replace="[files]"
/>
</tasks>
</configuration>
</execution>
<execution>
<id>process-mod</id>
<phase>process-resources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<concat destfile="${project.build.directory}/infinispan-query-libs.mod">
<fileset file="src/main/config-template/modules/infinispan-query-libs.mod"/>
<fileset file="${project.build.directory}/deps.txt"/>
</concat>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>src/main/assembly/config.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-common</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-commons</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-query</artifactId>
<version>${infinispan.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly>
<id>config</id>
<includeBaseDirectory>false</includeBaseDirectory>
<formats>
<format>jar</format>
</formats>
<fileSets>
<fileSet>
<directory>src/main/config-template</directory>
<outputDirectory></outputDirectory>
<includes>
<include>**</include>
</includes>
<excludes>
<exclude>**/infinispan-query-libs.mod</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>target</directory>
<outputDirectory>modules</outputDirectory>
<includes>
<include>infinispan-query-libs.mod</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@ -0,0 +1,86 @@
<?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">
<!-- ===================================================================== -->
<!-- Get a reference to the default local cache. -->
<!-- ===================================================================== -->
<!-- TODO allow users to add more properties -->
<New id="properties" class="java.util.Properties">
<Call name="put">
<Arg>
<Get class="org.hibernate.search.cfg.Environment" name="MODEL_MAPPING"/>
</Arg>
<Arg>
<New class="org.hibernate.search.cfg.SearchMapping">
<Call name="entity">
<Arg>
<Get class="org.eclipse.jetty.server.session.SessionData" name="class"/>
</Arg>
<Call name="indexed">
<Call name="providedId">
<Call name="property">
<Arg type="String">expiry</Arg>
<Arg>
<Get class="java.lang.annotation.ElementType" name="FIELD"/>
</Arg>
<Call name="field"/>
</Call>
</Call>
</Call>
</Call>
</New>
</Arg>
</Call>
</New>
<New id="cacheMgr" class="org.infinispan.manager.DefaultCacheManager">
<Arg>
<Property name="jetty.base" default="."/>/etc/infinispan.xml
</Arg>
<Get id="defaultConfig" name="defaultCacheConfiguration"/>
</New>
<New class="org.infinispan.configuration.cache.ConfigurationBuilder">
<Call name="read">
<Arg>
<Ref refid="defaultConfig"/>
</Arg>
<Call name="indexing">
<Call name="index">
<Arg>
<Get class="org.infinispan.configuration.cache.Index" name="ALL"/>
</Arg>
<Call name="addIndexedEntity">
<Arg>
<Get class="org.eclipse.jetty.server.session.SessionData" name="class"/>
</Arg>
<Call name="withProperties">
<Arg>
<Ref refid="properties"/>
</Arg>
<Call id="config" name="build"/>
</Call>
</Call>
</Call>
</Call>
</Call>
</New>
<Ref refid="cacheMgr">
<Call name="defineConfiguration">
<Arg>jetty-query-sessions</Arg>
<Arg><Ref refid="config"/></Arg>
</Call>
<Get id="cache" name="cache"/>
</Ref>
<!-- set queryMgrFactory reference to EmbeddedQueryManagerFactory -->
<New id="queryMgrFactory" class="org.eclipse.jetty.session.infinispan.EmbeddedQueryManagerFactory"/>
</Configure>

View File

@ -0,0 +1,20 @@
[description]
Enables querying with the Infinispan cache
[tags]
session
[provides]
infinispan-embedded
[depends]
infinispan-query-libs
[lib]
lib/infinispan/*.jar
lib/infinispan-embedded-query-${jetty.version}.jar
[xml]
etc/sessions/infinispan/infinispan-embedded-query.xml
etc/sessions/infinispan/infinispan-common.xml

View File

@ -0,0 +1,11 @@
[description]
The Infinispan query libraries
[tags]
3rdparty
infinispan
[depends]
infinispan-query

View File

@ -0,0 +1,16 @@
[description]
Enables querying with the Infinispan cache
[tags]
session
3rdparty
infinispan
[license]
Infinispan is an open source project hosted on Github and released under the Apache 2.0 license.
http://infinispan.org/
http://www.apache.org/licenses/LICENSE-2.0.html
[ini]
## Hide the infinispan libraries from deployed webapps
jetty.webapp.addServerClasses+=,${jetty.base.uri}/lib/infinispan/

View File

@ -0,0 +1,42 @@
package org.eclipse.jetty.session.infinispan;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jetty.server.session.SessionData;
import org.infinispan.Cache;
import org.infinispan.query.Search;
import org.infinispan.query.dsl.Query;
import org.infinispan.query.dsl.QueryFactory;
public class EmbeddedQueryManager implements QueryManager
{
private Cache<String, SessionData> _cache;
public EmbeddedQueryManager(Cache<String, SessionData> cache)
{
_cache = cache;
}
@Override
public Set<String> queryExpiredSessions(long time)
{
QueryFactory qf = Search.getQueryFactory(_cache);
Query q = qf.from(SessionData.class).select("id").having("expiry").lte(time).build();
List<Object[]> list = q.list();
Set<String> ids = new HashSet<>();
for(Object[] sl : list)
ids.add((String)sl[0]);
return ids;
}
@Override
public Set<String> queryExpiredSessions()
{
return queryExpiredSessions(System.currentTimeMillis());
}
}

View File

@ -0,0 +1,19 @@
package org.eclipse.jetty.session.infinispan;
import org.eclipse.jetty.server.session.SessionData;
import org.infinispan.Cache;
import org.infinispan.commons.api.BasicCache;
public class EmbeddedQueryManagerFactory implements QueryManagerFactory
{
@Override
public QueryManager getQueryManager(BasicCache<String, SessionData> cache)
{
if (!(cache instanceof Cache))
throw new IllegalArgumentException("Argument was not of type Cache");
return new EmbeddedQueryManager((Cache<String, SessionData>)cache);
}
}

View File

@ -0,0 +1,111 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.server.session.infinispan;
import java.lang.annotation.ElementType;
import java.util.HashSet;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import org.eclipse.jetty.server.session.SessionData;
import org.eclipse.jetty.session.infinispan.EmbeddedQueryManager;
import org.eclipse.jetty.session.infinispan.QueryManager;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.hibernate.search.cfg.Environment;
import org.hibernate.search.cfg.SearchMapping;
import org.infinispan.Cache;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.cache.Index;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class EmbeddedQueryManagerTest
{
public static final String DEFAULT_CACHE_NAME = "session_test_cache";
@Test
public void test() throws Exception
{
String _name = DEFAULT_CACHE_NAME+System.currentTimeMillis();
EmbeddedCacheManager _manager;
_manager = new DefaultCacheManager(new GlobalConfigurationBuilder().globalJmxStatistics().allowDuplicateDomains(true).build());
//TODO verify that this is being indexed properly, if you change expiry to something that is not a valid field it still passes the tests
SearchMapping mapping = new SearchMapping();
mapping.entity(SessionData.class).indexed().providedId().property("expiry", ElementType.FIELD).field();
Properties properties = new Properties();
properties.put(Environment.MODEL_MAPPING, mapping);
properties.put("hibernate.search.default.indexBase", MavenTestingUtils.getTargetTestingDir().getAbsolutePath());
Configuration dcc = _manager.getDefaultCacheConfiguration();
ConfigurationBuilder b = new ConfigurationBuilder();
if (dcc != null)
b = b.read(dcc);
b.indexing().index(Index.ALL).addIndexedEntity(SessionData.class).withProperties(properties);
Configuration c = b.build();
_manager.defineConfiguration(_name, c);
Cache<String, SessionData> _cache = _manager.getCache(_name);
//put some sessions into the cache
int numSessions = 10;
long currentTime = 500;
int maxExpiryTime = 1000;
Set<String> expiredSessions = new HashSet<>();
Random r = new Random();
for (int i=0; i<numSessions; i++)
{
//create new sessiondata with random expiry time
long expiryTime = r.nextInt(maxExpiryTime);
SessionData sd = new SessionData("sd"+i, "", "", 0, 0, 0, 0);
sd.setExpiry(expiryTime);
//if this entry has expired add it to expiry list
if (expiryTime <= currentTime)
expiredSessions.add("sd"+i);
//add to cache
_cache.put("sd"+i,sd);
}
//run the query
QueryManager qm = new EmbeddedQueryManager(_cache);
Set<String> queryResult = qm.queryExpiredSessions(currentTime);
// Check that the result is correct
assertEquals(expiredSessions.size(), queryResult.size());
for (String s : expiredSessions)
{
assertTrue(queryResult.contains(s));
}
}
}

View File

@ -0,0 +1,44 @@
<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</groupId>
<artifactId>infinispan-parent</artifactId>
<version>10.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>infinispan-embedded</artifactId>
<packaging>pom</packaging>
<name>Jetty :: Infinispan Session Manager Embedded</name>
<url>http://www.eclipse.org/jetty</url>
<properties>
<bundle-symbolic-name>${project.groupId}.infinispan.embedded</bundle-symbolic-name>
</properties>
<build>
<defaultGoal>install</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>config</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,17 @@
<?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">
<!-- ===================================================================== -->
<!-- Get a reference to the default local cache. -->
<!-- ===================================================================== -->
<New id="cacheMgr" class="org.infinispan.manager.DefaultCacheManager">
<Arg><Property name="jetty.base" default="."/>/etc/infinispan.xml</Arg>
<Get id="cache" name="cache"></Get>
</New>
<!-- set queryMgrFactory reference to NullQueryManagerFactory -->
<New id="queryMgrFactory" class="org.eclipse.jetty.session.infinispan.NullQueryManagerFactory"/>
</Configure>

View File

@ -0,0 +1,13 @@
[description]
Setup infinispan embedded without querying
[tags]
session
[provides]
infinispan-embedded
[xml]
etc/sessions/infinispan/infinispan-embedded.xml
etc/sessions/infinispan/infinispan-common.xml

View File

@ -0,0 +1,24 @@
[description]
Enables session data store in a local Infinispan cache
[tags]
session
[provides]
session-store
[depend]
infinispan-common
infinispan-embedded
[files]
basehome:modules/session-store-infinispan-embedded/infinispan.xml|etc/infinispan.xml
maven://org.infinispan/infinispan-embedded-it/${infinispan.version}|lib/infinispan/infinispan-embedded-it-${infinispan.version}.jar
[ini]
infinispan.version?=9.4.8.Final
[ini-template]
#jetty.session.infinispan.idleTimeout.seconds=0
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0

View File

@ -0,0 +1,5 @@
<infinispan>
<cache-container default-cache="jetty-sessions">
<local-cache name="jetty-sessions"/>
</cache-container>
</infinispan>

View File

@ -0,0 +1,169 @@
<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</groupId>
<artifactId>infinispan-parent</artifactId>
<version>10.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>infinispan-remote-query</artifactId>
<name>Jetty :: Infinispan Session Manager Remote</name>
<url>http://www.eclipse.org/jetty</url>
<properties>
<bundle-symbolic-name>${project.groupId}.infinispan.remote.query</bundle-symbolic-name>
</properties>
<build>
<defaultGoal>install</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>build-deps-file</id>
<phase>generate-resources</phase>
<goals>
<goal>list</goal>
</goals>
<configuration>
<appendOutput>false</appendOutput>
<outputFile>${project.build.directory}/deps.txt</outputFile>
<sort>true</sort>
<excludeGroupIds>org.eclipse.jetty,javax.servlet</excludeGroupIds>
<prependGroupId>true</prependGroupId>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>process-deps</id>
<phase>process-resources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<replaceregexp file="${project.build.directory}/deps.txt"
match=" *(.*):(.*):jar:(.*):(.*):.*$"
replace="maven://\1/\2/\4/jar/\3|lib/infinispan/\2-\3-\4.jar"
byline="true"
/>
<replaceregexp file="${project.build.directory}/deps.txt"
match=" *(.*):(.*):jar:(.*):.*$"
replace="maven://\1/\2/\3|lib/infinispan/\2-\3.jar"
byline="true"
/>
<replaceregexp file="${project.build.directory}/deps.txt"
match="The following files have been resolved:"
replace="[files]"
/>
</tasks>
</configuration>
</execution>
<execution>
<id>process-mod</id>
<phase>process-resources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<concat destfile="${project.build.directory}/infinispan-remote-query-libs.mod">
<fileset file="src/main/config-template/modules/infinispan-remote-query-libs.mod"/>
<fileset file="${project.build.directory}/deps.txt"/>
</concat>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>src/main/assembly/config.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</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>infinispan-common</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-commons</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-query</artifactId>
<version>${infinispan.version}</version>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-client-hotrod</artifactId>
<version>${infinispan.version}</version>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-remote-query-client</artifactId>
<version>${infinispan.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>remote</id>
<activation>
<property>
<name>hotrod.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>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly>
<id>config</id>
<includeBaseDirectory>false</includeBaseDirectory>
<formats>
<format>jar</format>
</formats>
<fileSets>
<fileSet>
<directory>src/main/config-template</directory>
<outputDirectory></outputDirectory>
<includes>
<include>**</include>
</includes>
<excludes>
<exclude>**/infinispan-remote-query-libs.mod</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>target</directory>
<outputDirectory>modules</outputDirectory>
<includes>
<include>infinispan-remote-query-libs.mod</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@ -3,23 +3,38 @@
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<!-- ===================================================================== -->
<!-- Set up infinispan to use protobuf marshalling instead of default. -->
<!-- Avoids classloading issues when marshalling/demarshalling webapps -->
<!-- that use jetty server classes in their attributes. -->
<!-- Get a reference to the remote cache manager. -->
<!-- ===================================================================== -->
<New id="mapping" class="org.hibernate.search.cfg.SearchMapping">
<Call name="entity">
<Arg><Call class="java.lang.Class" name="forName"><Arg>org.eclipse.jetty.server.session.SessionData</Arg></Call></Arg>
<Call name="indexed">
<Call name="providedId">
<Call name="property">
<Arg type="String">expiry</Arg>
<Arg><Get class="java.lang.annotation.ElementType" name="FIELD"/></Arg>
<Call name="field"/>
</Call>
</Call>
</Call>
</Call>
</New>
<New id="properties" class="java.util.Properties">
<Call name="load">
<Arg>
<New class="java.io.FileInputStream">
<New class="java.io.FileInputStream">
<Arg><Property name="jetty.base" default="."/>/resources/hotrod-client.properties</Arg>
</New>
</Arg>
</Call>
<Call name="put">
<Arg><Get class="org.hibernate.search.cfg.Environment" name="MODEL_MAPPING"/></Arg>
<Arg><Ref refid="mapping"/></Arg>
</Call>
</New>
<New class="org.infinispan.client.hotrod.configuration.ConfigurationBuilder">
<Call name="withProperties">
<Arg><Ref refid="properties"/></Arg>
@ -30,13 +45,12 @@
</Arg>
</Call>
<Call id="config" name="build"/>
</New>
</New>
<New id="remoteCacheManager" class="org.infinispan.client.hotrod.RemoteCacheManager">
<Arg><Ref refid="config"/></Arg>
</New>
<Call id="serial_context" class="org.infinispan.client.hotrod.marshall.ProtoStreamMarshaller" name="getSerializationContext">
<Arg>
<Ref refid="remoteCacheManager"/>
@ -60,29 +74,17 @@
</Arg>
</Call>
</Call>
<!-- ===================================================================== -->
<!-- Get a reference to the remote cache. -->
<!-- ===================================================================== -->
<!-- ===================================================================== -->
<Ref refid="remoteCacheManager">
<Call id="remoteCache" name="getCache">
<Call id="cache" name="getCache">
<Arg><Property name="jetty.session.infinispan.remoteCacheName" default="sessions"/></Arg>
</Call>
</Ref>
<!-- ===================================================================== -->
<!-- Configure a factory for InfinispanSessionDataStore using an -->
<!-- Infinispan remote cache -->
<!-- ===================================================================== -->
<Call name="addBean">
<Arg>
<New id="sessionDataStoreFactory" class="org.eclipse.jetty.session.infinispan.InfinispanSessionDataStoreFactory">
<Set name="cache"><Ref id="remoteCache"/></Set>
<Set name="infinispanIdleTimeoutSec"><Property name="jetty.session.infinispan.idleTimeout.seconds" default="0" /></Set>
<Set name="gracePeriodSec"><Property name="jetty.session.gracePeriod.seconds" default="3600" /></Set>
<Set name="savePeriodSec"><Property name="jetty.session.savePeriod.seconds" default="0" /></Set>
</New>
</Arg>
</Call>
<!-- set queryMgrFactory reference to RemoteQueryManagerFactory -->
<New id="queryMgrFactory" class="org.eclipse.jetty.session.infinispan.RemoteQueryManagerFactory"/>
</Configure>

View File

@ -0,0 +1,11 @@
[description]
The Infinispan remote query libraries
[tags]
3rdparty
infinispan
[depends]
infinispan-remote-query-serverclasses

View File

@ -0,0 +1,16 @@
[description]
Enables querying with a remote Infinispan cache
[tags]
session
3rdparty
infinispan
[license]
Infinispan is an open source project hosted on Github and released under the Apache 2.0 license.
http://infinispan.org/
http://www.apache.org/licenses/LICENSE-2.0.html
[ini]
## Hide the infinispan libraries from deployed webapps
jetty.webapp.addServerClasses+=,${jetty.base.uri}/lib/infinispan/

View File

@ -0,0 +1,24 @@
[description]
Enables querying with a remote Infinispan cache
[tags]
session
[provides]
infinispan-remote
[depends]
infinispan-remote-query-libs
[files]
basehome:modules/infinispan-remote-query/hotrod-client.properties|resources/hotrod-client.properties
basehome:modules/infinispan-remote-query/other_proto_marshallers.xml|etc/other_proto_marshallers.xml
[lib]
lib/infinispan/*.jar
lib/infinispan-remote-query-${jetty.version}.jar
[xml]
etc/sessions/infinispan/infinispan-remote-query.xml
etc/other_proto_marshallers.xml
etc/sessions/infinispan/infinispan-common.xml

View File

@ -0,0 +1,39 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<!-- ============================================================= -->
<!-- If your sessions contain any objects of classes from your -->
<!-- from your application, you need to describe each class in a -->
<!-- .proto file, and supply a marshaller for each to read/write -->
<!-- instances. These classes will need to exist on the server's -->
<!-- classpath because they are referenced BEFORE your webapp is -->
<!-- started. -->
<!-- ============================================================= -->
<Ref refid="serial_context">
<!--
<Call name="registerProtoFiles">
<Arg>
<New class="org.infinispan.protostream.FileDescriptorSource">
<Call name="addProtoFile">
<Arg>my.proto</Arg>
<Arg>
<New class="java.io.File">
<Arg><Property name="jetty.base" default="."/>/etc/my.proto</Arg>
</New>
</Arg>
</Call>
</New>
</Arg>
</Call>
-->
<!--
<Call name="registerMarshaller">
<Arg>
<New class="com.acme.MyMarshaller"/>
</Arg>
</Call>
-->
</Ref>
</Configure>

View File

@ -0,0 +1,68 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.session.infinispan;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jetty.server.session.SessionData;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.Search;
import org.infinispan.query.dsl.Query;
import org.infinispan.query.dsl.QueryFactory;
/**
* RemoteQueryManager
*
* A QueryManager impl that supports doing queries against remote infinispan server.
*
*/
public class RemoteQueryManager implements QueryManager
{
private RemoteCache<String, SessionData> _cache;
public RemoteQueryManager(RemoteCache<String, SessionData> cache)
{
_cache = cache;
}
@Override
public Set<String> queryExpiredSessions(long time)
{
// TODO can the QueryFactory be created only once
QueryFactory qf = Search.getQueryFactory(_cache);
Query q = qf.from(InfinispanSessionData.class).select("id").having("expiry").lte(time).build();
List<Object[]> list = q.list();
Set<String> ids = new HashSet<>();
for(Object[] sl : list)
ids.add((String)sl[0]);
return ids;
}
@Override
public Set<String> queryExpiredSessions()
{
return queryExpiredSessions(System.currentTimeMillis());
}
}

View File

@ -0,0 +1,38 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.session.infinispan;
import org.eclipse.jetty.server.session.SessionData;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.commons.api.BasicCache;
public class RemoteQueryManagerFactory implements QueryManagerFactory
{
@Override
public QueryManager getQueryManager(BasicCache<String, SessionData> cache)
{
System.err.println(cache.getClass().getName());
if (!RemoteCache.class.isAssignableFrom(cache.getClass()))
throw new IllegalArgumentException("Argument is not of type RemoteCache");
return new RemoteQueryManager((RemoteCache<String, SessionData>)cache);
}
}

View File

@ -0,0 +1,131 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.server.session.infinispan;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.annotation.ElementType;
import java.util.HashSet;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import org.eclipse.jetty.server.session.SessionData;
import org.eclipse.jetty.session.infinispan.QueryManager;
import org.eclipse.jetty.session.infinispan.RemoteQueryManager;
import org.eclipse.jetty.session.infinispan.SessionDataMarshaller;
import org.eclipse.jetty.util.IO;
import org.hibernate.search.cfg.Environment;
import org.hibernate.search.cfg.SearchMapping;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.client.hotrod.marshall.ProtoStreamMarshaller;
import org.infinispan.protostream.FileDescriptorSource;
import org.infinispan.protostream.SerializationContext;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class RemoteQueryManagerTest
{
public static final String DEFAULT_CACHE_NAME = "remote-session-test";
@Test
public void test() throws Exception
{
SearchMapping mapping = new SearchMapping();
mapping.entity(SessionData.class).indexed().providedId().property("expiry", ElementType.FIELD).field();
Properties properties = new Properties();
properties.put(Environment.MODEL_MAPPING, mapping);
ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder.withProperties(properties).addServer().host("127.0.0.1").marshaller(new ProtoStreamMarshaller());
RemoteCacheManager remoteCacheManager = new RemoteCacheManager(clientBuilder.build());
FileDescriptorSource fds = new FileDescriptorSource();
fds.addProtoFiles("/session.proto");
SerializationContext serCtx = ProtoStreamMarshaller.getSerializationContext(remoteCacheManager);
serCtx.registerProtoFiles(fds);
serCtx.registerMarshaller(new SessionDataMarshaller());
RemoteCache<String, SessionData> _cache = remoteCacheManager.getCache(DEFAULT_CACHE_NAME);
ByteArrayOutputStream baos;
try(InputStream is = RemoteQueryManagerTest.class.getClassLoader().getResourceAsStream("session.proto"))
{
if (is == null)
throw new IllegalStateException("inputstream is null");
baos = new ByteArrayOutputStream();
IO.copy(is, baos);
is.close();
}
String content = baos.toString("UTF-8");
remoteCacheManager.getCache("___protobuf_metadata").put("session.proto", content);
//put some sessions into the remote cache
int numSessions = 10;
long currentTime = 500;
int maxExpiryTime = 1000;
Set<String> expiredSessions = new HashSet<>();
Random r = new Random();
for(int i=0; i<numSessions; i++)
{
String id = "sd"+i;
//create new sessiondata with random expiry time
long expiryTime = r.nextInt(maxExpiryTime);
SessionData sd = new SessionData(id, "", "", 0, 0, 0, 0);
sd.setLastNode("lastNode");
sd.setExpiry(expiryTime);
//if this entry has expired add it to expiry list
if (expiryTime <= currentTime)
expiredSessions.add(id);
//add to cache
_cache.put(id, sd);
assertNotNull(_cache.get(id));
}
//run the query
QueryManager qm = new RemoteQueryManager(_cache);
Set<String> queryResult = qm.queryExpiredSessions(currentTime);
// Check that the result is correct
assertEquals(expiredSessions.size(), queryResult.size());
for(String s : expiredSessions)
{
assertTrue(queryResult.contains(s));
}
}
}

View File

@ -0,0 +1,44 @@
<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</groupId>
<artifactId>infinispan-parent</artifactId>
<version>10.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>infinispan-remote</artifactId>
<packaging>pom</packaging>
<name>Jetty :: Infinispan Session Manager Remote</name>
<url>http://www.eclipse.org/jetty</url>
<properties>
<bundle-symbolic-name>${project.groupId}.infinispan.remote</bundle-symbolic-name>
</properties>
<build>
<defaultGoal>install</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>config</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,76 @@
<?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">
<!-- ===================================================================== -->
<!-- Read hotrod-client.properties file -->
<!-- ===================================================================== -->
<New id="properties" class="java.util.Properties">
<Call name="load">
<Arg>
<New class="java.io.FileInputStream">
<Arg><Property name="jetty.base" default="."/>/resources/hotrod-client.properties</Arg>
</New>
</Arg>
</Call>
</New>
<!-- ===================================================================== -->
<!-- Convert properties to configuration -->
<!-- ===================================================================== -->
<New class="org.infinispan.client.hotrod.configuration.ConfigurationBuilder">
<Call name="withProperties">
<Arg><Ref refid="properties"/></Arg>
</Call>
<Call name="marshaller">
<Arg>
<New class="org.infinispan.client.hotrod.marshall.ProtoStreamMarshaller"/>
</Arg>
</Call>
<Call id="config" name="build"/>
</New>
<!-- ===================================================================== -->
<!-- Get a reference to the remote cache. -->
<!-- ===================================================================== -->
<New id="hotrodMgr" class="org.infinispan.client.hotrod.RemoteCacheManager">
<Arg><Ref refid="config"/></Arg>
</New>
<Call id="serial_context" class="org.infinispan.client.hotrod.marshall.ProtoStreamMarshaller" name="getSerializationContext">
<Arg>
<Ref refid="hotrodMgr"/>
</Arg>
<Call name="registerProtoFiles">
<Arg>
<New class="org.infinispan.protostream.FileDescriptorSource">
<Call name="addProtoFiles">
<Arg>
<Array type="java.lang.String">
<Item>/session.proto</Item>
</Array>
</Arg>
</Call>
</New>
</Arg>
</Call>
<Call name="registerMarshaller">
<Arg>
<New class="org.eclipse.jetty.session.infinispan.SessionDataMarshaller"/>
</Arg>
</Call>
</Call>
<Ref refid="hotrodMgr">
<Call id="cache" name="getCache">
<Arg><Property name="jetty.session.infinispan.remoteCacheName" default="sessions"/></Arg>
</Call>
</Ref>
<!-- set queryMgrFactory reference to NullQueryManagerFactory -->
<New id="queryMgrFactory" class="org.eclipse.jetty.session.infinispan.NullQueryManagerFactory"/>
</Configure>

View File

@ -0,0 +1,13 @@
[description]
Default setup for the remote infinispan cache without queries
[tags]
session
[provides]
infinispan-remote
[xml]
etc/sessions/infinispan/infinispan-remote.xml
etc/sessions/infinispan/infinispan-common.xml

View File

@ -1,5 +1,3 @@
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Enables session data store in a remote Infinispan cache
@ -8,30 +6,27 @@ session
[provides]
session-store
session-store-infinispan-remote
[depend]
sessions
infinispan-common
infinispan-remote
[files]
maven://org.infinispan/infinispan-remote/9.1.0.Final|lib/infinispan/infinispan-remote-9.1.0.Final.jar
basehome:modules/session-store-infinispan-remote/
maven://org.infinispan/infinispan-remote-it/${infinispan.version}|lib/infinispan/infinispan-remote-it-${infinispan.version}.jar
basehome:modules/session-store-infinispan-remote/resources/hotrod-client.properties|resources/hotrod-client.properties
[xml]
etc/sessions/infinispan/remote.xml
[ini]
infinispan.version?=9.4.8.Final
[lib]
lib/jetty-infinispan-${jetty.version}.jar
lib/infinispan/*.jar
[license]
Infinispan is an open source project hosted on Github and released under the Apache 2.0 license.
http://infinispan.org/
http://www.apache.org/licenses/LICENSE-2.0.html
[ini-template]
#jetty.session.infinispan.remoteCacheName=sessions
#jetty.session.infinispan.idleTimeout.seconds=0
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0

View File

@ -1,69 +1,26 @@
<?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</groupId>
<artifactId>jetty-project</artifactId>
<version>10.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-infinispan</artifactId>
<name>Jetty :: Infinispan Session Managers</name>
<url>http://www.eclipse.org/jetty</url>
<groupId>org.eclipse.jetty</groupId>
<artifactId>infinispan-parent</artifactId>
<packaging>pom</packaging>
<name>Jetty :: Infinispan</name>
<properties>
<bundle-symbolic-name>${project.groupId}.infinispan</bundle-symbolic-name>
</properties>
<build>
<defaultGoal>install</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>config</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId>
<version>${infinispan.version}</version>
</dependency>
<dependency>
<groupId>org.infinispan.protostream</groupId>
<artifactId>protostream</artifactId>
<version>4.2.2.Final</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-client-hotrod</artifactId>
<version>${infinispan.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-remote-query-client</artifactId>
<version>${infinispan.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<modules>
<module>infinispan-common</module>
<module>infinispan-embedded</module>
<module>infinispan-remote</module>
<module>infinispan-embedded-query</module>
<module>infinispan-remote-query</module>
</modules>
</project>

View File

@ -1,30 +0,0 @@
<?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">
<!-- ===================================================================== -->
<!-- Get a reference to the default local cache. -->
<!-- ===================================================================== -->
<New id="local" class="org.infinispan.manager.DefaultCacheManager">
<Arg><Property name="jetty.base" default="."/>/etc/infinispan-embedded.xml</Arg>
<Get id="cache" name="cache"></Get>
</New>
<!-- ===================================================================== -->
<!-- Configure a factory for InfinispanSessionDataStore using the -->
<!-- Infinispan DefaultCache -->
<!-- ===================================================================== -->
<Call name="addBean">
<Arg>
<New id="sessionDataStoreFactory" class="org.eclipse.jetty.session.infinispan.InfinispanSessionDataStoreFactory">
<Set name="cache"><Ref id="cache"/></Set>
<Set name="infinispanIdleTimeoutSec"><Property name="jetty.session.infinispan.idleTimeout.seconds" default="0" /></Set>
<Set name="gracePeriodSec"><Property name="jetty.session.gracePeriod.seconds" default="3600" /></Set>
<Set name="savePeriodSec"><Property name="jetty.session.savePeriod.seconds" default="0" /></Set>
</New>
</Arg>
</Call>
</Configure>

View File

@ -1,35 +0,0 @@
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Enables session data store in a local Infinispan cache
[tags]
session
[provides]
session-store
session-store-infinispan-embedded
[depend]
sessions
[files]
maven://org.infinispan/infinispan-embedded/9.1.0.Final|lib/infinispan/infinispan-embedded-9.1.0.Final.jar
basehome:modules/session-store-infinispan-embedded/infinispan-embedded.xml|etc/infinispan-embedded.xml
[xml]
etc/sessions/infinispan/default.xml
[lib]
lib/jetty-infinispan-${jetty.version}.jar
lib/infinispan/*.jar
[license]
Infinispan is an open source project hosted on Github and released under the Apache 2.0 license.
http://infinispan.org/
http://www.apache.org/licenses/LICENSE-2.0.html
[ini-template]
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0

View File

@ -1,35 +0,0 @@
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Enables session data store in a local Infinispan cache
[tags]
session
[provides]
session-store
session-store-infnispan-embedded
[depend]
sessions
[files]
maven://org.infinispan/infinispan-embedded/7.1.1.Final|lib/infinispan/infinispan-embedded-7.1.1.Final.jar
basehome:modules/session-store-infinispan-embedded/infinispan-embedded.xml|etc/infinispan-embedded.xml
[xml]
etc/sessions/infinispan/default.xml
[lib]
lib/jetty-infinispan-${jetty.version}.jar
lib/infinispan/*.jar
[license]
Infinispan is an open source project hosted on Github and released under the Apache 2.0 license.
http://infinispan.org/
http://www.apache.org/licenses/LICENSE-2.0.html
[ini-template]
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0

View File

@ -1,36 +0,0 @@
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Enables session data store in a remote Infinispan cache
[tags]
session
[provides]
session-store
[depend]
sessions
[files]
maven://org.infinispan/infinispan-remote/7.1.1.Final|lib/infinispan/infinispan-remote-7.1.1.Final.jar
basehome:modules/session-store-infinispan-remote/
[xml]
etc/sessions/infinispan/remote.xml
[lib]
lib/jetty-infinispan-${jetty.version}.jar
lib/infinispan/*.jar
[license]
Infinispan is an open source project hosted on Github and released under the Apache 2.0 license.
http://infinispan.org/
http://www.apache.org/licenses/LICENSE-2.0.html
[ini-template]
#jetty.session.infinispan.remoteCacheName=sessions
#jetty.session.infinispan.idleTimeout.seconds=0
#jetty.session.gracePeriod.seconds=3600
#jetty.session.savePeriod.seconds=0

View File

@ -18,10 +18,11 @@
package org.eclipse.jetty.server.handler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -32,12 +33,15 @@ import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.ByteArrayISO8859Writer;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import static java.nio.charset.StandardCharsets.UTF_8;
/* ------------------------------------------------------------ */
/** Default Handler.
@ -118,64 +122,87 @@ public class DefaultHandler extends AbstractHandler
}
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.setContentType(MimeTypes.Type.TEXT_HTML_8859_1.asString());
response.setContentType(MimeTypes.Type.TEXT_HTML_UTF_8.toString());
try (ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500);)
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(outputStream, UTF_8))
{
writer.write("<HTML>\n<HEAD>\n<TITLE>Error 404 - Not Found");
writer.write("</TITLE>\n<BODY>\n<H2>Error 404 - Not Found.</H2>\n");
writer.write("No context on this server matched or handled this request.<BR>");
writer.write("Contexts known to this server are: <ul>");
writer.append("<!DOCTYPE html>\n");
writer.append("<html lang=\"en\">\n<head>\n");
writer.append("<title>Error 404 - Not Found</title>\n");
writer.append("<meta charset=\"utf-8\">\n");
writer.append("<style>body { font-family: sans-serif; } table, td { border: 1px solid #333; } td, th { padding: 5px; } thead, tfoot { background-color: #333; color: #fff; } </style>\n");
writer.append("</head>\n<body>\n");
writer.append("<h2>Error 404 - Not Found.</h2>\n");
writer.append("<p>No context on this server matched or handled this request.</p>\n");
writer.append("<p>Contexts known to this server are:</p>\n");
Server server = getServer();
Handler[] handlers = server==null?null:server.getChildHandlersByClass(ContextHandler.class);
writer.append("<table class=\"contexts\"><thead><tr>");
writer.append("<th>Context Path</th>");
writer.append("<th>Display Name</th>");
writer.append("<th>Status</th>");
writer.append("<th>LifeCycle</th>");
writer.append("</tr></thead><tbody>\n");
for (int i=0;handlers!=null && i<handlers.length;i++)
{
writer.append("<tr><td>");
// Context Path
ContextHandler context = (ContextHandler)handlers[i];
String contextPath = context.getContextPath();
String href = URIUtil.encodePath(contextPath);
if (contextPath.length() > 1 && !contextPath.endsWith("/"))
{
href += '/';
}
if (context.isRunning())
{
writer.write("<li><a href=\"");
if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
writer.write(request.getScheme()+"://"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
writer.write(context.getContextPath());
if (context.getContextPath().length()>1 && context.getContextPath().endsWith("/"))
writer.write("/");
writer.write("\">");
writer.write(context.getContextPath());
if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
writer.write("&nbsp;@&nbsp;"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
writer.write("&nbsp;--->&nbsp;");
writer.write(context.toString());
writer.write("</a></li>\n");
writer.append("<a href=\"").append(href).append("\">");
}
writer.append(contextPath.replaceAll("%", "&#37;"));
if (context.isRunning())
{
writer.append("</a>");
}
writer.append("</td><td>");
// Display Name
if (StringUtil.isNotBlank(context.getDisplayName()))
{
writer.append(StringUtil.sanitizeXmlString(context.getDisplayName()));
}
writer.append("&nbsp;</td><td>");
// Available
if (context.isAvailable())
{
writer.append("Available");
}
else
{
writer.write("<li>");
writer.write(context.getContextPath());
if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
writer.write("&nbsp;@&nbsp;"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
writer.write("&nbsp;--->&nbsp;");
writer.write(context.toString());
if (context.isFailed())
writer.write(" [failed]");
if (context.isStopped())
writer.write(" [stopped]");
writer.write("</li>\n");
writer.append("<em>Not</em> Available");
}
writer.append("</td><td>");
// State
writer.append(context.getState());
writer.append("</td></tr>\n");
}
writer.write("</ul><hr>");
baseRequest.getHttpChannel().getHttpConfiguration()
.writePoweredBy(writer,"<a href=\"http://eclipse.org/jetty\"><img border=0 src=\"/favicon.ico\"/></a>&nbsp;","<hr/>\n");
writer.write("\n</BODY>\n</HTML>\n");
writer.append("</tbody></table><hr/>\n");
writer.append("<a href=\"http://eclipse.org/jetty\"><img alt=\"icon\" src=\"/favicon.ico\"/></a>&nbsp;");
writer.append("<a href=\"http://eclipse.org/jetty\">Powered by Eclipse Jetty:// Server</a><hr/>\n");
writer.append("</body>\n</html>\n");
writer.flush();
response.setContentLength(writer.size());
try (OutputStream out=response.getOutputStream())
byte content[] = outputStream.toByteArray();
response.setContentLength(content.length);
try (OutputStream out = response.getOutputStream())
{
writer.writeTo(out);
out.write(content);
}
}
}

View File

@ -0,0 +1,109 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.servlet;
import java.io.File;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.tools.HttpTester;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.resource.PathResource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
public class DefaultHandlerTest
{
private Server server;
private LocalConnector localConnector;
private File baseA;
private File baseFoo;
@BeforeEach
public void startServer() throws Exception
{
server = new Server();
localConnector = new LocalConnector(server);
server.addConnector(localConnector);
File docRoot = MavenTestingUtils.getTargetTestingDir(DefaultHandlerTest.class.getName());
FS.ensureDirExists(docRoot);
baseA = new File(docRoot, "baseA");
FS.ensureDirExists(baseA);
baseFoo = new File(docRoot, "baseFoo");
FS.ensureDirExists(baseFoo);
ServletContextHandler contextA = new ServletContextHandler();
contextA.setContextPath("/a");
contextA.setBaseResource(new PathResource(baseA));
ServletContextHandler contextFoo = new ServletContextHandler();
contextFoo.setContextPath("/foo");
contextFoo.setBaseResource(new PathResource(baseFoo));
HandlerList handlers = new HandlerList();
handlers.addHandler(contextA);
handlers.addHandler(contextFoo);
handlers.addHandler(new DefaultHandler());
server.setHandler(handlers);
server.start();
}
@AfterEach
public void stopServer() throws Exception
{
server.stop();
}
@Test
public void testNotRevealBaseResource() throws Exception
{
StringBuilder req = new StringBuilder();
req.append("GET / HTTP/1.0\r\n");
req.append("\r\n");
String rawResponse = localConnector.getResponse(req.toString());
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(HttpStatus.NOT_FOUND_404));
String body = response.getContent();
assertThat(body, containsString("No context on this server matched or handled this request"));
assertThat(body, containsString("Contexts known to this server are"));
assertThat(body, containsString("<a href=\"/a/\">"));
assertThat(body, containsString("<a href=\"/foo/\">"));
assertThat(body, not(containsString(baseA.toString())));
assertThat(body, not(containsString(baseFoo.toString())));
}
}

View File

@ -21,6 +21,8 @@ package org.eclipse.jetty.servlet;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@ -33,7 +35,6 @@ import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@ -52,12 +53,14 @@ import org.eclipse.jetty.http.tools.HttpTester;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.ResourceContentFactory;
import org.eclipse.jetty.server.ResourceService;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.util.resource.Resource;
import org.junit.jupiter.api.AfterEach;
@ -81,6 +84,7 @@ import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@ExtendWith(WorkDirExtension.class)
@ -90,6 +94,9 @@ public class DefaultServletTest
public Path docRoot;
// The name of the odd-jar used for testing "jar:file://" based resource access.
private static final String ODD_JAR = "jar-resource-odd.jar";
private Server server;
private LocalConnector connector;
private ServletContextHandler context;
@ -105,10 +112,17 @@ public class DefaultServletTest
connector = new LocalConnector(server);
connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
File extraJarResources = MavenTestingUtils.getTestResourceFile(ODD_JAR);
URL urls[] = new URL[] { extraJarResources.toURI().toURL() };
ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
URLClassLoader extraClassLoader = new URLClassLoader(urls, parentClassLoader);
context = new ServletContextHandler();
context.setBaseResource(new PathResource(docRoot));
context.setContextPath("/context");
context.setWelcomeFiles(new String[]{"index.html", "index.jsp", "index.htm"});
context.setClassLoader(extraClassLoader);
server.setHandler(context);
server.addConnector(connector);
@ -211,6 +225,136 @@ public class DefaultServletTest
assertThat(body, containsString("f??r"));
}
/**
* A regression on windows allowed the directory listing show
* the fully qualified paths within the directory listing.
* This test ensures that this behavior will not arise again.
*/
@Test
public void testListingFilenamesOnly() throws Exception
{
ServletHolder defholder = context.addServlet(DefaultServlet.class, "/*");
defholder.setInitParameter("dirAllowed", "true");
defholder.setInitParameter("redirectWelcome", "false");
defholder.setInitParameter("gzip", "false");
/* create some content in the docroot */
FS.ensureDirExists(docRoot);
Path one = docRoot.resolve("one");
FS.ensureDirExists(one);
Path deep = one.resolve("deep");
FS.ensureDirExists(deep);
FS.touch(deep.resolve("foo"));
FS.ensureDirExists(docRoot.resolve("two"));
FS.ensureDirExists(docRoot.resolve("three"));
String resBasePath = docRoot.toAbsolutePath().toString();
defholder.setInitParameter("resourceBase", resBasePath);
StringBuffer req1 = new StringBuffer();
req1.append("GET /context/one/deep/ HTTP/1.0\n");
req1.append("\n");
String rawResponse = connector.getResponse(req1.toString());
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(HttpStatus.OK_200));
String body = response.getContent();
assertThat(body, containsString("/foo"));
assertThat(body, not(containsString(resBasePath)));
}
/**
* A regression on windows allowed the directory listing show
* the fully qualified paths within the directory listing.
* This test ensures that this behavior will not arise again.
*/
@Test
public void testListingFilenamesOnly_UrlResource() throws Exception
{
URL extraResource = context.getClassLoader().getResource("rez/one");
assertNotNull(extraResource, "Must have extra jar resource in classloader");
String extraResourceBaseString = extraResource.toURI().toASCIIString();
extraResourceBaseString = extraResourceBaseString.substring(0, extraResourceBaseString.length() - "/one".length());
ServletHolder defholder = context.addServlet(DefaultServlet.class, "/extra/*");
defholder.setInitParameter("resourceBase", extraResourceBaseString);
defholder.setInitParameter("pathInfoOnly", "true");
defholder.setInitParameter("dirAllowed", "true");
defholder.setInitParameter("redirectWelcome", "false");
defholder.setInitParameter("gzip", "false");
StringBuffer req1;
String rawResponse;
HttpTester.Response response;
String body;
// Test that GET works first.
req1 = new StringBuffer();
req1.append("GET /context/extra/one HTTP/1.0\n");
req1.append("\n");
rawResponse = connector.getResponse(req1.toString());
response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(HttpStatus.OK_200));
body = response.getContent();
assertThat(body, containsString("is this the one?"));
// Typical directory listing of location in jar:file:// URL
req1 = new StringBuffer();
req1.append("GET /context/extra/deep/ HTTP/1.0\r\n");
req1.append("\r\n");
rawResponse = connector.getResponse(req1.toString());
response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(HttpStatus.OK_200));
body = response.getContent();
assertThat(body, containsString("/xxx"));
assertThat(body, containsString("/yyy"));
assertThat(body, containsString("/zzz"));
assertThat(body, not(containsString(extraResourceBaseString)));
assertThat(body, not(containsString(ODD_JAR)));
// Get deep resource
req1 = new StringBuffer();
req1.append("GET /context/extra/deep/yyy HTTP/1.0\r\n");
req1.append("\r\n");
rawResponse = connector.getResponse(req1.toString());
response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(HttpStatus.OK_200));
body = response.getContent();
assertThat(body, containsString("a file named yyy"));
// Convoluted directory listing of location in jar:file:// URL
// This exists to test proper encoding output
req1 = new StringBuffer();
req1.append("GET /context/extra/oddities/ HTTP/1.0\r\n");
req1.append("\r\n");
rawResponse = connector.getResponse(req1.toString());
response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(HttpStatus.OK_200));
body = response.getContent();
assertThat(body, containsString(">#hashcode&nbsp;<")); // text on page
assertThat(body, containsString("/oddities/%23hashcode")); // generated link
assertThat(body, containsString(">other%2fkind%2Fof%2fslash&nbsp;<")); // text on page
assertThat(body, containsString("/oddities/other%252fkind%252Fof%252fslash")); // generated link
assertThat(body, containsString(">a file with a space&nbsp;<")); // text on page
assertThat(body, containsString("/oddities/a%20file%20with%20a%20space")); // generated link
assertThat(body, not(containsString(extraResourceBaseString)));
assertThat(body, not(containsString(ODD_JAR)));
}
@Test
public void testListingProperUrlEncoding() throws Exception
{
@ -1744,11 +1888,13 @@ public class DefaultServletTest
ServletHolder defholder = context.addServlet(DefaultServlet.class, "/");
defholder.setInitParameter("resourceBase", docRoot.toFile().getAbsolutePath());
String rawResponse = connector.getResponse("GET /context/%0a HTTP/1.1\r\nHost: local\r\nConnection: close\r\n\r\n");
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
System.out.println(response + "\n" + response.getContent());
assertThat("Response.status", response.getStatus(), anyOf(is(HttpServletResponse.SC_NOT_FOUND), is(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)));
assertThat("Response.content", response.getContent(), is(not(containsString(docRoot.toString()))));
try (StacklessLogging ignore = new StacklessLogging(ResourceService.class))
{
String rawResponse = connector.getResponse("GET /context/%0a HTTP/1.1\r\nHost: local\r\nConnection: close\r\n\r\n");
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat("Response.status", response.getStatus(), anyOf(is(HttpServletResponse.SC_NOT_FOUND), is(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)));
assertThat("Response.content", response.getContent(), is(not(containsString(docRoot.toString()))));
}
}
@ParameterizedTest

Binary file not shown.

View File

@ -1,5 +1,5 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.LEVEL=INFO
org.eclipse.jetty.LEVEL=WARN
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.server.LEVEL=DEBUG
#org.eclipse.jetty.servlet.LEVEL=DEBUG

View File

@ -45,9 +45,11 @@ import org.eclipse.jetty.util.UrlEncoded;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import static java.nio.charset.StandardCharsets.UTF_8;
/* ------------------------------------------------------------ */
/**
/**
* Abstract resource class.
* <p>
* This class provides a resource abstraction, where a resource may be
@ -76,7 +78,7 @@ public abstract class Resource implements ResourceFactory, Closeable
{
return __defaultUseCaches;
}
/* ------------------------------------------------------------ */
/** Construct a resource from a uri.
* @param uri A URI.
@ -88,7 +90,7 @@ public abstract class Resource implements ResourceFactory, Closeable
{
return newResource(uri.toURL());
}
/* ------------------------------------------------------------ */
/** Construct a resource from a url.
* @param url A URL.
@ -98,8 +100,8 @@ public abstract class Resource implements ResourceFactory, Closeable
{
return newResource(url, __defaultUseCaches);
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/**
* Construct a resource from a url.
* @param url the url for which to make the resource
@ -137,8 +139,8 @@ public abstract class Resource implements ResourceFactory, Closeable
return new URLResource(url,null,useCaches);
}
/* ------------------------------------------------------------ */
/** Construct a resource from a string.
* @param resource A URL or filename.
@ -149,7 +151,7 @@ public abstract class Resource implements ResourceFactory, Closeable
{
return newResource(resource, __defaultUseCaches);
}
/* ------------------------------------------------------------ */
/** Construct a resource from a string.
* @param resource A URL or filename.
@ -218,7 +220,7 @@ public abstract class Resource implements ResourceFactory, Closeable
/** Construct a system resource from a string.
* The resource is tried as classloader resource before being
* treated as a normal resource.
* @param resource Resource as string representation
* @param resource Resource as string representation
* @return The new Resource
* @throws IOException Problem accessing resource.
*/
@ -254,17 +256,17 @@ public abstract class Resource implements ResourceFactory, Closeable
url=loader.getResource(resource.substring(1));
}
}
if (url==null)
{
url=ClassLoader.getSystemResource(resource);
if (url==null && resource.startsWith("/"))
url=ClassLoader.getSystemResource(resource.substring(1));
}
if (url==null)
return null;
return newResource(url);
}
@ -286,21 +288,21 @@ public abstract class Resource implements ResourceFactory, Closeable
* Unlike {@link ClassLoader#getSystemResource(String)} this method does not check for normal resources.
* @param name The relative name of the resource
* @param useCaches True if URL caches are to be used.
* @param checkParents True if forced searching of parent Classloaders is performed to work around
* @param checkParents True if forced searching of parent Classloaders is performed to work around
* loaders with inverted priorities
* @return Resource or null
*/
public static Resource newClassPathResource(String name,boolean useCaches,boolean checkParents)
{
URL url=Resource.class.getResource(name);
if (url==null)
url=Loader.getResource(name);
if (url==null)
return null;
return newResource(url,useCaches);
}
/* ------------------------------------------------------------ */
public static boolean isContainedIn (Resource r, Resource containingResource) throws MalformedURLException
{
@ -309,7 +311,7 @@ public abstract class Resource implements ResourceFactory, Closeable
/* ------------------------------------------------------------ */
public abstract boolean isContainedIn (Resource r) throws MalformedURLException;
/* ------------------------------------------------------------ */
/** Release any temporary resources held by the resource.
*/
@ -321,7 +323,7 @@ public abstract class Resource implements ResourceFactory, Closeable
* @return true if the represented resource exists.
*/
public abstract boolean exists();
/* ------------------------------------------------------------ */
/**
@ -334,7 +336,7 @@ public abstract class Resource implements ResourceFactory, Closeable
/* ------------------------------------------------------------ */
/**
* Time resource was last modified.
*
*
* @return the last modified time as milliseconds since unix epoch
*/
public abstract long lastModified();
@ -343,15 +345,15 @@ public abstract class Resource implements ResourceFactory, Closeable
/* ------------------------------------------------------------ */
/**
* Length of the resource.
*
*
* @return the length of the resource
*/
public abstract long length();
/* ------------------------------------------------------------ */
/**
* URI representing the resource.
*
*
* @return an URI representing the given resource
*/
public abstract URI getURI();
@ -359,38 +361,38 @@ public abstract class Resource implements ResourceFactory, Closeable
/* ------------------------------------------------------------ */
/**
* File representing the given resource.
*
*
* @return an File representing the given resource or NULL if this
* is not possible.
* @throws IOException if unable to get the resource due to permissions
* @throws IOException if unable to get the resource due to permissions
*/
public abstract File getFile()
throws IOException;
/* ------------------------------------------------------------ */
/**
* The name of the resource.
*
*
* @return the name of the resource
*/
public abstract String getName();
/* ------------------------------------------------------------ */
/**
* Input stream to the resource
*
*
* @return an input stream to the resource
* @throws IOException if unable to open the input stream
*/
public abstract InputStream getInputStream()
throws IOException;
/* ------------------------------------------------------------ */
/**
* Readable ByteChannel for the resource.
*
*
* @return an readable bytechannel to the resource or null if one is not available.
* @throws IOException if unable to open the readable bytechannel for the resource.
*/
@ -402,11 +404,11 @@ public abstract class Resource implements ResourceFactory, Closeable
* Deletes the given resource
* @return true if resource was found and successfully deleted, false if resource didn't exist or was unable to
* be deleted.
* @throws SecurityException if unable to delete due to permissions
* @throws SecurityException if unable to delete due to permissions
*/
public abstract boolean delete()
throws SecurityException;
/* ------------------------------------------------------------ */
/**
* Rename the given resource
@ -416,7 +418,7 @@ public abstract class Resource implements ResourceFactory, Closeable
*/
public abstract boolean renameTo(Resource dest)
throws SecurityException;
/* ------------------------------------------------------------ */
/**
* list of resource names contained in the given resource.
@ -482,7 +484,7 @@ public abstract class Resource implements ResourceFactory, Closeable
{
return getAlias()!=null;
}
/* ------------------------------------------------------------ */
/**
* @return The canonical Alias of this resource or null if none.
@ -491,7 +493,7 @@ public abstract class Resource implements ResourceFactory, Closeable
{
return null;
}
/* ------------------------------------------------------------ */
/** Get the resource list as a HTML directory listing.
* @param base The base URL
@ -671,7 +673,7 @@ public abstract class Resource implements ResourceFactory, Closeable
buf.append("<tbody>\n");
String encodedBase = hrefEncodeURI(base);
if (parent)
{
// Name
@ -689,12 +691,12 @@ public abstract class Resource implements ResourceFactory, Closeable
DateFormat.MEDIUM);
for (Resource item: items)
{
String name = item.getName();
int slashIdx = name.lastIndexOf('/');
if (slashIdx != -1)
String name = item.getFileName();
if (StringUtil.isBlank(name))
{
name = name.substring(slashIdx + 1);
continue; // skip
}
if (item.isDirectory() && !name.endsWith("/"))
{
name += URIUtil.SLASH;
@ -711,13 +713,21 @@ public abstract class Resource implements ResourceFactory, Closeable
// Last Modified
buf.append("<td class=\"lastmodified\">");
buf.append(dfmt.format(new Date(item.lastModified())));
buf.append("</td>");
long lastModified = item.lastModified();
if (lastModified > 0)
{
buf.append(dfmt.format(new Date(item.lastModified())));
}
buf.append("&nbsp;</td>");
// Size
buf.append("<td class=\"size\">");
buf.append(String.format("%,d", item.length()));
buf.append(" bytes&nbsp;</td></tr>\n");
long length = item.length();
if (length >= 0)
{
buf.append(String.format("%,d bytes", item.length()));
}
buf.append("&nbsp;</td></tr>\n");
}
buf.append("</tbody>\n");
buf.append("</table>\n");
@ -725,7 +735,56 @@ public abstract class Resource implements ResourceFactory, Closeable
return buf.toString();
}
/**
* Get the raw (decoded if possible) Filename for this Resource.
* This is the last segment of the path.
* @return the raw / decoded filename for this resource
*/
private String getFileName()
{
try
{
// if a Resource supports File
File file = getFile();
if (file != null)
{
return file.getName();
}
}
catch (Throwable ignore)
{
}
// All others use raw getName
try
{
String rawName = getName(); // gets long name "/foo/bar/xxx"
int idx = rawName.lastIndexOf('/');
if (idx == rawName.length()-1)
{
// hit a tail slash, aka a name for a directory "/foo/bar/"
idx = rawName.lastIndexOf('/', idx-1);
}
String encodedFileName;
if (idx >= 0)
{
encodedFileName = rawName.substring(idx + 1);
}
else
{
encodedFileName = rawName; // entire name
}
return UrlEncoded.decodeString(encodedFileName, 0, encodedFileName.length(), UTF_8);
}
catch (Throwable ignore)
{
}
return null;
}
/**
* Encode any characters that could break the URI string in an HREF.
* Such as <a href="/path/to;<script>Window.alert("XSS"+'%20'+"here");</script>">Link</a>
@ -790,7 +849,7 @@ public abstract class Resource implements ResourceFactory, Closeable
/* ------------------------------------------------------------ */
/**
* @param out the output stream to write to
* @param out the output stream to write to
* @param start First byte to write
* @param count Bytes to write or -1 for all of them.
* @throws IOException if unable to copy the Resource to the output
@ -813,7 +872,7 @@ public abstract class Resource implements ResourceFactory, Closeable
* Copy the Resource to the new destination file.
* <p>
* Will not replace existing destination file.
*
*
* @param destination the destination file to create
* @throws IOException if unable to copy the resource
*/
@ -822,7 +881,7 @@ public abstract class Resource implements ResourceFactory, Closeable
{
if (destination.exists())
throw new IllegalArgumentException(destination + " exists");
try (OutputStream out = new FileOutputStream(destination))
{
writeTo(out,0,-1);
@ -832,14 +891,14 @@ public abstract class Resource implements ResourceFactory, Closeable
/* ------------------------------------------------------------ */
/**
* Generate a weak ETag reference for this Resource.
*
*
* @return the weak ETag reference for this resource.
*/
public String getWeakETag()
{
return getWeakETag("");
}
public String getWeakETag(String suffix)
{
try

View File

@ -53,6 +53,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<forkedProcessExitTimeoutInSeconds>45</forkedProcessExitTimeoutInSeconds>
<forkedProcessTimeoutInSeconds>240</forkedProcessTimeoutInSeconds>
<systemPropertyVariables>
<java.util.logging.config.file>${project.build.testOutputDirectory}/logging.properties</java.util.logging.config.file>
</systemPropertyVariables>

View File

@ -16,11 +16,8 @@
// ========================================================================
//
package org.eclipse.jetty.hazelcast.session;
import static org.junit.jupiter.api.Assertions.fail;
import org.eclipse.jetty.server.session.AbstractSessionDataStoreFactory;
import org.eclipse.jetty.server.session.AbstractSessionDataStoreTest;
import org.eclipse.jetty.server.session.SessionContext;
@ -31,6 +28,9 @@ import org.eclipse.jetty.server.session.UnreadableSessionDataException;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.fail;
/**
* HazelcastSessionDataStoreTest
@ -39,16 +39,15 @@ import org.junit.jupiter.api.BeforeEach;
*/
public class HazelcastSessionDataStoreTest extends AbstractSessionDataStoreTest
{
HazelcastTestHelper _testHelper;
@Override
public SessionDataStoreFactory createSessionDataStoreFactory()
{
{
return _testHelper.createSessionDataStoreFactory(false);
}
@BeforeEach
public void setUp()
{
@ -61,58 +60,91 @@ public class HazelcastSessionDataStoreTest extends AbstractSessionDataStoreTest
_testHelper.tearDown();
}
@Override
public void persistSession(SessionData data) throws Exception
{
_testHelper.createSession(data);
}
@Override
public void persistUnreadableSession(SessionData data) throws Exception
{
//not used by testLoadSessionFails()
// not used by testLoadSessionFails()
}
@Override
public boolean checkSessionExists(SessionData data) throws Exception
{
return _testHelper.checkSessionExists(data);
}
@Test
@Override
public void testGetExpiredDifferentNode() throws Exception
{
//This test will not work for hazelcast because we can't enable
//HazelcastSessionDataStore.setScavengeZombieSessions, as it's
//too difficult to get the required classes onto the embedded
//hazelcast instance: these classes are required to handle
//the serialization/deserialization that hazelcast performs when querying
//to find zombie sessions.
}
/**
@Test
@Override
public void testGetExpiredPersistedAndExpiredOnly() throws Exception
{
//This test will not work for hazelcast because we can't enable
//HazelcastSessionDataStore.setScavengeZombieSessions, as it's
//too difficult to get the required classes onto the embedded
//hazelcast instance: these classes are required to handle
//the serialization/deserialization that hazelcast performs when querying
//to find zombie sessions.
}
@Override
public void testStoreSession() throws Exception
{
//This test will not work for hazelcast because we can't enable
//HazelcastSessionDataStore.setScavengeZombieSessions, as it's
//too difficult to get the required classes onto the embedded
//hazelcast instance: these classes are required to handle
//the serialization/deserialization that hazelcast performs when querying
//to find zombie sessions.
}
/**
*
* This test deliberately sets the sessionDataMap to null
* for the HazelcastSessionDataStore to provoke an exception
* in the load() method.
* This test deliberately sets the sessionDataMap to null for the
* HazelcastSessionDataStore to provoke an exception in the load() method.
*/
@Override
@Test
public void testLoadSessionFails() throws Exception
{
//create the SessionDataStore
// create the SessionDataStore
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/test");
context.setContextPath("/test");
SessionDataStoreFactory factory = createSessionDataStoreFactory();
((AbstractSessionDataStoreFactory)factory).setGracePeriodSec(GRACE_PERIOD_SEC);
((AbstractSessionDataStoreFactory) factory).setGracePeriodSec(GRACE_PERIOD_SEC);
SessionDataStore store = factory.getSessionDataStore(context.getSessionHandler());
SessionContext sessionContext = new SessionContext("foo", context.getServletContext());
store.initialize(sessionContext);
//persist a session
// persist a session
long now = System.currentTimeMillis();
SessionData data = store.newSessionData("222", 100, now, now-1, -1);
SessionData data = store.newSessionData("222", 100, now, now - 1, -1);
data.setLastNode(sessionContext.getWorkerName());
persistSession(data);
store.start();
((HazelcastSessionDataStore)store).setSessionDataMap(null);
//test that loading it fails
store.start();
((HazelcastSessionDataStore) store).setSessionDataMap(null);
// test that loading it fails
try
{
store.load("222");
@ -120,34 +152,9 @@ public class HazelcastSessionDataStoreTest extends AbstractSessionDataStoreTest
}
catch (UnreadableSessionDataException e)
{
//expected exception
// expected exception
}
}
/**
* This test currently won't work for Hazelcast - there is currently no
* means to query it to find sessions that have expired.
*
*/
@Override
public void testGetExpiredPersistedAndExpiredOnly() throws Exception
{
//ignore
}
/**
* This test currently won't work for Hazelcast - there is currently no
* means to query it to find sessions that have expired.
*/
@Override
public void testGetExpiredDifferentNode() throws Exception
{
//ignore
}
@Override
public boolean checkSessionPersisted(SessionData data) throws Exception

View File

@ -19,23 +19,25 @@
package org.eclipse.jetty.hazelcast.session;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import com.hazelcast.client.HazelcastClient;
import com.hazelcast.client.config.ClientConfig;
import com.hazelcast.client.config.ClientNetworkConfig;
import com.hazelcast.config.Config;
import com.hazelcast.config.JoinConfig;
import com.hazelcast.config.MapConfig;
import com.hazelcast.config.MulticastConfig;
import com.hazelcast.config.NetworkConfig;
import com.hazelcast.config.SerializerConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import org.eclipse.jetty.server.session.SessionData;
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
import com.hazelcast.config.Config;
import com.hazelcast.config.MapConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* HazelcastTestHelper
@ -73,8 +75,22 @@ public class HazelcastTestHelper
HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
factory.setOnlyClient( onlyClient );
factory.setMapName(_name);
factory.setHazelcastInstance(_instance);
if(onlyClient){
ClientNetworkConfig clientNetworkConfig = new ClientNetworkConfig()
.setAddresses(Collections.singletonList("localhost:"+_instance.getConfig().getNetworkConfig().getPort()));
ClientConfig clientConfig = new ClientConfig()
.setNetworkConfig(clientNetworkConfig);
SerializerConfig sc = new SerializerConfig().
setImplementation(new SessionDataSerializer()).
setTypeClass(SessionData.class);
clientConfig.getSerializationConfig().addSerializerConfig(sc);
factory.setHazelcastInstance(HazelcastClient.newHazelcastClient(clientConfig));
} else {
factory.setHazelcastInstance(_instance);
}
return factory;
}

View File

@ -0,0 +1,164 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.HazelcastSessionDataStore;
import org.eclipse.jetty.hazelcast.session.HazelcastTestHelper;
import org.eclipse.jetty.server.session.AbstractSessionDataStoreFactory;
import org.eclipse.jetty.server.session.AbstractSessionDataStoreTest;
import org.eclipse.jetty.server.session.SessionContext;
import org.eclipse.jetty.server.session.SessionData;
import org.eclipse.jetty.server.session.SessionDataStore;
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
import org.eclipse.jetty.server.session.UnreadableSessionDataException;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.fail;
/**
* HazelcastSessionDataStoreTest
*
*
*/
public class HazelcastSessionDataStoreTest extends AbstractSessionDataStoreTest
{
HazelcastTestHelper _testHelper;
@Override
public SessionDataStoreFactory createSessionDataStoreFactory()
{
return _testHelper.createSessionDataStoreFactory(true);
}
@BeforeEach
public void setUp()
{
_testHelper = new HazelcastTestHelper();
}
@AfterEach
public void shutdown()
{
_testHelper.tearDown();
}
@Override
public void persistSession(SessionData data) throws Exception
{
_testHelper.createSession(data);
}
@Override
public void persistUnreadableSession(SessionData data) throws Exception
{
// not used by testLoadSessionFails()
}
@Override
public boolean checkSessionExists(SessionData data) throws Exception
{
return _testHelper.checkSessionExists(data);
}
@Test
@Override
public void testGetExpiredDifferentNode() throws Exception
{
//This test will not work for hazelcast because we can't enable
//HazelcastSessionDataStore.setScavengeZombieSessions, as it's
//too difficult to get the required classes onto the embedded
//hazelcast instance: these classes are required to handle
//the serialization/deserialization that hazelcast performs when querying
//to find zombie sessions.
}
@Test
@Override
public void testGetExpiredPersistedAndExpiredOnly() throws Exception
{
//This test will not work for hazelcast because we can't enable
//HazelcastSessionDataStore.setScavengeZombieSessions, as it's
//too difficult to get the required classes onto the embedded
//hazelcast instance: these classes are required to handle
//the serialization/deserialization that hazelcast performs when querying
//to find zombie sessions.
}
@Override
public void testStoreSession() throws Exception
{
//This test will not work for hazelcast because we can't enable
//HazelcastSessionDataStore.setScavengeZombieSessions, as it's
//too difficult to get the required classes onto the embedded
//hazelcast instance: these classes are required to handle
//the serialization/deserialization that hazelcast performs when querying
//to find zombie sessions.
}
/**
*
* This test deliberately sets the sessionDataMap to null for the
* HazelcastSessionDataStore to provoke an exception in the load() method.
*/
@Override
@Test
public void testLoadSessionFails() throws Exception
{
// create the SessionDataStore
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/test");
SessionDataStoreFactory factory = createSessionDataStoreFactory();
((AbstractSessionDataStoreFactory) factory).setGracePeriodSec(GRACE_PERIOD_SEC);
SessionDataStore store = factory.getSessionDataStore(context.getSessionHandler());
SessionContext sessionContext = new SessionContext("foo", context.getServletContext());
store.initialize(sessionContext);
// persist a session
long now = System.currentTimeMillis();
SessionData data = store.newSessionData("222", 100, now, now - 1, -1);
data.setLastNode(sessionContext.getWorkerName());
persistSession(data);
store.start();
((HazelcastSessionDataStore) store).setSessionDataMap(null);
// test that loading it fails
try
{
store.load("222");
fail("Session should be unreadable");
}
catch (UnreadableSessionDataException e)
{
// expected exception
}
}
@Override
public boolean checkSessionPersisted(SessionData data) throws Exception
{
return _testHelper.checkSessionPersisted(data);
}
}

View File

@ -83,14 +83,8 @@
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-infinispan</artifactId>
<artifactId>infinispan-remote-query</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
@ -109,6 +103,12 @@
<version>${infinispan.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-query</artifactId>
<version>${infinispan.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-client-hotrod</artifactId>
@ -121,6 +121,18 @@
<version>${infinispan.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.infinispan.protostream</groupId>
<artifactId>protostream</artifactId>
<version>4.2.2.Final</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<!-- to test hotrod, configure a cache called "remote-session-test" -->

View File

@ -19,13 +19,23 @@
package org.eclipse.jetty.server.session;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.session.infinispan.InfinispanSessionDataStore;
import org.eclipse.jetty.session.infinispan.InfinispanSessionDataStoreFactory;
import org.infinispan.Cache;
import org.infinispan.query.Search;
import org.infinispan.query.dsl.Query;
import org.infinispan.query.dsl.QueryFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
/**
* InfinispanSessionDataStoreTest
@ -164,4 +174,32 @@ public class InfinispanSessionDataStoreTest extends AbstractSessionDataStoreTest
}
}
@Test
public void testQuery() throws Exception
{
Cache<String, SessionData> cache = __testSupport.getCache();
SessionData sd1 = new SessionData("sd1", "", "", 0, 0, 0, 0);
SessionData sd2 = new SessionData("sd2", "", "", 0, 0, 0, 1000);
sd2.setExpiry(100L); //long ago
SessionData sd3 = new SessionData("sd3", "", "", 0, 0, 0, 0);
cache.put("session1", sd1);
cache.put("session2", sd2);
cache.put("session3", sd3);
QueryFactory qf = Search.getQueryFactory(cache);
Query q = qf.from(SessionData.class).select("id").having("expiry").lte(System.currentTimeMillis()).and().having("expiry").gt(0).toBuilder().build();
List<Object[]> list = q.list();
List<String> ids = new ArrayList<>();
for(Object[] sl : list)
ids.add((String)sl[0]);
assertFalse(ids.isEmpty());
assertTrue(1==ids.size());
assertTrue(ids.contains("sd2"));
}
}

View File

@ -19,19 +19,25 @@
package org.eclipse.jetty.server.session;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.File;
import java.lang.annotation.ElementType;
import java.util.Properties;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.hibernate.search.cfg.Environment;
import org.hibernate.search.cfg.SearchMapping;
import org.infinispan.Cache;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.cache.Index;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* InfinispanTestSupport
*
@ -89,19 +95,44 @@ public class InfinispanTestSupport
public void setup () throws Exception
{
if (_useFileStore)
{
_tmpdir = File.createTempFile("infini", "span");
_tmpdir.delete();
_tmpdir.mkdir();
Configuration config = _builder.persistence().addSingleFileStore().location(_tmpdir.getAbsolutePath()).storeAsBinary().build();
_manager.defineConfiguration(_name, config);
}
else
{
_manager.defineConfiguration(_name, _builder.build());
}
_cache = _manager.getCache(_name);
File testdir = MavenTestingUtils.getTargetTestingDir();
File tmp = new File (testdir, "indexes");
IO.delete(tmp);
tmp.mkdirs();
SearchMapping mapping = new SearchMapping();
mapping.entity(SessionData.class).indexed().providedId().property("expiry", ElementType.FIELD).field();
Properties properties = new Properties();
properties.put(Environment.MODEL_MAPPING, mapping);
properties.put("hibernate.search.default.indexBase", tmp.getAbsolutePath());
if (_useFileStore)
{
_tmpdir = File.createTempFile("infini", "span");
_tmpdir.delete();
_tmpdir.mkdir();
Configuration config = _builder.indexing()
.index(Index.ALL)
.addIndexedEntity(SessionData.class)
.withProperties(properties)
.persistence()
.addSingleFileStore()
.location(_tmpdir.getAbsolutePath())
.storeAsBinary()
.build();
_manager.defineConfiguration(_name, config);
}
else
{
_manager.defineConfiguration(_name, _builder.indexing()
.withProperties(properties)
.index(Index.ALL)
.addIndexedEntity(SessionData.class)
.build());
}
_cache = _manager.getCache(_name);
}

View File

@ -25,6 +25,7 @@ import org.eclipse.jetty.session.infinispan.InfinispanSessionDataStoreFactory;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
/**
* ClusteredSessionScavengingTest
*

View File

@ -19,8 +19,6 @@
package org.eclipse.jetty.server.session.remote;
import static org.junit.jupiter.api.Assertions.fail;
import org.eclipse.jetty.server.session.AbstractSessionDataStoreFactory;
import org.eclipse.jetty.server.session.AbstractSessionDataStoreTest;
import org.eclipse.jetty.server.session.SessionContext;
@ -29,10 +27,19 @@ import org.eclipse.jetty.server.session.SessionDataStore;
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
import org.eclipse.jetty.server.session.UnreadableSessionDataException;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.session.infinispan.InfinispanSessionData;
import org.eclipse.jetty.session.infinispan.InfinispanSessionDataStore;
import org.eclipse.jetty.session.infinispan.InfinispanSessionDataStoreFactory;
import org.eclipse.jetty.session.infinispan.RemoteQueryManager;
import org.infinispan.client.hotrod.Search;
import org.infinispan.query.dsl.Query;
import org.infinispan.query.dsl.QueryFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
/**
* RemoteInfinispanSessionDataStoreTest
@ -67,6 +74,7 @@ public class RemoteInfinispanSessionDataStoreTest extends AbstractSessionDataSto
{
InfinispanSessionDataStoreFactory factory = new InfinispanSessionDataStoreFactory();
factory.setCache(__testSupport.getCache());
factory.setQueryManager(new RemoteQueryManager(__testSupport.getCache()));
return factory;
}
@ -110,29 +118,6 @@ public class RemoteInfinispanSessionDataStoreTest extends AbstractSessionDataSto
/**
* This test currently won't work for Infinispan - there is currently no
* means to query it to find sessions that have expired.
*
* @see org.eclipse.jetty.server.session.AbstractSessionDataStoreTest#testGetExpiredPersistedAndExpiredOnly()
*/
@Override
public void testGetExpiredPersistedAndExpiredOnly() throws Exception
{
}
/**
* This test won't work for Infinispan - there is currently no
* means to query infinispan to find other expired sessions.
*/
@Override
public void testGetExpiredDifferentNode() throws Exception
{
//Ignore
}
/**
@ -175,4 +160,32 @@ public class RemoteInfinispanSessionDataStoreTest extends AbstractSessionDataSto
}
}
@Test
public void testQuery() throws Exception
{
InfinispanSessionData sd1 = new InfinispanSessionData("sd1", "", "", 0, 0, 0, 1000);
sd1.setLastNode("fred1");
__testSupport.getCache().put("session1", sd1);
InfinispanSessionData sd2 = new InfinispanSessionData("sd2", "", "", 0, 0, 0, 2000);
sd2.setLastNode("fred2");
__testSupport.getCache().put("session2", sd2);
InfinispanSessionData sd3 = new InfinispanSessionData("sd3", "", "", 0, 0, 0, 3000);
sd3.setLastNode("fred3");
__testSupport.getCache().put("session3", sd3);
QueryFactory qf = Search.getQueryFactory(__testSupport.getCache());
for(int i=0; i<=3; i++)
{
long now = System.currentTimeMillis();
Query q = qf.from(InfinispanSessionData.class).having("expiry").lt(now).build();
assertEquals(i, q.list().size());
Thread.sleep(1000);
}
}
}

View File

@ -19,22 +19,28 @@
package org.eclipse.jetty.server.session.remote;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.annotation.ElementType;
import java.util.Properties;
import org.eclipse.jetty.server.session.SessionData;
import org.eclipse.jetty.session.infinispan.InfinispanSessionData;
import org.eclipse.jetty.session.infinispan.SessionDataMarshaller;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.eclipse.jetty.util.IO;
import org.hibernate.search.cfg.Environment;
import org.hibernate.search.cfg.SearchMapping;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.client.hotrod.marshall.ProtoStreamMarshaller;
import org.infinispan.protostream.FileDescriptorSource;
import org.infinispan.protostream.SerializationContext;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
/**
@ -45,7 +51,7 @@ import org.infinispan.protostream.SerializationContext;
public class RemoteInfinispanTestSupport
{
public static final String DEFAULT_CACHE_NAME = "session_test_cache";
public RemoteCache<String,Object> _cache;
public RemoteCache<String,SessionData> _cache;
private String _name;
public static RemoteCacheManager _manager;
@ -54,8 +60,14 @@ public class RemoteInfinispanTestSupport
try
{
String host = System.getProperty("hotrod.host","127.0.0.1");
SearchMapping mapping = new SearchMapping();
mapping.entity(SessionData.class).indexed().providedId()
.property("expiry", ElementType.METHOD).field();
Properties properties = new Properties();
properties.put(Environment.MODEL_MAPPING, mapping);
ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
clientBuilder.withProperties(properties).addServer().host(host).marshaller(new ProtoStreamMarshaller());
@ -67,6 +79,19 @@ public class RemoteInfinispanTestSupport
SerializationContext serCtx = ProtoStreamMarshaller.getSerializationContext(_manager);
serCtx.registerProtoFiles(fds);
serCtx.registerMarshaller(new SessionDataMarshaller());
ByteArrayOutputStream baos;
try(InputStream is = RemoteInfinispanSessionDataStoreTest.class.getClassLoader().getResourceAsStream("session.proto"))
{
if (is == null)
throw new IllegalStateException("inputstream is null");
baos = new ByteArrayOutputStream();
IO.copy(is, baos);
}
String content = baos.toString("UTF-8");
_manager.getCache("___protobuf_metadata").put("session.proto", content);
}
catch (Exception e)
{
@ -89,7 +114,7 @@ public class RemoteInfinispanTestSupport
public RemoteCache<String,Object> getCache ()
public RemoteCache<String,SessionData> getCache ()
{
return _cache;
}