Remove GemFire
Fixes gh-768
This commit is contained in:
@@ -1,70 +0,0 @@
|
|||||||
package build;
|
|
||||||
|
|
||||||
import org.gradle.api.plugins.JavaPlugin
|
|
||||||
import org.gradle.api.tasks.bundling.Zip
|
|
||||||
import org.gradle.api.DefaultTask
|
|
||||||
import org.gradle.api.Plugin
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.api.tasks.TaskAction
|
|
||||||
|
|
||||||
public class GemFireServerPlugin implements Plugin<Project> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Project project) {
|
|
||||||
project.tasks.create('gemFireServer', GemFireServerTask)
|
|
||||||
|
|
||||||
project.tasks.integrationTest.doLast {
|
|
||||||
println 'Stopping GemFire Server...'
|
|
||||||
project.tasks.gemFireServer.process?.destroyForcibly()
|
|
||||||
}
|
|
||||||
|
|
||||||
project.tasks.prepareAppServerForIntegrationTests {
|
|
||||||
dependsOn project.tasks.gemFireServer
|
|
||||||
doFirst {
|
|
||||||
project.gretty {
|
|
||||||
jvmArgs = ["-Dspring.session.data.gemfire.port=${project.tasks.gemFireServer.port}"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int availablePort() {
|
|
||||||
new ServerSocket(0).withCloseable { socket ->
|
|
||||||
socket.localPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class GemFireServerTask extends DefaultTask {
|
|
||||||
def mainClassName = "sample.ServerConfig"
|
|
||||||
def process
|
|
||||||
def port
|
|
||||||
boolean debug
|
|
||||||
|
|
||||||
@TaskAction
|
|
||||||
def greet() {
|
|
||||||
port = availablePort()
|
|
||||||
println "Starting GemFire Server on port [$port]..."
|
|
||||||
|
|
||||||
def out = debug ? System.out : new StringBuilder()
|
|
||||||
def err = debug ? System.out : new StringBuilder()
|
|
||||||
|
|
||||||
String classpath = project.sourceSets.main.runtimeClasspath.collect { it }.join(File.pathSeparator)
|
|
||||||
String gemfireLogLevel = System.getProperty('spring.session.data.gemfire.log-level', 'warning')
|
|
||||||
|
|
||||||
String[] commandLine = [
|
|
||||||
'java', '-server', '-ea', '-classpath', classpath,
|
|
||||||
//"-Dgemfire.log-file=gemfire-server.log",
|
|
||||||
//"-Dgemfire.log-level=config",
|
|
||||||
"-Dspring.session.data.gemfire.log-level=" + gemfireLogLevel,
|
|
||||||
"-Dspring.session.data.gemfire.port=${port}",
|
|
||||||
'sample.ServerConfig'
|
|
||||||
]
|
|
||||||
|
|
||||||
//println commandLine
|
|
||||||
|
|
||||||
project.tasks.appRun.ext.process = process = commandLine.execute()
|
|
||||||
|
|
||||||
process.consumeProcessOutput(out, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
implementation-class=build.GemFireServerPlugin
|
|
||||||
@@ -3,7 +3,6 @@ apply plugin: 'io.spring.convention.spring-test'
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testCompile project(':spring-session')
|
testCompile project(':spring-session')
|
||||||
testCompile project(':spring-session-data-gemfire')
|
|
||||||
testCompile project(':spring-session-data-mongo')
|
testCompile project(':spring-session-data-mongo')
|
||||||
testCompile project(':spring-session-data-redis')
|
testCompile project(':spring-session-data-redis')
|
||||||
testCompile "org.springframework:spring-jdbc"
|
testCompile "org.springframework:spring-jdbc"
|
||||||
|
|||||||
@@ -1,270 +0,0 @@
|
|||||||
= Spring Session - HttpSession with GemFire Client/Server using Spring Boot
|
|
||||||
John Blum
|
|
||||||
:toc:
|
|
||||||
|
|
||||||
This guide describes how to build a _Spring Boot_ application configured with _Spring Session_ to transparently leverage
|
|
||||||
Pivotal GemFire to back a web application's `HttpSession`.
|
|
||||||
|
|
||||||
In this sample, GemFire's client/server topology is employed using a pair of _Spring Boot_ applications, one to
|
|
||||||
configure and run a GemFire Server and another to configure and run the client, Spring MVC-based web application
|
|
||||||
making use of the `HttpSession`.
|
|
||||||
|
|
||||||
NOTE: The completed guide can be found in the <<httpsession-gemfire-boot-sample,HttpSession with GemFire using Spring Boot Sample Application>>.
|
|
||||||
|
|
||||||
== Updating Dependencies
|
|
||||||
Before using _Spring Session_, you must ensure that the required dependencies are included.
|
|
||||||
If you are using Maven, include the following `dependencies` in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
[subs="verbatim,attributes"]
|
|
||||||
----
|
|
||||||
<dependencies>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.session</groupId>
|
|
||||||
<artifactId>spring-session-data-gemfire</artifactId>
|
|
||||||
<version>{spring-session-version}</version>
|
|
||||||
<type>pom</type>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
----
|
|
||||||
|
|
||||||
ifeval::["{version-snapshot}" == "true"]
|
|
||||||
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
|
|
||||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
<repositories>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<repository>
|
|
||||||
<id>spring-snapshot</id>
|
|
||||||
<url>https://repo.spring.io/libs-snapshot</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
----
|
|
||||||
endif::[]
|
|
||||||
|
|
||||||
ifeval::["{version-milestone}" == "true"]
|
|
||||||
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
|
|
||||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
<repositories>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<repository>
|
|
||||||
<id>spring-milestone</id>
|
|
||||||
<url>https://repo.spring.io/libs-milestone</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
----
|
|
||||||
endif::[]
|
|
||||||
|
|
||||||
// tag::config[]
|
|
||||||
[[httpsession-spring-java-configuration]]
|
|
||||||
== Spring Boot Configuration
|
|
||||||
|
|
||||||
After adding the required dependencies and repository declarations, we can create our Spring configuration
|
|
||||||
for both the GemFire client and server using _Spring Boot_. The Spring configuration is responsible for
|
|
||||||
creating a Servlet Filter that replaces the `HttpSession` with an implementation backed by _Spring Session_
|
|
||||||
and GemFire.
|
|
||||||
|
|
||||||
=== Spring Boot-based GemFire Server
|
|
||||||
|
|
||||||
We start with the _Spring Boot_ application for configuring and bootstrapping a GemFire Server process...
|
|
||||||
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
include::{samples-dir}boot/gemfire/src/main/java/sample/server/GemFireServer.java[tags=class]
|
|
||||||
----
|
|
||||||
|
|
||||||
<1> The `@EnableGemFireHttpSession` annotation is used on the GemFire Server to mainly define the corresponding
|
|
||||||
Region (e.g. `ClusteredSpringSessions`, the default) in which Session state information will be stored
|
|
||||||
and managed by GemFire. As well, we have specified an arbitrary expiration attribute (i.e. `maxInactiveIntervalInSeconds`)
|
|
||||||
for when the Session will timeout, which is triggered by a GemFire Region entry expiration event that also invalidates
|
|
||||||
the Session object in the Region.
|
|
||||||
<2> Next, we define a few `Properties` that allow us to configure certain aspects of the GemFire Server using
|
|
||||||
http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System properties].
|
|
||||||
<3> Then, we create an instance of the GemFire `Cache` using our defined `Properties`.
|
|
||||||
<4> Finally, we configure and start a `CacheServer` running in the GemFire Server to listen for connections
|
|
||||||
from cache clients. The `CacheServer's` `Socket` will be used to connect our GemFire cache client,
|
|
||||||
_Spring Boot_ web application to the server.
|
|
||||||
|
|
||||||
The sample also makes use of a `PropertySourcesPlaceholderConfigurer` bean in order to externalize the sample application
|
|
||||||
configuration to affect GemFire and application configuration/behavior from the command-line (e.g. such as GemFire's
|
|
||||||
`log-level` using the `gemfire.log.level` System property; more details below).
|
|
||||||
|
|
||||||
=== Spring Boot-based GemFire cache client Web application
|
|
||||||
|
|
||||||
Now, we create our _Spring Boot_ Web application exposing our Web service with Spring MVC, running as a
|
|
||||||
GemFire cache client connected to our _Spring Boot_-based GemFire Server, using Spring Session backed by GemFire
|
|
||||||
to manage Session state in a clustered, replicated fashion.
|
|
||||||
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
include::{samples-dir}boot/gemfire/src/main/java/sample/client/Application.java[tags=class]
|
|
||||||
----
|
|
||||||
|
|
||||||
<1> Here, again, we use the `@EnableGemFireHttpSession` annotation to not only configure the GemFire cache client,
|
|
||||||
but to also override the (HTTP) Web application container's `HttpSession` and replace it with a Session implementation
|
|
||||||
backed by _Spring Session_ and GemFire. Also notice, we did not define any Session expiration timeout with the
|
|
||||||
`maxInactiveIntervalInSeconds` attribute this time. That is because the Session expiration is managed by GemFire,
|
|
||||||
on the server, which will appropriately notify the cache client when the Session times out. Again, we have just
|
|
||||||
resorted to using the default named Region, `ClusteredSpringSessions`. Of course, we can change the Region name,
|
|
||||||
but we must do so on both the client and the server. That is a GemFire requirement, not a
|
|
||||||
_Spring Session Data GemFire_ requirement.
|
|
||||||
<2> Similarly to the server configuration, we set a few basic GemFire System `Properties` on the client.
|
|
||||||
<3> Although, this time, an instance of `ClientCache` is created with the `ClientCacheFactoryBean`
|
|
||||||
from _Spring Data GemFire_.
|
|
||||||
<4> However, in order to connect to the GemFire Server we must define a GemFire `Pool` bean containing a
|
|
||||||
pool of connections to the server. Whenever a client Region entry operation corresponding to a Session update occurs,
|
|
||||||
the client-side Region will use an existing, pooled connection to route the operation to the server.
|
|
||||||
<5> The following _Spring_ `BeanPostProcessor` (along with some utility methods) are only needed for testing purposes
|
|
||||||
and are not required by any production code. Specifically, the `BeanPostProcessor` along with the code referenced in *6*
|
|
||||||
is useful in integration test cases where the client and server processes are forked by the test framework. It is pretty
|
|
||||||
easy to figure out that a race condition is imminent without proper coordination between the client and the server,
|
|
||||||
therefore, the BPP and `ClientMembershipListener` help sync the interaction between the client and the server
|
|
||||||
on startup during automated testing.
|
|
||||||
<6> Navigates the Web application to the home page (`index.html`), which uses **Thymeleaf** templates for server-side
|
|
||||||
pages.
|
|
||||||
<7> Heartbeat Web service endpoint (useful for manual testing purposes).
|
|
||||||
<8> Web service endpoint allowing the user to add a Session attribute using the Web application UI. In addition,
|
|
||||||
the webapp stores an additional Session attribute (`requestCount`) to keep track of how many HTTP requests the user
|
|
||||||
has sent during the current "session".
|
|
||||||
|
|
||||||
There are many other utility methods, so please refer to the actual source code for full details.
|
|
||||||
|
|
||||||
TIP: In typical GemFire deployments, where the cluster includes potentially hundreds or thousands of GemFire data nodes
|
|
||||||
(servers), it is more common for clients to connect to one or more GemFire Locators running in the cluster. A Locator
|
|
||||||
passes meta-data to clients about the servers available, their load and which servers have the client's data of interest,
|
|
||||||
which is particularly important in direct, single-hop data access and latency-sensitive operations. See more details
|
|
||||||
about the http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client/Server Topology in GemFire's User Guide].
|
|
||||||
|
|
||||||
NOTE: For more information on configuring _Spring Data GemFire_, refer to the http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
|
|
||||||
|
|
||||||
The `@EnableGemFireHttpSession` annotation enables a developer to configure certain aspects of both _Spring Session_
|
|
||||||
and GemFire out-of-the-box using the following attributes:
|
|
||||||
|
|
||||||
* `maxInactiveIntervalInSeconds` - controls _HttpSession_ idle-timeout expiration (defaults to **30 minutes**).
|
|
||||||
* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "*ClusteredSpringSessions*").
|
|
||||||
* `clientRegionShort` - specifies GemFire's http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
|
|
||||||
with a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
|
|
||||||
(default is `PROXY`). This attribute is only used when configuring client Region.
|
|
||||||
* `poolName` - name of the dedicated GemFire Pool used to connect a client to the cluster of servers. The attribute
|
|
||||||
is only used when the application is a GemFire cache client. Defaults to `gemfirePool`.
|
|
||||||
* `serverRegionShort` - specifies GemFire's http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
|
|
||||||
using a GemFire http://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html[RegionShortcut]
|
|
||||||
(default is `PARTITION`). This attribute is only used when configuring server Regions, or when a p2p topology is employed.
|
|
||||||
|
|
||||||
NOTE: It is important to remember that the GemFire client Region name must match a server Region by the same name if
|
|
||||||
the client Region is a `PROXY` or `CACHING_PROXY`. Client and server Region names are not required to match if
|
|
||||||
the client Region used to store Spring Sessions is `LOCAL`. However, keep in mind that your session state will not
|
|
||||||
be propagated to the server and you lose all the benefits of using GemFire to store and manage distributed, replicated
|
|
||||||
session state information in a cluster.
|
|
||||||
|
|
||||||
[[httpsession-gemfire-boot-sample]]
|
|
||||||
== HttpSession with GemFire using Spring Boot Sample Application
|
|
||||||
|
|
||||||
=== Running the httpsession-gemfire-boot Sample Application
|
|
||||||
|
|
||||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following commands.
|
|
||||||
|
|
||||||
First, you must run the server:
|
|
||||||
|
|
||||||
----
|
|
||||||
$ ./gradlew :samples:httpsession-gemfire-boot:run [-Dgemfire.log-level=config]
|
|
||||||
----
|
|
||||||
|
|
||||||
Then, in a separate terminal, run the client:
|
|
||||||
|
|
||||||
----
|
|
||||||
$ ./gradlew :samples:httpsession-gemfire-boot:bootRun [-Dgemfire.log-level=config]
|
|
||||||
----
|
|
||||||
|
|
||||||
You should now be able to access the application at http://localhost:8080/. In this sample, the web application
|
|
||||||
is the client cache and the server is standalone.
|
|
||||||
|
|
||||||
=== Exploring the httpsession-gemfire-boot Sample Application
|
|
||||||
|
|
||||||
Try using the application. Fill out the form with the following information:
|
|
||||||
|
|
||||||
* **Attribute Name:** _username_
|
|
||||||
* **Attribute Value:** _test_
|
|
||||||
|
|
||||||
Now click the **Set Attribute** button. You should now see the attribute name and value displayed in the table
|
|
||||||
along with an additional attribute (`requestCount`) indicating the number of Session interactions (via HTTP requests).
|
|
||||||
|
|
||||||
=== How does it work?
|
|
||||||
|
|
||||||
We interact with the standard `HttpSession` in the the Spring MVC web service endpoint, shown here for convenience:
|
|
||||||
|
|
||||||
.src/main/java/sample/SessionServlet.java
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
@RequestMapping(method = RequestMethod.POST, path = "/session")
|
|
||||||
public String session(HttpSession session, ModelMap modelMap,
|
|
||||||
@RequestParam(name = "attributeName", required = false) String name,
|
|
||||||
@RequestParam(name = "attributeValue", required = false) String value) {
|
|
||||||
|
|
||||||
modelMap.addAttribute("sessionAttributes",
|
|
||||||
attributes(setAttribute(updateRequestCount(session), name, value)));
|
|
||||||
|
|
||||||
return INDEX_TEMPLATE_VIEW_NAME;
|
|
||||||
}
|
|
||||||
----
|
|
||||||
|
|
||||||
Instead of using the embedded HTTP server's `HttpSession`, we are actually persisting the Session state in GemFire.
|
|
||||||
_Spring Session_ creates a cookie named SESSION in your browser that contains the id of your session.
|
|
||||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome]
|
|
||||||
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
|
||||||
|
|
||||||
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
|
|
||||||
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
|
|
||||||
|
|
||||||
NOTE: In order to run the following, you must uncomment the lines in the `GemFireServer` class, `gemfireProperties` bean
|
|
||||||
for the following GemFire System properties: `jmx-manager` and `jmx-manager-start`.
|
|
||||||
|
|
||||||
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
|
|
||||||
at the command-line:
|
|
||||||
|
|
||||||
$ gfsh
|
|
||||||
|
|
||||||
Then, enter the following commands in _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
|
|
||||||
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
|
|
||||||
|
|
||||||
....
|
|
||||||
gfsh>connect --jmx-manager=localhost[1099]
|
|
||||||
|
|
||||||
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
|
|
||||||
|
|
||||||
Result : true
|
|
||||||
startCount : 0
|
|
||||||
endCount : 20
|
|
||||||
Rows : 1
|
|
||||||
|
|
||||||
Result
|
|
||||||
------------------------------------
|
|
||||||
70002719-3c54-4c20-82c3-e7faa6b718f3
|
|
||||||
|
|
||||||
NEXT_STEP_NAME : END
|
|
||||||
|
|
||||||
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
|
|
||||||
....
|
|
||||||
|
|
||||||
NOTE: The _GemFire User Guide_ has more detailed instructions on using http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
|
|
||||||
|
|
||||||
Now visit the application at http://localhost:8080/ again and observe that the attribute we added is no longer displayed.
|
|
||||||
|
|
||||||
Alternatively, you can wait **20 seconds** for the session to expire and timeout, and then refresh the page. The attribute
|
|
||||||
we added should no longer be displayed in the table.
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
= Spring Session - HttpSession with GemFire Client/Server (Quick Start)
|
|
||||||
John Blum
|
|
||||||
:toc:
|
|
||||||
|
|
||||||
This guide describes how to configure Spring Session to transparently leverage Pivotal GemFire to back a web application's
|
|
||||||
`HttpSession` using Java Configuration.
|
|
||||||
|
|
||||||
NOTE: The completed guide can be found in the <<httpsession-gemfire-clientserver-java-sample-app,HttpSession with GemFire (Client/Server) Sample Application>>.
|
|
||||||
|
|
||||||
== Updating Dependencies
|
|
||||||
Before using Spring Session, you must ensure that the required dependencies are included.
|
|
||||||
If you are using Maven, include the following `dependencies` in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
[subs="verbatim,attributes"]
|
|
||||||
----
|
|
||||||
<dependencies>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.session</groupId>
|
|
||||||
<artifactId>spring-session-data-gemfire</artifactId>
|
|
||||||
<version>{spring-session-version}</version>
|
|
||||||
<type>pom</type>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-web</artifactId>
|
|
||||||
<version>{spring-version}</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
----
|
|
||||||
|
|
||||||
ifeval::["{version-snapshot}" == "true"]
|
|
||||||
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
|
|
||||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
<repositories>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<repository>
|
|
||||||
<id>spring-snapshot</id>
|
|
||||||
<url>https://repo.spring.io/libs-snapshot</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
----
|
|
||||||
endif::[]
|
|
||||||
|
|
||||||
ifeval::["{version-milestone}" == "true"]
|
|
||||||
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
|
|
||||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
<repositories>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<repository>
|
|
||||||
<id>spring-milestone</id>
|
|
||||||
<url>https://repo.spring.io/libs-milestone</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
----
|
|
||||||
endif::[]
|
|
||||||
|
|
||||||
// tag::config[]
|
|
||||||
[[httpsession-spring-java-configuration]]
|
|
||||||
== Spring Java Configuration
|
|
||||||
|
|
||||||
After adding the required dependencies and repository declarations, we can create our Spring configuration.
|
|
||||||
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession`
|
|
||||||
with an implementation backed by Spring Session and GemFire.
|
|
||||||
|
|
||||||
Add the following Spring Configuration:
|
|
||||||
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
include::{samples-dir}javaconfig/gemfire-clientserver/src/main/java/sample/ClientConfig.java[tags=class]
|
|
||||||
----
|
|
||||||
|
|
||||||
<1> The `@EnableGemFireHttpSession` annotation creates a Spring bean named `springSessionRepositoryFilter` that
|
|
||||||
implements `Filter`. The filter is what replaces the `HttpSession` with an implementation backed by Spring Session
|
|
||||||
and GemFire.
|
|
||||||
<2> Next, we register a `Properties` bean that allows us to configure certain aspects of the GemFire client cache
|
|
||||||
using http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System properties].
|
|
||||||
<3> We use the `Properties` to configure an instance of a GemFire `ClientCache`.
|
|
||||||
<4> Then, we configure a `Pool` of client connections to talk to the GemFire Server in our Client/Server topology. In our
|
|
||||||
configuration, we have used sensible settings for timeouts, number of connections and so on. Also, the `Pool` has been
|
|
||||||
configured to connect directly to a server. Learn more about various `Pool` configuration settings from the
|
|
||||||
http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/PoolFactory.html[PoolFactory API].
|
|
||||||
<5> Finally, we include a Spring `BeanPostProcessor` to block the client until our GemFire Server is up and running,
|
|
||||||
listening for and accepting client connections.
|
|
||||||
|
|
||||||
The `gemfireCacheServerReadyBeanPostProcessor` is necessary in order to coordinate the client and server in
|
|
||||||
an automated fashion during testing, but unnecessary in situations where the GemFire cluster is already presently
|
|
||||||
running, such as in production.
|
|
||||||
|
|
||||||
The `BeanPostProcessor` uses a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/management/membership/ClientMembershipListener.html[ClientMembershipListener]
|
|
||||||
that will be notified when the client has successfully connected to the server. Once a connection has been established,
|
|
||||||
the listener releases the latch that the `BeanPostProcessor` will wait on (up to the specified timeout) in the
|
|
||||||
`postProcessAfterInitialization` callback to block the client.
|
|
||||||
|
|
||||||
TIP: In typical GemFire deployments, where the cluster includes potentially hundreds of GemFire data nodes (servers),
|
|
||||||
it is more common for clients to connect to one or more GemFire Locators running in the cluster. A Locator passes meta-data
|
|
||||||
to clients about the servers available, load and which servers have the client's data of interest, which is particularly
|
|
||||||
important for single-hop, direct data access. See more details about the http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client/Server Topology in GemFire's User Guide].
|
|
||||||
|
|
||||||
NOTE: For more information on configuring _Spring Data GemFire_, refer to the http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
|
|
||||||
|
|
||||||
The `@EnableGemFireHttpSession` annotation enables a developer to configure certain aspects of both Spring Session
|
|
||||||
and GemFire out-of-the-box using the following attributes:
|
|
||||||
|
|
||||||
* `maxInactiveIntervalInSeconds` - controls _HttpSession_ idle-timeout expiration (defaults to **30 minutes**).
|
|
||||||
* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "*ClusteredSpringSessions*").
|
|
||||||
* `clientRegionShort` - specifies GemFire's http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
|
|
||||||
with a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
|
|
||||||
(default is `PROXY`). This attribute is only used when configuring client Region.
|
|
||||||
* `poolName` - name of the dedicated GemFire Pool used to connect a client to the cluster of servers. The attribute
|
|
||||||
is only used when the application is a GemFire cache client. Defaults to `gemfirePool`.
|
|
||||||
* `serverRegionShort` - specifies GemFire's http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
|
|
||||||
using a GemFire http://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html[RegionShortcut]
|
|
||||||
(default is `PARTITION`). This attribute is only used when configuring server Regions, or when a p2p topology is employed.
|
|
||||||
|
|
||||||
NOTE: It is important to note that the GemFire client Region name must match a server Region by the same name if
|
|
||||||
the client Region is a `PROXY` or `CACHING_PROXY`. Names are not required to match if the client Region used to
|
|
||||||
store Spring Sessions is `LOCAL`, however, keep in mind that your session state will not be propagated to the server
|
|
||||||
and you lose all benefits of using GemFire to store and manage distributed, replicated session state information
|
|
||||||
in a cluster.
|
|
||||||
|
|
||||||
NOTE: `serverRegionShort` is ignored in a client/server cache configuration and only applies when
|
|
||||||
a peer-to-peer (P2P) topology, and more specifically, a GemFire peer cache is used.
|
|
||||||
|
|
||||||
=== Server Configuration
|
|
||||||
|
|
||||||
We have only covered one side of the equation. We also need a GemFire Server for our client to talk to and send
|
|
||||||
session state to the server to manage.
|
|
||||||
|
|
||||||
In this sample, we will use the following GemFire Server Java Configuration:
|
|
||||||
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
include::{samples-dir}javaconfig/gemfire-clientserver/src/main/java/sample/ServerConfig.java[tags=class]
|
|
||||||
----
|
|
||||||
|
|
||||||
<1> On the server, we also configure Spring Session using the `@EnableGemFireHttpSession` annotation. This ensures
|
|
||||||
the Region names on both the client and server match (in this sample, we use the default "_ClusteredSpringSessions_").
|
|
||||||
We have also set the session timeout to **30 seconds**. Later, we will see how this timeout is used.
|
|
||||||
<2> Next, we configure the GemFire Server using GemFire System Properties very much like our P2P samples.
|
|
||||||
With the `mcast-port` set to 0 and no `locators` property specified, our server will be standalone. We also allow a
|
|
||||||
JMX client (e.g. _Gfsh_) to connect to our server with the use of the GemFire-specific JMX System properties.
|
|
||||||
<3> Then, we create an instance of a GemFire peer `Cache` initialized with our GemFire System Properties.
|
|
||||||
<4> We also setup a GemFire `CacheServer` instance running on **localhost**, listening to port **12480**,
|
|
||||||
ready to accept our client connection.
|
|
||||||
<5> Finally, we declare a `main` method as an entry point for launching and running our GemFire Server
|
|
||||||
from the command-line.
|
|
||||||
|
|
||||||
== Java Servlet Container Initialization
|
|
||||||
|
|
||||||
Our <<httpsession-spring-java-configuration,Spring Java Configuration>> created a Spring bean named `springSessionRepositoryFilter`
|
|
||||||
that implements `Filter`. The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession`
|
|
||||||
with a custom implementation backed by Spring Session and GemFire.
|
|
||||||
|
|
||||||
In order for our `Filter` to do its magic, Spring needs to load our `ClientConfig` class. We also need to ensure our
|
|
||||||
Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request. Fortunately, Spring Session
|
|
||||||
provides a utility class named `AbstractHttpSessionApplicationInitializer` to make both of these steps extremely easy.
|
|
||||||
|
|
||||||
You can find an example below:
|
|
||||||
|
|
||||||
.src/main/java/sample/Initializer.java
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
include::{samples-dir}javaconfig/gemfire-clientserver/src/main/java/sample/Initializer.java[tags=class]
|
|
||||||
----
|
|
||||||
|
|
||||||
NOTE: The name of our class (`Initializer`) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
|
|
||||||
|
|
||||||
<1> The first step is to extend `AbstractHttpSessionApplicationInitializer`.
|
|
||||||
This ensures that a Spring bean named `springSessionRepositoryFilter` is registered with our Servlet Container
|
|
||||||
and used for every request.
|
|
||||||
<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to easily allow Spring to load our `ClientConfig`.
|
|
||||||
// end::config[]
|
|
||||||
|
|
||||||
[[httpsession-gemfire-clientserver-java-sample-app]]
|
|
||||||
== HttpSession with GemFire (Client/Server) Sample Application
|
|
||||||
|
|
||||||
|
|
||||||
=== Running the httpsession-gemfire-clientserver Sample Application
|
|
||||||
|
|
||||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following commands.
|
|
||||||
|
|
||||||
First, you need to run the server using:
|
|
||||||
|
|
||||||
----
|
|
||||||
$ ./gradlew :samples:httpsession-gemfire-clientserver:run [-Dsample.httpsession.gemfire.log-level=info]
|
|
||||||
----
|
|
||||||
|
|
||||||
Then, in a separate terminal, you run the client using:
|
|
||||||
|
|
||||||
----
|
|
||||||
$ ./gradlew :samples:httpsession-gemfire-clientserver:tomcatRun [-Dsample.httpsession.gemfire.log-level=info]
|
|
||||||
----
|
|
||||||
|
|
||||||
You should now be able to access the application at http://localhost:8080/. In this sample, the web application
|
|
||||||
is the client cache and the server is standalone.
|
|
||||||
|
|
||||||
=== Exploring the httpsession-gemfire-clientserver Sample Application
|
|
||||||
|
|
||||||
Try using the application. Fill out the form with the following information:
|
|
||||||
|
|
||||||
* **Attribute Name:** _username_
|
|
||||||
* **Attribute Value:** _john_
|
|
||||||
|
|
||||||
Now click the **Set Attribute** button. You should now see the values displayed in the table.
|
|
||||||
|
|
||||||
=== How does it work?
|
|
||||||
|
|
||||||
We interact with the standard `HttpSession` in the `SessionServlet` shown below:
|
|
||||||
|
|
||||||
.src/main/java/sample/SessionServlet.java
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
include::{samples-dir}javaconfig/gemfire-clientserver/src/main/java/sample/SessionServlet.java[tags=class]
|
|
||||||
----
|
|
||||||
|
|
||||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire.
|
|
||||||
Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
|
|
||||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome]
|
|
||||||
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
|
||||||
|
|
||||||
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
|
|
||||||
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
|
|
||||||
|
|
||||||
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
|
|
||||||
at the command-line:
|
|
||||||
|
|
||||||
$ gfsh
|
|
||||||
|
|
||||||
Then, enter the following commands in _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
|
|
||||||
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
|
|
||||||
|
|
||||||
....
|
|
||||||
gfsh>connect --jmx-manager=localhost[1099]
|
|
||||||
|
|
||||||
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
|
|
||||||
|
|
||||||
Result : true
|
|
||||||
startCount : 0
|
|
||||||
endCount : 20
|
|
||||||
Rows : 1
|
|
||||||
|
|
||||||
Result
|
|
||||||
------------------------------------
|
|
||||||
70002719-3c54-4c20-82c3-e7faa6b718f3
|
|
||||||
|
|
||||||
NEXT_STEP_NAME : END
|
|
||||||
|
|
||||||
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
|
|
||||||
....
|
|
||||||
|
|
||||||
NOTE: The _GemFire User Guide_ has more detailed instructions on using http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
|
|
||||||
|
|
||||||
Now visit the application at http://localhost:8080/ again and observe that the attribute we added is no longer displayed.
|
|
||||||
|
|
||||||
Alternatively, you can wait **30 seconds** for the session to expire and timeout, and then refresh the page. The attribute
|
|
||||||
we added should no longer be displayed in the table. However, keep in mind, that by refreshing the page, you will inadvertently
|
|
||||||
create a new (empty) session. If you run the query again, you will also see two session IDs, the new and the old,
|
|
||||||
since GemFire keeps a "tombstone" of the old session around.
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
= Spring Session - HttpSession with GemFire P2P (Quick Start)
|
|
||||||
John Blum, Rob Winch
|
|
||||||
:toc:
|
|
||||||
|
|
||||||
This guide describes how to configure Pivotal GemFire as a provider in Spring Session to transparently back
|
|
||||||
a web application's `HttpSession` using Java Configuration.
|
|
||||||
|
|
||||||
NOTE: The completed guide can be found in the <<httpsession-gemfire-p2p-java-sample-app,HttpSession with GemFire (P2P) Sample Application>>.
|
|
||||||
|
|
||||||
== Updating Dependencies
|
|
||||||
Before using Spring Session, you must ensure that the required dependencies are included.
|
|
||||||
If you are using Maven, include the following `dependencies` in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
[subs="verbatim,attributes"]
|
|
||||||
----
|
|
||||||
<dependencies>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.session</groupId>
|
|
||||||
<artifactId>spring-session-data-gemfire</artifactId>
|
|
||||||
<version>{spring-session-version}</version>
|
|
||||||
<type>pom</type>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-web</artifactId>
|
|
||||||
<version>{spring-version}</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
----
|
|
||||||
|
|
||||||
ifeval::["{version-snapshot}" == "true"]
|
|
||||||
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
|
|
||||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
<repositories>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<repository>
|
|
||||||
<id>spring-snapshot</id>
|
|
||||||
<url>https://repo.spring.io/libs-snapshot</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
----
|
|
||||||
endif::[]
|
|
||||||
|
|
||||||
ifeval::["{version-milestone}" == "true"]
|
|
||||||
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
|
|
||||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
<repositories>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<repository>
|
|
||||||
<id>spring-milestone</id>
|
|
||||||
<url>https://repo.spring.io/libs-milestone</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
----
|
|
||||||
endif::[]
|
|
||||||
|
|
||||||
// tag::config[]
|
|
||||||
[[httpsession-spring-java-configuration]]
|
|
||||||
== Spring Java Configuration
|
|
||||||
|
|
||||||
After adding the required dependencies and repository declarations, we can create our Spring configuration.
|
|
||||||
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession`
|
|
||||||
with an implementation backed by Spring Session and GemFire.
|
|
||||||
|
|
||||||
Add the following Spring Configuration:
|
|
||||||
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
include::{samples-dir}javaconfig/gemfire-p2p/src/main/java/sample/Config.java[tags=class]
|
|
||||||
----
|
|
||||||
|
|
||||||
<1> The `@EnableGemFireHttpSession` annotation creates a Spring bean named `springSessionRepositoryFilter` that
|
|
||||||
implements `Filter`. The filter is what replaces the `HttpSession` with an implementation backed by Spring Session.
|
|
||||||
In this instance, Spring Session is backed by GemFire.
|
|
||||||
<2> Then, we configure a GemFire peer cache using standard GemFire System properties. We give the GemFire data node
|
|
||||||
a name using the `name` property and set `mcast-port` to 0. With the absence of a `locators` property, this data node
|
|
||||||
will be a standalone server. GemFire's `log-level` is set using an application-specific System property (`sample.httpsession.gemfire.log-level`)
|
|
||||||
that a user can specify on the command-line when running this sample application using either Maven or Gradle (default is "_warning_").
|
|
||||||
<3> Finally, we create an instance of the GemFire peer cache that embeds GemFire in the same JVM process as the running
|
|
||||||
Spring Session sample application.
|
|
||||||
|
|
||||||
TIP: Additionally, we have configured this data node (server) as a GemFire Manager as well using GemFire-specific
|
|
||||||
JMX System properties that enable JMX client (e.g. _Gfsh_) to connect to this running data node.
|
|
||||||
|
|
||||||
NOTE: For more information on configuring _Spring Data GemFire_, refer to the http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
|
|
||||||
|
|
||||||
The `@EnableGemFireHttpSession` annotation enables a developer to configure certain aspects of Spring Session
|
|
||||||
and GemFire out-of-the-box using the following attributes:
|
|
||||||
|
|
||||||
* `maxInactiveIntervalInSeconds` - controls HttpSession idle-timeout expiration (defaults to **30 minutes**).
|
|
||||||
* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "_ClusteredSpringSessions_").
|
|
||||||
* `serverRegionShort` - specifies GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policies]
|
|
||||||
with a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html[RegionShortcut]
|
|
||||||
(default is `PARTITION`).
|
|
||||||
|
|
||||||
NOTE: `clientRegionShort` is ignored in a peer cache configuration and only applies when a client-server topology,
|
|
||||||
and more specifically, a GemFire client cache is used.
|
|
||||||
|
|
||||||
== Java Servlet Container Initialization
|
|
||||||
|
|
||||||
Our <<httpsession-spring-java-configuration,Spring Java Configuration>> created a Spring bean named `springSessionRepositoryFilter`
|
|
||||||
that implements `Filter`. The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession`
|
|
||||||
with a custom implementation backed by Spring Session and GemFire.
|
|
||||||
|
|
||||||
In order for our `Filter` to do its magic, Spring needs to load our `Config` class. We also need to ensure our
|
|
||||||
Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request. Fortunately, Spring Session
|
|
||||||
provides a utility class named `AbstractHttpSessionApplicationInitializer` to make both of these steps extremely easy.
|
|
||||||
|
|
||||||
You can find an example below:
|
|
||||||
|
|
||||||
.src/main/java/sample/Initializer.java
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
include::{samples-dir}javaconfig/gemfire-p2p/src/main/java/sample/Initializer.java[tags=class]
|
|
||||||
----
|
|
||||||
|
|
||||||
NOTE: The name of our class (`Initializer`) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
|
|
||||||
|
|
||||||
<1> The first step is to extend `AbstractHttpSessionApplicationInitializer`.
|
|
||||||
This ensures that a Spring bean named `springSessionRepositoryFilter` is registered with our Servlet Container
|
|
||||||
and used for every request.
|
|
||||||
<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to easily allow Spring to load our `Config`.
|
|
||||||
// end::config[]
|
|
||||||
|
|
||||||
[[httpsession-gemfire-p2p-java-sample-app]]
|
|
||||||
== HttpSession with GemFire (P2P) Sample Application
|
|
||||||
|
|
||||||
|
|
||||||
=== Running the httpsession-gemfire-p2p Sample Application
|
|
||||||
|
|
||||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
|
|
||||||
|
|
||||||
----
|
|
||||||
$ ./gradlew :samples:httpsession-gemfire-p2p:tomcatRun [-Dsample.httpsession.gemfire.log-level=info]
|
|
||||||
----
|
|
||||||
|
|
||||||
You should now be able to access the application at http://localhost:8080/
|
|
||||||
|
|
||||||
=== Exploring the httpsession-gemfire-p2p Sample Application
|
|
||||||
|
|
||||||
Try using the application. Fill out the form with the following information:
|
|
||||||
|
|
||||||
* **Attribute Name:** _username_
|
|
||||||
* **Attribute Value:** _john_
|
|
||||||
|
|
||||||
Now click the **Set Attribute** button. You should now see the values displayed in the table.
|
|
||||||
|
|
||||||
=== How does it work?
|
|
||||||
|
|
||||||
We interact with the standard `HttpSession` in the `SessionServlet` shown below:
|
|
||||||
|
|
||||||
.src/main/java/sample/SessionServlet.java
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
include::{samples-dir}javaconfig/gemfire-p2p/src/main/java/sample/SessionServlet.java[tags=class]
|
|
||||||
----
|
|
||||||
|
|
||||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire.
|
|
||||||
Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
|
|
||||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome]
|
|
||||||
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
|
||||||
|
|
||||||
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
|
|
||||||
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
|
|
||||||
|
|
||||||
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
|
|
||||||
at the command-line:
|
|
||||||
|
|
||||||
$ gfsh
|
|
||||||
|
|
||||||
Then, enter the following into _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
|
|
||||||
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
|
|
||||||
|
|
||||||
....
|
|
||||||
gfsh>connect --jmx-manager=localhost[1099]
|
|
||||||
|
|
||||||
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
|
|
||||||
|
|
||||||
Result : true
|
|
||||||
startCount : 0
|
|
||||||
endCount : 20
|
|
||||||
Rows : 1
|
|
||||||
|
|
||||||
Result
|
|
||||||
------------------------------------
|
|
||||||
70002719-3c54-4c20-82c3-e7faa6b718f3
|
|
||||||
|
|
||||||
NEXT_STEP_NAME : END
|
|
||||||
|
|
||||||
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
|
|
||||||
....
|
|
||||||
|
|
||||||
NOTE: The _GemFire User Guide_ has more detailed instructions on using http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
|
|
||||||
|
|
||||||
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
= Spring Session - HttpSession with GemFire Client/Server using XML (Quick Start)
|
|
||||||
John Blum
|
|
||||||
:toc:
|
|
||||||
|
|
||||||
This guide describes how to configure Spring Session to transparently leverage Pivotal GemFire to back a web application's
|
|
||||||
`HttpSession` using XML Configuration.
|
|
||||||
|
|
||||||
NOTE: The completed guide can be found in the <<httpsession-gemfire-clientserver-xml-sample-app,HttpSession with GemFire (Client/Server) using XML Sample Application>>.
|
|
||||||
|
|
||||||
== Updating Dependencies
|
|
||||||
Before using Spring Session, you must ensure that the required dependencies are included.
|
|
||||||
If you are using Maven, include the following `dependencies` in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
[subs="verbatim,attributes"]
|
|
||||||
----
|
|
||||||
<dependencies>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.session</groupId>
|
|
||||||
<artifactId>spring-session-data-gemfire</artifactId>
|
|
||||||
<version>{spring-session-version}</version>
|
|
||||||
<type>pom</type>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-web</artifactId>
|
|
||||||
<version>{spring-version}</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
----
|
|
||||||
|
|
||||||
ifeval::["{version-snapshot}" == "true"]
|
|
||||||
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
|
|
||||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
<repositories>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<repository>
|
|
||||||
<id>spring-snapshot</id>
|
|
||||||
<url>https://repo.spring.io/libs-snapshot</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
----
|
|
||||||
endif::[]
|
|
||||||
|
|
||||||
ifeval::["{version-milestone}" == "true"]
|
|
||||||
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
|
|
||||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
<repositories>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<repository>
|
|
||||||
<id>spring-milestone</id>
|
|
||||||
<url>https://repo.spring.io/libs-milestone</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
----
|
|
||||||
endif::[]
|
|
||||||
|
|
||||||
// tag::config[]
|
|
||||||
[[httpsession-spring-xml-configuration]]
|
|
||||||
== Spring XML Configuration
|
|
||||||
|
|
||||||
After adding the required dependencies and repository declarations, we can create our Spring configuration.
|
|
||||||
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession`
|
|
||||||
with an implementation backed by Spring Session and GemFire.
|
|
||||||
|
|
||||||
Add the following Spring Configuration:
|
|
||||||
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
include::{samples-dir}xml/gemfire-clientserver/src/main/webapp/WEB-INF/spring/session-client.xml[tags=beans]
|
|
||||||
----
|
|
||||||
|
|
||||||
<1> Spring annotation configuration support is enabled with `<context:annotation-config/>` element so that any
|
|
||||||
Spring beans declared in the XML config that are annotated with either Spring or Standard Java annotations supported
|
|
||||||
by Spring will be configured appropriately.
|
|
||||||
<2> The `META-INF/spring/application.properties` file are used along with the `PropertySourcesPlaceholderConfigurer`
|
|
||||||
bean to replace placeholders in the Spring XML configuration meta-data with the approrpriate property values.
|
|
||||||
<3> Then the `GemFireCacheSeverReadyBeanPostProcessor`is registered to determine whether a GemFire Server
|
|
||||||
at the designated host/port is running and listening for client connections, blocking client startup until
|
|
||||||
the server is available and ready.
|
|
||||||
<4> Next, we include a `Properties` bean to configure certain aspects of the GemFire client cache using
|
|
||||||
http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System Properties].
|
|
||||||
In this case, we are just setting GemFire's `log-level` from a application-specific System property, defaulting
|
|
||||||
to `warning` if unspecified.
|
|
||||||
<5> Then we create a instance of a GemFire `ClientCache` initialized with our `gemfireProperties`.
|
|
||||||
<6> We configure a Pool of client connections to talk to the GemFire Server in our Client/Server topology.
|
|
||||||
In our configuration, we use sensible settings for timeouts, number of connections and so on. Also, our `Pool`
|
|
||||||
has been configured to connect directly to a server.
|
|
||||||
<7> Finally, the `GemFireHttpSessionConfiguration` is registered to enable Spring Session functionality.
|
|
||||||
|
|
||||||
TIP: In typical GemFire deployments, where the cluster includes potentially hundreds of GemFire data nodes (servers),
|
|
||||||
it is more common for clients to connect to one or more GemFire Locators running in the cluster. A Locator passes meta-data
|
|
||||||
to clients about the servers available, load and which servers have the client's data of interest, which is particularly
|
|
||||||
important for single-hop, direct data access. See more details about the http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client/Server Topology in GemFire's User Guide].
|
|
||||||
|
|
||||||
NOTE: For more information on configuring _Spring Data GemFire_, refer to the http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
|
|
||||||
|
|
||||||
=== Server Configuration
|
|
||||||
|
|
||||||
We have only covered one side of the equation. We also need a GemFire Server for our client to talk to and send
|
|
||||||
session state information to the server to manage.
|
|
||||||
|
|
||||||
In this sample, we will use the following GemFire Server Java Configuration:
|
|
||||||
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
include::{samples-dir}xml/gemfire-clientserver/src/main/resources/META-INF/spring/session-server.xml[tags=beans]
|
|
||||||
----
|
|
||||||
|
|
||||||
<1> First, we enable Spring annotation config support with the `<context:annotation-config>` element so that any
|
|
||||||
Spring beans declared in the XML config that are annotated with either Spring or Standard Java annotations supported
|
|
||||||
by Spring will be configured appropriately.
|
|
||||||
<2> A `PropertySourcesPlaceholderConfigurer` is registered to replace placeholders in our Spring XML configuration
|
|
||||||
meta-data with property values in the `META-INF/spring/application.properties` file.
|
|
||||||
<3> Next, we configure the GemFire Server using GemFire System Properties very much like our P2P samples.
|
|
||||||
With the `mcast-port` set to 0 and no `locators` property specified, our server will be standalone. We also allow a
|
|
||||||
JMX client (e.g. _Gfsh_) to connect to our server with the use of the GemFire-specific JMX System properties.
|
|
||||||
<4> Then we create an instance of a GemFire peer `Cache` initialized with our GemFire System Properties.
|
|
||||||
<5> We also setup a GemFire `CacheServer` instance running on *localhost*, listening to port **11235**,
|
|
||||||
ready to accept our client connection.
|
|
||||||
<6> Finally, we enable the same Spring Session functionality we used on the client by registering an instance of
|
|
||||||
`GemFireHttpSessionConfiguration`, except that we set the session expiration timeout to **30 seconds**.
|
|
||||||
We will explain later what this means.
|
|
||||||
|
|
||||||
The GemFire Server configuration gets bootstrapped with the following:
|
|
||||||
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
include::{samples-dir}xml/gemfire-clientserver/src/main/java/sample/Application.java[tags=class]
|
|
||||||
----
|
|
||||||
|
|
||||||
TIP: Instead of a simple Java class with a main method, you could also use _Spring Boot_.
|
|
||||||
|
|
||||||
<1> The `@Configuration` annotation designates this Java class as a source for Spring configuration meta-data using
|
|
||||||
Spring's annotation configuration support.
|
|
||||||
<2> Primarily, the configuration comes from the `META-INF/spring/session-server.xml` file, which is also the reason
|
|
||||||
why _Spring Boot_ was not used in this sample, since using XML seemingly defeats the purpose and benefits
|
|
||||||
of using Spring Boot. However, this sample is about demonstrating how to use Spring XML to configure
|
|
||||||
the GemFire client and server.
|
|
||||||
|
|
||||||
== XML Servlet Container Initialization
|
|
||||||
|
|
||||||
Our <<httpsession-spring-xml-configuration,Spring XML Configuration>> created a Spring bean named `springSessionRepositoryFilter`
|
|
||||||
that implements `Filter`. The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with
|
|
||||||
a custom implementation that is backed by Spring Session and GemFire.
|
|
||||||
|
|
||||||
In order for our `Filter` to do its magic, we need to instruct Spring to load our `session-client.xml` configuration file.
|
|
||||||
We do this with the following configuration:
|
|
||||||
|
|
||||||
.src/main/webapp/WEB-INF/web.xml
|
|
||||||
[source,xml,indent=0]
|
|
||||||
----
|
|
||||||
include::{samples-dir}xml/gemfire-clientserver/src/main/webapp/WEB-INF/web.xml[tags=context-param]
|
|
||||||
include::{samples-dir}xml/gemfire-clientserver/src/main/webapp/WEB-INF/web.xml[tags=listeners]
|
|
||||||
----
|
|
||||||
|
|
||||||
The http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-create[ContextLoaderListener]
|
|
||||||
reads the `contextConfigLocation` context parameter value and picks up our _session-client.xml_ configuration file.
|
|
||||||
|
|
||||||
Finally, we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter`
|
|
||||||
for every request.
|
|
||||||
|
|
||||||
The following snippet performs this last step for us:
|
|
||||||
|
|
||||||
.src/main/webapp/WEB-INF/web.xml
|
|
||||||
[source,xml,indent=0]
|
|
||||||
----
|
|
||||||
include::{samples-dir}xml/gemfire-clientserver/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter]
|
|
||||||
----
|
|
||||||
|
|
||||||
The http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[DelegatingFilterProxy]
|
|
||||||
will look up a bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`. For every request that `DelegatingFilterProxy`
|
|
||||||
is invoked, the `springSessionRepositoryFilter` will be invoked.
|
|
||||||
// end::config[]
|
|
||||||
|
|
||||||
[[httpsession-gemfire-clientserver-xml-sample-app]]
|
|
||||||
== HttpSession with GemFire (Client/Server) using XML Sample Application
|
|
||||||
|
|
||||||
|
|
||||||
=== Running the httpsession-gemfire-clientserver-xml Sample Application
|
|
||||||
|
|
||||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following commands.
|
|
||||||
|
|
||||||
First, you need to run the server using:
|
|
||||||
|
|
||||||
----
|
|
||||||
$ ./gradlew :samples:httpsession-gemfire-clientserver-xml:run [-Dsample.httpsession.gemfire.log-level=info]
|
|
||||||
----
|
|
||||||
|
|
||||||
Now, in a separate terminal, you can run the client using:
|
|
||||||
|
|
||||||
----
|
|
||||||
$ ./gradlew :samples:httpsession-gemfire-clientserver-xml:tomcatRun [-Dsample.httpsession.gemfire.log-level=info]
|
|
||||||
----
|
|
||||||
|
|
||||||
You should now be able to access the application at http://localhost:8080/. In this sample, the web application
|
|
||||||
is the client cache and the server is standalone.
|
|
||||||
|
|
||||||
=== Exploring the httpsession-gemfire-clientserver-xml Sample Application
|
|
||||||
|
|
||||||
Try using the application. Fill out the form with the following information:
|
|
||||||
|
|
||||||
* **Attribute Name:** _username_
|
|
||||||
* **Attribute Value:** _john_
|
|
||||||
|
|
||||||
Now click the **Set Attribute** button. You should now see the values displayed in the table.
|
|
||||||
|
|
||||||
=== How does it work?
|
|
||||||
|
|
||||||
We interact with the standard `HttpSession` in the `SessionServlet` shown below:
|
|
||||||
|
|
||||||
.src/main/java/sample/SessionServlet.java
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
include::{samples-dir}xml/gemfire-clientserver/src/main/java/sample/SessionServlet.java[tags=class]
|
|
||||||
----
|
|
||||||
|
|
||||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire.
|
|
||||||
Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
|
|
||||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome]
|
|
||||||
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
|
||||||
|
|
||||||
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
|
|
||||||
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
|
|
||||||
|
|
||||||
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
|
|
||||||
at the command-line:
|
|
||||||
|
|
||||||
$ gfsh
|
|
||||||
|
|
||||||
Then, enter the following commands in _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
|
|
||||||
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
|
|
||||||
|
|
||||||
....
|
|
||||||
gfsh>connect --jmx-manager=localhost[1099]
|
|
||||||
|
|
||||||
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
|
|
||||||
|
|
||||||
Result : true
|
|
||||||
startCount : 0
|
|
||||||
endCount : 20
|
|
||||||
Rows : 1
|
|
||||||
|
|
||||||
Result
|
|
||||||
------------------------------------
|
|
||||||
70002719-3c54-4c20-82c3-e7faa6b718f3
|
|
||||||
|
|
||||||
NEXT_STEP_NAME : END
|
|
||||||
|
|
||||||
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
|
|
||||||
....
|
|
||||||
|
|
||||||
NOTE: The _GemFire User Guide_ has more detailed instructions on using http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
|
|
||||||
|
|
||||||
Now visit the application at http://localhost:8080/ again and observe that the attribute we added is no longer displayed.
|
|
||||||
|
|
||||||
Alternatively, you can wait *30 seconds* for the session to timeout (i.e. expire) and refresh the page. Again, the
|
|
||||||
attribute we added should no longer be displayed in the table. However, keep in mind, that by refreshing the page,
|
|
||||||
you will inadvertently create a new (empty) session. If you run the query again, you will also see two session IDs,
|
|
||||||
the new and the old, since GemFire keeps a "tombstone" of the old session around.
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
= Spring Session - HttpSession with GemFire P2P using XML (Quick Start)
|
|
||||||
John Blum, Rob Winch
|
|
||||||
:toc:
|
|
||||||
|
|
||||||
This guide describes how to configure Pivotal GemFire as a provider in Spring Session to transparently back
|
|
||||||
a web application's `HttpSession` using XML Configuration.
|
|
||||||
|
|
||||||
NOTE: The completed guide can be found in the <<httpsession-gemfire-p2p-xml-sample-app,HttpSession with GemFire (P2P) using XML Sample Application>>.
|
|
||||||
|
|
||||||
== Updating Dependencies
|
|
||||||
Before using Spring Session, you must ensure that the required dependencies are included.
|
|
||||||
If you are using Maven, include the following `dependencies` in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
[subs="verbatim,attributes"]
|
|
||||||
----
|
|
||||||
<dependencies>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.session</groupId>
|
|
||||||
<artifactId>spring-session-data-gemfire</artifactId>
|
|
||||||
<version>{spring-session-version}</version>
|
|
||||||
<type>pom</type>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-web</artifactId>
|
|
||||||
<version>{spring-version}</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
----
|
|
||||||
|
|
||||||
ifeval::["{version-snapshot}" == "true"]
|
|
||||||
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
|
|
||||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
<repositories>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<repository>
|
|
||||||
<id>spring-snapshot</id>
|
|
||||||
<url>https://repo.spring.io/libs-snapshot</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
----
|
|
||||||
endif::[]
|
|
||||||
|
|
||||||
ifeval::["{version-milestone}" == "true"]
|
|
||||||
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
|
|
||||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
|
||||||
|
|
||||||
.pom.xml
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
<repositories>
|
|
||||||
<!-- ... -->
|
|
||||||
|
|
||||||
<repository>
|
|
||||||
<id>spring-milestone</id>
|
|
||||||
<url>https://repo.spring.io/libs-milestone</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
----
|
|
||||||
endif::[]
|
|
||||||
|
|
||||||
// tag::config[]
|
|
||||||
[[httpsession-spring-xml-configuration]]
|
|
||||||
== Spring XML Configuration
|
|
||||||
|
|
||||||
After adding the required dependencies and repository declarations, we can create our Spring configuration.
|
|
||||||
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession`
|
|
||||||
with an implementation backed by Spring Session and GemFire.
|
|
||||||
|
|
||||||
Add the following Spring Configuration:
|
|
||||||
|
|
||||||
.src/main/webapp/WEB-INF/spring/session.xml
|
|
||||||
[source,xml,indent=0]
|
|
||||||
----
|
|
||||||
include::{samples-dir}xml/gemfire-p2p/src/main/webapp/WEB-INF/spring/session.xml[tags=beans]
|
|
||||||
----
|
|
||||||
|
|
||||||
<1> We use the combination of `<context:annotation-config/>` and `GemFireHttpSessionConfiguration` because Spring Session
|
|
||||||
does not yet provide XML Namespace support (see https://github.com/spring-projects/spring-session/issues/104[gh-104]).
|
|
||||||
This creates a Spring bean with the name of `springSessionRepositoryFilter` that implements `Filter`. The filter is what
|
|
||||||
replaces the `HttpSession` with an implementation backed by Spring Session.
|
|
||||||
In this instance, Spring Session is backed by GemFire.
|
|
||||||
<2> Then, we configure a GemFire peer cache using standard GemFire System properties. We give the GemFire data node
|
|
||||||
a name using the `name` property and set `mcast-port` to 0. With the absence of a `locators` property, this data node
|
|
||||||
will be a standalone server. GemFire's `log-level` is set using an application-specific System property
|
|
||||||
(`sample.httpsession.gemfire.log-level`) that a user can specify on the command-line when running this application
|
|
||||||
using either Maven or Gradle (default is "_warning_").
|
|
||||||
<3> Finally, we create an instance of the GemFire peer cache that embeds GemFire in the same JVM process as the running
|
|
||||||
Spring Session sample application.
|
|
||||||
|
|
||||||
TIP: Additionally, we have configured this data node (server) as a GemFire Manager as well using GemFire-specific
|
|
||||||
JMX System properties that enable JMX client (e.g. _Gfsh_) to connect to this running data node.
|
|
||||||
|
|
||||||
NOTE: For more information on configuring _Spring Data GemFire_, refer to the http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
|
|
||||||
|
|
||||||
== XML Servlet Container Initialization
|
|
||||||
|
|
||||||
Our <<httpsession-spring-xml-configuration,Spring XML Configuration>> created a Spring bean named `springSessionRepositoryFilter`
|
|
||||||
that implements `Filter`. The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with
|
|
||||||
a custom implementation that is backed by Spring Session and GemFire.
|
|
||||||
|
|
||||||
In order for our `Filter` to do its magic, we need to instruct Spring to load our `session.xml` configuration file.
|
|
||||||
We do this with the following configuration:
|
|
||||||
|
|
||||||
.src/main/webapp/WEB-INF/web.xml
|
|
||||||
[source,xml,indent=0]
|
|
||||||
----
|
|
||||||
include::{samples-dir}xml/gemfire-p2p/src/main/webapp/WEB-INF/web.xml[tags=context-param]
|
|
||||||
include::{samples-dir}xml/gemfire-p2p/src/main/webapp/WEB-INF/web.xml[tags=listeners]
|
|
||||||
----
|
|
||||||
|
|
||||||
The http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-create[ContextLoaderListener]
|
|
||||||
reads the `contextConfigLocation` context parameter value and picks up our _session.xml_ configuration file.
|
|
||||||
|
|
||||||
Finally, we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter`
|
|
||||||
for every request.
|
|
||||||
|
|
||||||
The following snippet performs this last step for us:
|
|
||||||
|
|
||||||
.src/main/webapp/WEB-INF/web.xml
|
|
||||||
[source,xml,indent=0]
|
|
||||||
----
|
|
||||||
include::{samples-dir}xml/gemfire-p2p/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter]
|
|
||||||
----
|
|
||||||
|
|
||||||
The http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[DelegatingFilterProxy]
|
|
||||||
will look up a bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`. For every request that `DelegatingFilterProxy`
|
|
||||||
is invoked, the `springSessionRepositoryFilter` will be invoked.
|
|
||||||
// end::config[]
|
|
||||||
|
|
||||||
[[httpsession-gemfire-p2p-xml-sample-app]]
|
|
||||||
== HttpSession with GemFire (P2P) using XML Sample Application
|
|
||||||
|
|
||||||
|
|
||||||
=== Running the httpsession-gemfire-p2p-xml Sample Application
|
|
||||||
|
|
||||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
|
|
||||||
|
|
||||||
----
|
|
||||||
$ ./gradlew :samples:httpsession-gemfire-p2p-xml:tomcatRun [-Dsample.httpsession.gemfire.log-level=info]
|
|
||||||
----
|
|
||||||
|
|
||||||
You should now be able to access the application at http://localhost:8080/
|
|
||||||
|
|
||||||
=== Exploring the httpsession-gemfire-p2p-xml Sample Application
|
|
||||||
|
|
||||||
Try using the application. Fill out the form with the following information:
|
|
||||||
|
|
||||||
* **Attribute Name:** _username_
|
|
||||||
* **Attribute Value:** _john_
|
|
||||||
|
|
||||||
Now click the **Set Attribute** button. You should now see the values displayed in the table.
|
|
||||||
|
|
||||||
=== How does it work?
|
|
||||||
|
|
||||||
We interact with the standard `HttpSession` in the `SessionServlet` shown below:
|
|
||||||
|
|
||||||
.src/main/java/sample/SessionServlet.java
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
include::{samples-dir}xml/gemfire-p2p/src/main/java/sample/SessionServlet.java[tags=class]
|
|
||||||
----
|
|
||||||
|
|
||||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire.
|
|
||||||
Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
|
|
||||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome]
|
|
||||||
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
|
||||||
|
|
||||||
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
|
|
||||||
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
|
|
||||||
|
|
||||||
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
|
|
||||||
at the command-line:
|
|
||||||
|
|
||||||
$ gfsh
|
|
||||||
|
|
||||||
Then, enter the following into _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
|
|
||||||
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
|
|
||||||
|
|
||||||
....
|
|
||||||
gfsh>connect --jmx-manager=localhost[1099]
|
|
||||||
|
|
||||||
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
|
|
||||||
|
|
||||||
Result : true
|
|
||||||
startCount : 0
|
|
||||||
endCount : 20
|
|
||||||
Rows : 1
|
|
||||||
|
|
||||||
Result
|
|
||||||
------------------------------------
|
|
||||||
70002719-3c54-4c20-82c3-e7faa6b718f3
|
|
||||||
|
|
||||||
NEXT_STEP_NAME : END
|
|
||||||
|
|
||||||
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
|
|
||||||
....
|
|
||||||
|
|
||||||
NOTE: The _GemFire User Guide_ has more detailed instructions on using http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
|
|
||||||
|
|
||||||
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.
|
|
||||||
@@ -52,10 +52,6 @@ If you are looking to get started with Spring Session, the best place to start i
|
|||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis.
|
| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis.
|
||||||
| link:guides/boot.html[HttpSession with Redis Guide]
|
| link:guides/boot.html[HttpSession with Redis Guide]
|
||||||
|
|
||||||
| {gh-samples-url}boot/gemfire[HttpSession with GemFire]
|
|
||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a Client/Server topology.
|
|
||||||
| link:guides/boot-gemfire.html[HttpSession with GemFire Guide]
|
|
||||||
|
|
||||||
| {gh-samples-url}boot/mongo[HttpSession with Mongo]
|
| {gh-samples-url}boot/mongo[HttpSession with Mongo]
|
||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with Mongo.
|
| Demonstrates how to use Spring Session to replace the `HttpSession` with Mongo.
|
||||||
| link:guides/boot-mongo.html[HttpSession with Mongo Guide]
|
| link:guides/boot-mongo.html[HttpSession with Mongo Guide]
|
||||||
@@ -86,14 +82,6 @@ If you are looking to get started with Spring Session, the best place to start i
|
|||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis.
|
| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis.
|
||||||
| link:guides/java-redis.html[HttpSession with Redis Guide]
|
| link:guides/java-redis.html[HttpSession with Redis Guide]
|
||||||
|
|
||||||
| {gh-samples-url}javaconfig/gemfire-clientserver[HttpSession with GemFire (Client/Server)]
|
|
||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a Client/Server topology.
|
|
||||||
| link:guides/java-gemfire-clientserver.html[HttpSession with GemFire (Client/Server) Guide]
|
|
||||||
|
|
||||||
| {gh-samples-url}javaconfig/gemfire-p2p[HttpSession with GemFire (P2P)]
|
|
||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a P2P topology.
|
|
||||||
| link:guides/java-gemfire-p2p.html[HttpSession with GemFire (P2P) Guide]
|
|
||||||
|
|
||||||
| {gh-samples-url}javaconfig/jdbc[HttpSession with JDBC]
|
| {gh-samples-url}javaconfig/jdbc[HttpSession with JDBC]
|
||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store.
|
| Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store.
|
||||||
| link:guides/java-jdbc.html[HttpSession with JDBC Guide]
|
| link:guides/java-jdbc.html[HttpSession with JDBC Guide]
|
||||||
@@ -128,14 +116,6 @@ If you are looking to get started with Spring Session, the best place to start i
|
|||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a Redis store.
|
| Demonstrates how to use Spring Session to replace the `HttpSession` with a Redis store.
|
||||||
| link:guides/xml-redis.html[HttpSession with Redis Guide]
|
| link:guides/xml-redis.html[HttpSession with Redis Guide]
|
||||||
|
|
||||||
| {gh-samples-url}xml/gemfire-clientserver[HttpSession with GemFire (Client/Server)]
|
|
||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a Client/Server topology.
|
|
||||||
| link:guides/xml-gemfire-clientserver.html[HttpSession with GemFire (Client/Server) Guide]
|
|
||||||
|
|
||||||
| {gh-samples-url}xml/gemfire-p2p[HttpSession with GemFire (P2P)]
|
|
||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a P2P topology.
|
|
||||||
| link:guides/xml-gemfire-p2p.html[HttpSession with GemFire (P2P) Guide]
|
|
||||||
|
|
||||||
| {gh-samples-url}xml/jdbc[HttpSession with JDBC]
|
| {gh-samples-url}xml/jdbc[HttpSession with JDBC]
|
||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store.
|
| Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store.
|
||||||
| link:guides/xml-jdbc.html[HttpSession with JDBC Guide]
|
| link:guides/xml-jdbc.html[HttpSession with JDBC Guide]
|
||||||
@@ -200,110 +180,6 @@ You can read the basic steps for integration below, but you are encouraged to fo
|
|||||||
|
|
||||||
include::guides/xml-redis.adoc[tags=config,leveloffset=+3]
|
include::guides/xml-redis.adoc[tags=config,leveloffset=+3]
|
||||||
|
|
||||||
[[httpsession-gemfire]]
|
|
||||||
=== HttpSession with Pivotal GemFire
|
|
||||||
|
|
||||||
When https://pivotal.io/big-data/pivotal-gemfire[Pivotal GemFire] is used with Spring Session, a web application's
|
|
||||||
`HttpSession` can be replaced with a **clustered** implementation managed by GemFire and conveniently accessed
|
|
||||||
with Spring Session's API.
|
|
||||||
|
|
||||||
The two most common topologies to manage Spring Sessions using GemFire include:
|
|
||||||
|
|
||||||
* <<httpsession-gemfire-clientserver,Client-Server>>
|
|
||||||
* <<httpsession-gemfire-p2p,Peer-To-Peer (P2P)>>
|
|
||||||
|
|
||||||
Additionally, GemFire supports site-to-site replication using http://gemfire.docs.pivotal.io/docs-gemfire/topologies_and_comm/multi_site_configuration/chapter_overview.html[WAN functionality].
|
|
||||||
The ability to configure and use GemFire's WAN support is independent of Spring Session, and is beyond the scope
|
|
||||||
of this document. More details on GemFire WAN functionality can be found http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/#bootstrap:gateway[here].
|
|
||||||
|
|
||||||
[[httpsession-gemfire-clientserver]]
|
|
||||||
==== GemFire Client-Server
|
|
||||||
|
|
||||||
The http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client-Server]
|
|
||||||
topology will probably be the more common configuration preference for users when using GemFire as a provider in
|
|
||||||
Spring Session since a GemFire server will have significantly different and unique JVM heap requirements when compared
|
|
||||||
to the application. Using a client-server topology enables an application to manage (e.g. replicate) application state
|
|
||||||
independently from other application processes.
|
|
||||||
|
|
||||||
In a client-server topology, an application using Spring Session will open a client cache connection to a (remote)
|
|
||||||
GemFire server cluster to manage and provide consistent access to all `HttpSession` state.
|
|
||||||
|
|
||||||
You can configure a Client-Server topology with either:
|
|
||||||
|
|
||||||
* <<httpsession-gemfire-clientserver-java,Java-based Configuration>>
|
|
||||||
* <<httpsession-gemfire-clientserver-xml,XML-based Configuration>>
|
|
||||||
|
|
||||||
[[httpsession-gemfire-clientserver-java]]
|
|
||||||
===== GemFire Client-Server Java-based Configuration
|
|
||||||
|
|
||||||
This section describes how to use GemFire's Client-Server topology to back an `HttpSession` with Java-based configuration.
|
|
||||||
|
|
||||||
NOTE: The <<samples,HttpSession with GemFire (Client-Server) Sample>> provides a working sample on how to integrate
|
|
||||||
Spring Session and GemFire to replace the HttpSession using Java configuration. You can read the basic steps for
|
|
||||||
integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (Client-Server)
|
|
||||||
Guide when integrating with your own application.
|
|
||||||
|
|
||||||
include::guides/java-gemfire-clientserver.adoc[tags=config,leveloffset=+3]
|
|
||||||
|
|
||||||
[[http-session-gemfire-clientserver-xml]]
|
|
||||||
===== GemFire Client-Server XML-based Configuration
|
|
||||||
|
|
||||||
This section describes how to use GemFire's Client-Server topology to back an `HttpSession` with XML-based configuration.
|
|
||||||
|
|
||||||
NOTE: The <<samples,HttpSession with GemFire (Client-Server) using XML Sample>> provides a working sample on how to
|
|
||||||
integrate Spring Session and GemFire to replace the `HttpSession` using XML configuration. You can read the basic steps
|
|
||||||
for integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (Client-Server)
|
|
||||||
using XML Guide when integrating with your own application.
|
|
||||||
|
|
||||||
include::guides/xml-gemfire-clientserver.adoc[tags=config,leveloffset=+3]
|
|
||||||
|
|
||||||
[[httpsession-gemfire-p2p]]
|
|
||||||
==== GemFire Peer-To-Peer (P2P)
|
|
||||||
|
|
||||||
Perhaps less common would be to configure the Spring Session application as a peer member in the GemFire cluster using
|
|
||||||
the http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/p2p_configuration/chapter_overview.html[Peer-To-Peer (P2P)] topology.
|
|
||||||
In this configuration, a Spring Session application would be an actual data node (server) in the GemFire cluster,
|
|
||||||
and **not** a cache client as before.
|
|
||||||
|
|
||||||
One advantage to this approach is the proximity of the application to the application's state (i.e. it's data). However,
|
|
||||||
there are other effective means of accomplishing similar data dependent computations, such as using GemFire's
|
|
||||||
http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/function_exec/chapter_overview.html[Function Execution].
|
|
||||||
Any of GemFire's other http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/product_intro.html[features]
|
|
||||||
can be used when GemFire is serving as a provider in Spring Session.
|
|
||||||
|
|
||||||
P2P is very useful for both testing purposes as well as smaller, more focused and self-contained applications,
|
|
||||||
such as those found in a microservices architecture, and will most certainly improve on your application's latency,
|
|
||||||
throughput and consistency needs.
|
|
||||||
|
|
||||||
You can configure a Peer-To-Peer (P2P) topology with either:
|
|
||||||
|
|
||||||
* <<httpsession-gemfire-p2p-java,Java-based Configuration>>
|
|
||||||
* <<httpsession-gemfire-p2p-xml,XML-based Configuration>>
|
|
||||||
|
|
||||||
[[httpsession-gemfire-p2p-java]]
|
|
||||||
===== GemFire Peer-To-Peer (P2P) Java-based Configuration
|
|
||||||
|
|
||||||
This section describes how to use GemFire's Peer-To-Peer (P2P) topology to back an `HttpSession` using Java-based configuration.
|
|
||||||
|
|
||||||
NOTE: The <<samples, HttpSession with GemFire (P2P) Sample>> provides a working sample on how to integrate
|
|
||||||
Spring Session and GemFire to replace the `HttpSession` using Java configuration. You can read the basic steps
|
|
||||||
for integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (P2P) Guide
|
|
||||||
when integrating with your own application.
|
|
||||||
|
|
||||||
include::guides/java-gemfire-p2p.adoc[tags=config,leveloffset=+3]
|
|
||||||
|
|
||||||
[[httpsession-gemfire-p2p-xml]]
|
|
||||||
===== GemFire Peer-To-Peer (P2P) XML-based Configuration
|
|
||||||
|
|
||||||
This section describes how to use GemFire's Peer-To-Peer (P2P) topology to back an `HttpSession` using XML-based configuration.
|
|
||||||
|
|
||||||
NOTE: The <<samples, HttpSession with GemFire (P2P) using XML Sample>> provides a working sample on how to integrate
|
|
||||||
Spring Session and GemFire to replace the `HttpSession` using XML configuration. You can read the basic steps for
|
|
||||||
integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (P2P) using XML
|
|
||||||
Guide when integrating with your own application.
|
|
||||||
|
|
||||||
include::guides/xml-gemfire-p2p.adoc[tags=config,leveloffset=+3]
|
|
||||||
|
|
||||||
[[httpsession-jdbc]]
|
[[httpsession-jdbc]]
|
||||||
=== HttpSession with JDBC
|
=== HttpSession with JDBC
|
||||||
|
|
||||||
@@ -952,89 +828,6 @@ redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed
|
|||||||
"\xac\xed\x00\x05t\x00\x03rob"
|
"\xac\xed\x00\x05t\x00\x03rob"
|
||||||
----
|
----
|
||||||
|
|
||||||
[[api-gemfireoperationssessionrepository]]
|
|
||||||
=== GemFireOperationsSessionRepository
|
|
||||||
|
|
||||||
`GemFireOperationsSessionRepository` is a `SessionRepository` that is implemented using Spring Data's `GemFireOperationsSessionRepository`.
|
|
||||||
In a web environment, this is typically used in combination with `SessionRepositoryFilter`.
|
|
||||||
The implementation supports `SessionDestroyedEvent` and `SessionCreatedEvent` through `SessionMessageListener`.
|
|
||||||
|
|
||||||
[[api-gemfireoperationssessionrepository-indexing]]
|
|
||||||
==== Using Indexes with GemFire
|
|
||||||
|
|
||||||
While best practices concerning the proper definition of indexes that positively impact GemFire's performance is beyond
|
|
||||||
the scope of this document, it is important to realize that Spring Session Data GemFire creates and uses indexes to
|
|
||||||
query and find Sessions efficiently.
|
|
||||||
|
|
||||||
Out-of-the-box, Spring Session Data GemFire creates 1 Hash-typed Index on the principal name. There are two different buit in
|
|
||||||
strategies for finding the principal name. The first strategy is that the value of the session attribute with the name
|
|
||||||
`FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME` will be indexed to the same index name. For example:
|
|
||||||
|
|
||||||
[source,java,indent=0]
|
|
||||||
----
|
|
||||||
include::{docs-itest-dir}docs/http/HttpSessionGemFireIndexingITests.java[tags=findbyindexname-set]
|
|
||||||
include::{docs-itest-dir}docs/http/HttpSessionGemFireIndexingITests.java[tags=findbyindexname-get]
|
|
||||||
----
|
|
||||||
|
|
||||||
[[api-gemfireoperationssessionrepository-indexing-security]]
|
|
||||||
==== Using Indexes with GemFire & Spring Security
|
|
||||||
|
|
||||||
Alternatively, Spring Session Data GemFire will map Spring Security's current `Authentication#getName()` to the index
|
|
||||||
`FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME`. For example, if you are using Spring Security you can
|
|
||||||
find the current user's sessions using:
|
|
||||||
|
|
||||||
[source,java,indent=0]
|
|
||||||
----
|
|
||||||
include::{docs-itest-dir}docs/http/HttpSessionGemFireIndexingITests.java[tags=findbyspringsecurityindexname-context]
|
|
||||||
include::{docs-itest-dir}docs/http/HttpSessionGemFireIndexingITests.java[tags=findbyspringsecurityindexname-get]
|
|
||||||
----
|
|
||||||
|
|
||||||
[[api-gemfireoperationssessionrepository-indexing-custom]]
|
|
||||||
==== Using Custom Indexes with GemFire
|
|
||||||
|
|
||||||
This enables developers using the `GemFireOperationsSessionRepository` programmatically to query and find all Sessions
|
|
||||||
with a given principal name efficiently.
|
|
||||||
|
|
||||||
Additionally, Spring Session Data GemFire will create a Range-based Index on the implementing Session's Map-type
|
|
||||||
`attributes` property (i.e. on any arbitrary Session attribute) when a developer identifies 1 or more named Session
|
|
||||||
attributes that should be indexed by GemFire.
|
|
||||||
|
|
||||||
Sessions attributes to index can be specified with the `indexableSessionAttributes` attribute on the `@EnableGemFireHttpSession`
|
|
||||||
annotation. A developer adds this annotation to their Spring application `@Configuration` class when s/he wishes to
|
|
||||||
enable Spring Session support for HttpSession backed by GemFire.
|
|
||||||
|
|
||||||
For example, the following configuration:
|
|
||||||
|
|
||||||
[source,java,indent=0]
|
|
||||||
----
|
|
||||||
include::{docs-itest-dir}docs/http/gemfire/indexablesessionattributes/GemFireHttpSessionConfig.java[tags=class-start]
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
----
|
|
||||||
|
|
||||||
will allow searching for sessions using the following:
|
|
||||||
|
|
||||||
[source,java,indent=0]
|
|
||||||
----
|
|
||||||
include::{docs-itest-dir}docs/http/gemfire/indexablesessionattributes/HttpSessionGemFireIndexingCustomITests.java[tags=findbyindexname-set]
|
|
||||||
include::{docs-itest-dir}docs/http/gemfire/indexablesessionattributes/HttpSessionGemFireIndexingCustomITests.java[tags=findbyindexname-get]
|
|
||||||
----
|
|
||||||
|
|
||||||
NOTE: Only Session attribute names identified in the `@EnableGemFireHttpSession` annotation's `indexableSessionAttributes`
|
|
||||||
attribute will have an Index defined. All other Session attributes will not be indexed.
|
|
||||||
|
|
||||||
However, there is one caveat. Any values stored in indexable Session attributes must implement the `java.lang.Comparable<T>`
|
|
||||||
interface. If those object values do not implement `Comparable`, then GemFire will throw an error on startup when the
|
|
||||||
Index is defined for Regions with persistent Session data, or when an attempt is made at runtime to assign the indexable
|
|
||||||
Session attribute a value that is not `Comparable` and the Session is saved to GemFire.
|
|
||||||
|
|
||||||
NOTE: Any Session attribute that is not indexed may store non-`Comparable` values.
|
|
||||||
|
|
||||||
To learn more about GemFire's Range-based Indexes, see http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/creating_map_indexes.html[Creating Indexes on Map Fields].
|
|
||||||
|
|
||||||
To learn more about GemFire Indexing in general, see http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/query_index.html[Working with Indexes].
|
|
||||||
|
|
||||||
|
|
||||||
[[api-mapsessionrepository]]
|
[[api-mapsessionrepository]]
|
||||||
=== MapSessionRepository
|
=== MapSessionRepository
|
||||||
|
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package docs.http;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.data.gemfire.config.annotation.PeerCacheApplication;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.test.context.support.WithMockUser;
|
|
||||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
|
||||||
import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Rob Winch
|
|
||||||
* @author John Blum
|
|
||||||
*/
|
|
||||||
@RunWith(SpringRunner.class)
|
|
||||||
@ContextConfiguration(classes = HttpSessionGemFireIndexingITests.Config.class)
|
|
||||||
public class HttpSessionGemFireIndexingITests {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private GemFireOperationsSessionRepository sessionRepository;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void findByIndexName() {
|
|
||||||
ExpiringSession session = this.sessionRepository.createSession();
|
|
||||||
String username = "HttpSessionGemFireIndexingITests-findByIndexName-username";
|
|
||||||
|
|
||||||
// tag::findbyindexname-set[]
|
|
||||||
String indexName = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
|
|
||||||
|
|
||||||
session.setAttribute(indexName, username);
|
|
||||||
// end::findbyindexname-set[]
|
|
||||||
|
|
||||||
this.sessionRepository.save(session);
|
|
||||||
|
|
||||||
// tag::findbyindexname-get[]
|
|
||||||
Map<String, ExpiringSession> idToSessions =
|
|
||||||
this.sessionRepository.findByIndexNameAndIndexValue(indexName, username);
|
|
||||||
// end::findbyindexname-get[]
|
|
||||||
|
|
||||||
assertThat(idToSessions.keySet()).containsOnly(session.getId());
|
|
||||||
|
|
||||||
this.sessionRepository.delete(session.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@WithMockUser("HttpSessionGemFireIndexingITests-findBySpringSecurityIndexName")
|
|
||||||
public void findBySpringSecurityIndexName() {
|
|
||||||
ExpiringSession session = this.sessionRepository.createSession();
|
|
||||||
|
|
||||||
// tag::findbyspringsecurityindexname-context[]
|
|
||||||
SecurityContext context = SecurityContextHolder.getContext();
|
|
||||||
Authentication authentication = context.getAuthentication();
|
|
||||||
// end::findbyspringsecurityindexname-context[]
|
|
||||||
|
|
||||||
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context);
|
|
||||||
this.sessionRepository.save(session);
|
|
||||||
|
|
||||||
// tag::findbyspringsecurityindexname-get[]
|
|
||||||
String indexName = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
|
|
||||||
Map<String, ExpiringSession> idToSessions =
|
|
||||||
this.sessionRepository.findByIndexNameAndIndexValue(indexName, authentication.getName());
|
|
||||||
// end::findbyspringsecurityindexname-get[]
|
|
||||||
|
|
||||||
assertThat(idToSessions.keySet()).containsOnly(session.getId());
|
|
||||||
|
|
||||||
this.sessionRepository.delete(session.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@PeerCacheApplication(name = "HttpSessionGemFireIndexingITests", logLevel = "error")
|
|
||||||
@EnableGemFireHttpSession(regionName = "HttpSessionGemFireIndexingTestRegion")
|
|
||||||
static class Config {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package docs.http.gemfire.indexablesessionattributes;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.data.gemfire.config.annotation.PeerCacheApplication;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Rob Winch
|
|
||||||
* @author John Blum
|
|
||||||
*/
|
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
|
||||||
@ContextConfiguration(classes = HttpSessionGemFireIndexingCustomITests.Config.class)
|
|
||||||
public class HttpSessionGemFireIndexingCustomITests {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private GemFireOperationsSessionRepository sessionRepository;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void findByIndexName() {
|
|
||||||
ExpiringSession session = this.sessionRepository.createSession();
|
|
||||||
String attrValue = "HttpSessionGemFireIndexingCustomITests-findByIndexName";
|
|
||||||
|
|
||||||
// tag::findbyindexname-set[]
|
|
||||||
String indexName = "name1";
|
|
||||||
session.setAttribute(indexName, attrValue);
|
|
||||||
// end::findbyindexname-set[]
|
|
||||||
|
|
||||||
this.sessionRepository.save(session);
|
|
||||||
|
|
||||||
// tag::findbyindexname-get[]
|
|
||||||
Map<String, ExpiringSession> idToSessions =
|
|
||||||
this.sessionRepository.findByIndexNameAndIndexValue(indexName, attrValue);
|
|
||||||
// end::findbyindexname-get[]
|
|
||||||
|
|
||||||
assertThat(idToSessions.keySet()).containsOnly(session.getId());
|
|
||||||
|
|
||||||
this.sessionRepository.delete(session.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@PeerCacheApplication(name = "HttpSessionGemFireIndexingCustomITests", logLevel = "error")
|
|
||||||
@EnableGemFireHttpSession(indexableSessionAttributes = { "name1", "name2", "name3" },
|
|
||||||
regionName = "HttpSessionGemFireIndexingCustomTestRegion")
|
|
||||||
static class Config {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,9 +13,6 @@ configurations.spring3TestRuntime {
|
|||||||
if (details.requested.name == 'spring-data-commons') {
|
if (details.requested.name == 'spring-data-commons') {
|
||||||
details.useVersion '1.12.1.RELEASE'
|
details.useVersion '1.12.1.RELEASE'
|
||||||
}
|
}
|
||||||
if (details.requested.name == 'spring-data-gemfire') {
|
|
||||||
details.useVersion '1.8.1.RELEASE'
|
|
||||||
}
|
|
||||||
if (details.requested.name == 'spring-data-keyvalue') {
|
if (details.requested.name == 'spring-data-keyvalue') {
|
||||||
details.useVersion '1.1.1.RELEASE'
|
details.useVersion '1.1.1.RELEASE'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
|
||||||
apply plugin: "application"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile project(':spring-session-data-gemfire')
|
|
||||||
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
|
|
||||||
compile "org.springframework.boot:spring-boot-starter-web"
|
|
||||||
compile "org.webjars:bootstrap"
|
|
||||||
compile "org.webjars:webjars-locator"
|
|
||||||
|
|
||||||
runtime "org.springframework.shell:spring-shell"
|
|
||||||
|
|
||||||
testCompile "org.springframework.boot:spring-boot-starter-test"
|
|
||||||
|
|
||||||
integrationTestCompile seleniumDependencies
|
|
||||||
|
|
||||||
integrationTestRuntime "org.springframework.shell:spring-shell"
|
|
||||||
}
|
|
||||||
|
|
||||||
run {
|
|
||||||
doFirst {
|
|
||||||
mainClassName = 'sample.server.GemFireServer'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bootJar {
|
|
||||||
mainClass = 'sample.client.Application'
|
|
||||||
}
|
|
||||||
|
|
||||||
task runGemFireServer() {
|
|
||||||
doLast {
|
|
||||||
ext.port = reservePort()
|
|
||||||
|
|
||||||
println "Starting GemFire Server on port [$port] ..."
|
|
||||||
|
|
||||||
def out = new StringBuilder()
|
|
||||||
def err = new StringBuilder()
|
|
||||||
|
|
||||||
String classpath = sourceSets.main.runtimeClasspath.collect { it }.join(File.pathSeparator)
|
|
||||||
|
|
||||||
String[] commandLine = [
|
|
||||||
'java', '-server', '-ea', '-classpath', classpath,
|
|
||||||
//"-Dgemfire.log-file=gemfire-server.log",
|
|
||||||
//"-Dgemfire.log-level=config",
|
|
||||||
"-Dspring-session-data-gemfire.cache.server.port=$port",
|
|
||||||
"-Dspring-session-data-gemfire.log.level="
|
|
||||||
+ System.getProperty('spring-session-data-gemfire.log.level', 'warning'),
|
|
||||||
'sample.server.GemFireServer'
|
|
||||||
]
|
|
||||||
|
|
||||||
//println commandLine
|
|
||||||
|
|
||||||
ext.process = commandLine.execute()
|
|
||||||
//ext.process = new ProcessBuilder().command(commandLine).redirectErrorStream(true).start();
|
|
||||||
|
|
||||||
ext.process.consumeProcessOutput(out, err)
|
|
||||||
|
|
||||||
//println 'OUT: ' + out
|
|
||||||
//println 'ERR: ' + err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
integrationTest {
|
|
||||||
dependsOn runGemFireServer
|
|
||||||
doFirst {
|
|
||||||
def port = reservePort()
|
|
||||||
systemProperties['management.port'] = 0
|
|
||||||
systemProperties['server.port'] = port
|
|
||||||
//systemProperties['gemfire.log-file'] = "gemfire-client.log"
|
|
||||||
//systemProperties['gemfire.log-level'] = "config"
|
|
||||||
systemProperties['spring-session-data-gemfire.cache.server.port'] = runGemFireServer.port
|
|
||||||
systemProperties['spring-session-data-gemfire.log.level'] = System.getProperty("spring-session-data-gemfire.log.level", "warning")
|
|
||||||
}
|
|
||||||
doLast {
|
|
||||||
println 'Stopping GemFire Server...'
|
|
||||||
runGemFireServer.process?.destroyForcibly()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def reservePort() {
|
|
||||||
def socket = new ServerSocket(0)
|
|
||||||
def result = socket.localPort
|
|
||||||
socket.close()
|
|
||||||
result
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
import sample.client.Application;
|
|
||||||
import sample.pages.HomePage;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
|
||||||
import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDriverBuilder;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Eddú Meléndez
|
|
||||||
*/
|
|
||||||
@RunWith(SpringRunner.class)
|
|
||||||
@SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.MOCK)
|
|
||||||
@AutoConfigureMockMvc
|
|
||||||
public class AttributeTests {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private MockMvc mockMvc;
|
|
||||||
|
|
||||||
private WebDriver driver;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
this.driver.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void noAttributes() {
|
|
||||||
HomePage home = HomePage.go(this.driver, HomePage.class);
|
|
||||||
assertThat(home.attributes().size()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void createAttribute() {
|
|
||||||
HomePage home = HomePage.go(this.driver, HomePage.class);
|
|
||||||
home = home.form()
|
|
||||||
.attributeName("a")
|
|
||||||
.attributeValue("b")
|
|
||||||
.submit(HomePage.class);
|
|
||||||
assertThat(home.attributes()).extracting("attributeName").containsOnly("requestCount", "a");
|
|
||||||
assertThat(home.attributes()).extracting("attributeValue").containsOnly("1", "b");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.pages;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.openqa.selenium.By;
|
|
||||||
import org.openqa.selenium.Cookie;
|
|
||||||
import org.openqa.selenium.SearchContext;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
import org.openqa.selenium.WebElement;
|
|
||||||
import org.openqa.selenium.support.FindBy;
|
|
||||||
import org.openqa.selenium.support.PageFactory;
|
|
||||||
import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Eddú Meléndez
|
|
||||||
* @author Rob Winch
|
|
||||||
*/
|
|
||||||
public class HomePage {
|
|
||||||
|
|
||||||
private WebDriver driver;
|
|
||||||
|
|
||||||
@FindBy(tagName = "form")
|
|
||||||
WebElement form;
|
|
||||||
|
|
||||||
@FindBy(css = "table tbody tr")
|
|
||||||
List<WebElement> trs;
|
|
||||||
|
|
||||||
List<Attribute> attributes;
|
|
||||||
|
|
||||||
public HomePage(WebDriver driver) {
|
|
||||||
this.driver = driver;
|
|
||||||
this.attributes = new ArrayList<Attribute>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void get(WebDriver driver, String get) {
|
|
||||||
String baseUrl = "http://localhost:" + System.getProperty("app.port", "8080");
|
|
||||||
driver.get(baseUrl + get);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T go(WebDriver driver, Class<T> page) {
|
|
||||||
get(driver, "/");
|
|
||||||
return PageFactory.initElements(driver, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void containCookie(String cookieName) {
|
|
||||||
Set<Cookie> cookies = this.driver.manage().getCookies();
|
|
||||||
assertThat(cookies).extracting("name").contains(cookieName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doesNotContainCookie(String cookieName) {
|
|
||||||
Set<Cookie> cookies = this.driver.manage().getCookies();
|
|
||||||
assertThat(cookies).extracting("name").doesNotContain(cookieName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HomePage logout() {
|
|
||||||
WebElement logout = this.driver
|
|
||||||
.findElement(By.cssSelector("input[type=\"submit\"]"));
|
|
||||||
logout.click();
|
|
||||||
return PageFactory.initElements(this.driver, HomePage.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Attribute> attributes() {
|
|
||||||
List<Attribute> rows = new ArrayList<Attribute>();
|
|
||||||
for (WebElement tr : this.trs) {
|
|
||||||
rows.add(new Attribute(tr));
|
|
||||||
}
|
|
||||||
this.attributes.addAll(rows);
|
|
||||||
return this.attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form form() {
|
|
||||||
return new Form(this.form);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Form {
|
|
||||||
@FindBy(name = "attributeName")
|
|
||||||
WebElement attributeName;
|
|
||||||
|
|
||||||
@FindBy(name = "attributeValue")
|
|
||||||
WebElement attributeValue;
|
|
||||||
|
|
||||||
@FindBy(css = "input[type=\"submit\"]")
|
|
||||||
WebElement submit;
|
|
||||||
|
|
||||||
public Form(SearchContext context) {
|
|
||||||
PageFactory.initElements(new DefaultElementLocatorFactory(context), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form attributeName(String text) {
|
|
||||||
this.attributeName.sendKeys(text);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form attributeValue(String text) {
|
|
||||||
this.attributeValue.sendKeys(text);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T submit(Class<T> page) {
|
|
||||||
this.submit.click();
|
|
||||||
return PageFactory.initElements(HomePage.this.driver, page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Attribute {
|
|
||||||
@FindBy(xpath = ".//td[1]")
|
|
||||||
WebElement attributeName;
|
|
||||||
|
|
||||||
@FindBy(xpath = ".//td[2]")
|
|
||||||
WebElement attributeValue;
|
|
||||||
|
|
||||||
public Attribute(SearchContext context) {
|
|
||||||
PageFactory.initElements(new DefaultElementLocatorFactory(context), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the attributeName
|
|
||||||
*/
|
|
||||||
public String getAttributeName() {
|
|
||||||
return this.attributeName.getText();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the attributeValue
|
|
||||||
*/
|
|
||||||
public String getAttributeValue() {
|
|
||||||
return this.attributeValue.getText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,336 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.client;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.client.ClientCache;
|
|
||||||
import org.apache.geode.cache.client.Pool;
|
|
||||||
import org.apache.geode.cache.client.PoolManager;
|
|
||||||
import org.apache.geode.cache.client.internal.PoolImpl;
|
|
||||||
import org.apache.geode.management.membership.ClientMembership;
|
|
||||||
import org.apache.geode.management.membership.ClientMembershipEvent;
|
|
||||||
import org.apache.geode.management.membership.ClientMembershipListenerAdapter;
|
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
|
||||||
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.config.xml.GemfireConstants;
|
|
||||||
import org.springframework.data.gemfire.support.ConnectionEndpoint;
|
|
||||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.ui.ModelMap;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Spring Boot, GemFire cache client, web application that reveals the current state of the HTTP Session.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @see javax.servlet.http.HttpSession
|
|
||||||
* @see org.springframework.boot.SpringApplication
|
|
||||||
* @see org.springframework.boot.autoconfigure.SpringBootApplication
|
|
||||||
* @see org.springframework.context.annotation.Bean
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
|
|
||||||
* @see org.springframework.stereotype.Controller
|
|
||||||
* @see org.apache.geode.cache.client.ClientCache
|
|
||||||
* @see org.apache.geode.cache.client.Pool
|
|
||||||
* @since 1.2.1
|
|
||||||
*/
|
|
||||||
// tag::class[]
|
|
||||||
@SpringBootApplication
|
|
||||||
@EnableGemFireHttpSession(poolName = "DEFAULT")// <1>
|
|
||||||
@Controller
|
|
||||||
public class Application {
|
|
||||||
|
|
||||||
static final long DEFAULT_TIMEOUT = TimeUnit.SECONDS.toMillis(60);
|
|
||||||
|
|
||||||
static final CountDownLatch LATCH = new CountDownLatch(1);
|
|
||||||
|
|
||||||
static final String DEFAULT_GEMFIRE_LOG_LEVEL = "warning";
|
|
||||||
static final String GEMFIRE_DEFAULT_POOL_NAME = "DEFAULT";
|
|
||||||
static final String INDEX_TEMPLATE_VIEW_NAME = "index";
|
|
||||||
static final String PING_RESPONSE = "PONG";
|
|
||||||
static final String REQUEST_COUNT_ATTRIBUTE_NAME = "requestCount";
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(Application.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
|
|
||||||
return new PropertySourcesPlaceholderConfigurer();
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties gemfireProperties() { // <2>
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
|
|
||||||
gemfireProperties.setProperty("name", applicationName());
|
|
||||||
//gemfireProperties.setProperty("log-file", "gemfire-client.log");
|
|
||||||
gemfireProperties.setProperty("log-level", logLevel());
|
|
||||||
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
String applicationName() {
|
|
||||||
return "spring-session-data-gemfire-boot-sample.".concat(getClass().getSimpleName());
|
|
||||||
}
|
|
||||||
|
|
||||||
String logLevel() {
|
|
||||||
return System.getProperty("spring-session-data-gemfire.log.level", DEFAULT_GEMFIRE_LOG_LEVEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
ClientCacheFactoryBean gemfireCache(
|
|
||||||
@Value("${spring-session-data-gemfire.cache.server.host:localhost}") String host,
|
|
||||||
@Value("${spring-session-data-gemfire.cache.server.port:12480}") int port) { // <3>
|
|
||||||
|
|
||||||
ClientCacheFactoryBean gemfireCache = new ClientCacheFactoryBean();
|
|
||||||
|
|
||||||
gemfireCache.setClose(true);
|
|
||||||
gemfireCache.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
// GemFire Pool settings <4>
|
|
||||||
gemfireCache.setKeepAlive(false);
|
|
||||||
gemfireCache.setPingInterval(TimeUnit.SECONDS.toMillis(5));
|
|
||||||
gemfireCache.setReadTimeout(intValue(TimeUnit.SECONDS.toMillis(15)));
|
|
||||||
gemfireCache.setRetryAttempts(1);
|
|
||||||
gemfireCache.setSubscriptionEnabled(true);
|
|
||||||
gemfireCache.setThreadLocalConnections(false);
|
|
||||||
gemfireCache.setServers(Collections.singletonList(newConnectionEndpoint(host, port)));
|
|
||||||
|
|
||||||
registerClientMembershipListener(); // <5>
|
|
||||||
|
|
||||||
return gemfireCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
int intValue(Number number) {
|
|
||||||
return number.intValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionEndpoint newConnectionEndpoint(String host, int port) {
|
|
||||||
return new ConnectionEndpoint(host, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerClientMembershipListener() {
|
|
||||||
ClientMembership.registerClientMembershipListener(new ClientMembershipListenerAdapter() {
|
|
||||||
@Override
|
|
||||||
public void memberJoined(ClientMembershipEvent event) {
|
|
||||||
LATCH.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
BeanPostProcessor gemfireClientServerReadyBeanPostProcessor(
|
|
||||||
@Value("${spring-session-data-gemfire.cache.server.host:localhost}") final String host,
|
|
||||||
@Value("${spring-session-data-gemfire.cache.server.port:12480}") final int port) { // <5>
|
|
||||||
|
|
||||||
return new BeanPostProcessor() {
|
|
||||||
|
|
||||||
private final AtomicBoolean checkGemFireServerIsRunning = new AtomicBoolean(true);
|
|
||||||
private final AtomicReference<Pool> defaultPool = new AtomicReference<Pool>(null);
|
|
||||||
|
|
||||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
|
||||||
if (shouldCheckWhetherGemFireServerIsRunning(bean, beanName)) {
|
|
||||||
try {
|
|
||||||
validateCacheClientNotified();
|
|
||||||
validateCacheClientSubscriptionQueueConnectionEstablished();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean shouldCheckWhetherGemFireServerIsRunning(Object bean, String beanName) {
|
|
||||||
return (isGemFireRegion(bean, beanName)
|
|
||||||
? checkGemFireServerIsRunning.compareAndSet(true, false)
|
|
||||||
: whenGemFireCache(bean, beanName));
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isGemFireRegion(Object bean, String beanName) {
|
|
||||||
return (GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME.equals(beanName)
|
|
||||||
|| bean instanceof Region);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean whenGemFireCache(Object bean, String beanName) {
|
|
||||||
if (bean instanceof ClientCache) {
|
|
||||||
defaultPool.compareAndSet(null, ((ClientCache) bean).getDefaultPool());
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateCacheClientNotified() throws InterruptedException {
|
|
||||||
boolean didNotTimeout = LATCH.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
|
|
||||||
|
|
||||||
Assert.state(didNotTimeout, String.format(
|
|
||||||
"GemFire Cache Server failed to start on host [%s] and port [%d]", host, port));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("all")
|
|
||||||
private void validateCacheClientSubscriptionQueueConnectionEstablished() throws InterruptedException {
|
|
||||||
boolean cacheClientSubscriptionQueueConnectionEstablished = false;
|
|
||||||
|
|
||||||
Pool pool = defaultIfNull(this.defaultPool.get(), GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME,
|
|
||||||
GEMFIRE_DEFAULT_POOL_NAME);
|
|
||||||
|
|
||||||
if (pool instanceof PoolImpl) {
|
|
||||||
long timeout = (System.currentTimeMillis() + DEFAULT_TIMEOUT);
|
|
||||||
|
|
||||||
while (System.currentTimeMillis() < timeout
|
|
||||||
&& !((PoolImpl) pool).isPrimaryUpdaterAlive()) {
|
|
||||||
|
|
||||||
synchronized (pool) {
|
|
||||||
TimeUnit.MILLISECONDS.timedWait(pool, 500L);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheClientSubscriptionQueueConnectionEstablished |=
|
|
||||||
((PoolImpl) pool).isPrimaryUpdaterAlive();
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.state(cacheClientSubscriptionQueueConnectionEstablished, String.format(
|
|
||||||
"Cache client subscription queue connection not established; GemFire Pool was [%s];"
|
|
||||||
+ " GemFire Pool configuration was [locators = %s, servers = %s]",
|
|
||||||
pool, pool.getLocators(), pool.getServers()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Pool defaultIfNull(Pool pool, String... poolNames) {
|
|
||||||
for (String poolName : poolNames) {
|
|
||||||
pool = (pool != null ? pool : PoolManager.find(poolName));
|
|
||||||
}
|
|
||||||
|
|
||||||
return pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping("/")
|
|
||||||
public String index() { // <6>
|
|
||||||
return INDEX_TEMPLATE_VIEW_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping(method = RequestMethod.GET, path = "/ping")
|
|
||||||
@ResponseBody
|
|
||||||
public String ping() { // <7>
|
|
||||||
return PING_RESPONSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping(method = RequestMethod.POST, path = "/session")
|
|
||||||
public String session(HttpSession session, ModelMap modelMap,
|
|
||||||
@RequestParam(name = "attributeName", required = false) String name,
|
|
||||||
@RequestParam(name = "attributeValue", required = false) String value) { // <8>
|
|
||||||
|
|
||||||
modelMap.addAttribute("sessionAttributes", attributes(setAttribute(updateRequestCount(session), name, value)));
|
|
||||||
|
|
||||||
return INDEX_TEMPLATE_VIEW_NAME;
|
|
||||||
}
|
|
||||||
// end::class[]
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
@SuppressWarnings("all")
|
|
||||||
HttpSession updateRequestCount(HttpSession session) {
|
|
||||||
synchronized (session) {
|
|
||||||
Integer currentRequestCount = (Integer) session.getAttribute(REQUEST_COUNT_ATTRIBUTE_NAME);
|
|
||||||
session.setAttribute(REQUEST_COUNT_ATTRIBUTE_NAME, nullSafeIncrement(currentRequestCount));
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
Integer nullSafeIncrement(Integer value) {
|
|
||||||
return (nullSafeIntValue(value) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
int nullSafeIntValue(Number value) {
|
|
||||||
return (value != null ? value.intValue() : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
HttpSession setAttribute(HttpSession session, String attributeName, String attributeValue) {
|
|
||||||
if (isSet(attributeName, attributeValue)) {
|
|
||||||
session.setAttribute(attributeName, attributeValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
boolean isSet(String... values) {
|
|
||||||
boolean set = true;
|
|
||||||
|
|
||||||
for (String value : values) {
|
|
||||||
set &= StringUtils.hasText(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
Map<String, String> attributes(HttpSession session) {
|
|
||||||
Map<String, String> sessionAttributes = new HashMap<String, String>();
|
|
||||||
|
|
||||||
for (String attributeName : toIterable(session.getAttributeNames())) {
|
|
||||||
sessionAttributes.put(attributeName, String.valueOf(session.getAttribute(attributeName)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return sessionAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
<T> Iterable<T> toIterable(final Enumeration<T> enumeration) {
|
|
||||||
return new Iterable<T>() {
|
|
||||||
public Iterator<T> iterator() {
|
|
||||||
return (enumeration == null ? Collections.<T>emptyIterator()
|
|
||||||
: CollectionUtils.toIterator(enumeration));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.server;
|
|
||||||
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Cache;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
|
||||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.server.CacheServerFactoryBean;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Spring Boot application bootstrapping a GemFire Cache Server JVM process.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @see org.springframework.boot.SpringApplication
|
|
||||||
* @see org.springframework.boot.autoconfigure.SpringBootApplication
|
|
||||||
* @see org.springframework.context.annotation.Bean
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
|
|
||||||
* @see org.apache.geode.cache.Cache
|
|
||||||
* @since 1.2.1
|
|
||||||
*/
|
|
||||||
// tag::class[]
|
|
||||||
@SpringBootApplication
|
|
||||||
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 20) // <1>
|
|
||||||
public class GemFireServer {
|
|
||||||
|
|
||||||
static final String DEFAULT_GEMFIRE_LOG_LEVEL = "warning";
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication springApplication = new SpringApplication(GemFireServer.class);
|
|
||||||
springApplication.setWebEnvironment(false);
|
|
||||||
springApplication.run(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
|
|
||||||
return new PropertySourcesPlaceholderConfigurer();
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties gemfireProperties() { // <2>
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
|
|
||||||
gemfireProperties.setProperty("name", applicationName());
|
|
||||||
//gemfireProperties.setProperty("log-file", "gemfire-server.log");
|
|
||||||
gemfireProperties.setProperty("log-level", logLevel());
|
|
||||||
//gemfireProperties.setProperty("jmx-manager", "true");
|
|
||||||
//gemfireProperties.setProperty("jmx-manager-start", "true");
|
|
||||||
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
String applicationName() {
|
|
||||||
return "spring-session-data-gemfire-boot-sample:".concat(getClass().getSimpleName());
|
|
||||||
}
|
|
||||||
|
|
||||||
String logLevel() {
|
|
||||||
return System.getProperty("spring-session-data-gemfire.log.level", DEFAULT_GEMFIRE_LOG_LEVEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheFactoryBean gemfireCache() { // <3>
|
|
||||||
CacheFactoryBean gemfireCache = new CacheFactoryBean();
|
|
||||||
|
|
||||||
gemfireCache.setClose(true);
|
|
||||||
gemfireCache.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
return gemfireCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache,
|
|
||||||
@Value("${spring-session-data-gemfire.cache.server.bind-address:localhost}") String bindAddress,
|
|
||||||
@Value("${spring-session-data-gemfire.cache.server.hostname-for-clients:localhost}") String hostnameForClients,
|
|
||||||
@Value("${spring-session-data-gemfire.cache.server.port:12480}") int port) { // <4>
|
|
||||||
|
|
||||||
CacheServerFactoryBean gemfireCacheServer = new CacheServerFactoryBean();
|
|
||||||
|
|
||||||
gemfireCacheServer.setAutoStartup(true);
|
|
||||||
gemfireCacheServer.setBindAddress(bindAddress);
|
|
||||||
gemfireCacheServer.setCache(gemfireCache);
|
|
||||||
gemfireCacheServer.setHostNameForClients(hostnameForClients);
|
|
||||||
gemfireCacheServer.setMaxTimeBetweenPings(Long.valueOf(TimeUnit.SECONDS.toMillis(60)).intValue());
|
|
||||||
gemfireCacheServer.setPort(port);
|
|
||||||
|
|
||||||
return gemfireCacheServer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// end::class[]
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.server;
|
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Cache;
|
|
||||||
import org.apache.geode.cache.CacheFactory;
|
|
||||||
import org.apache.geode.cache.ExpirationAction;
|
|
||||||
import org.apache.geode.cache.ExpirationAttributes;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.RegionFactory;
|
|
||||||
import org.apache.geode.cache.RegionShortcut;
|
|
||||||
import org.apache.geode.cache.server.CacheServer;
|
|
||||||
|
|
||||||
import org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link NativeGemFireServer} class uses the GemFire API to create a GemFire (cache) instance.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @see org.apache.geode.cache.Cache
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
* @see org.apache.geode.cache.server.CacheServer
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
|
||||||
* @since 1.3.0
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public final class NativeGemFireServer implements Runnable {
|
|
||||||
|
|
||||||
private static final int GEMFIRE_CACHE_SERVER_PORT =
|
|
||||||
Integer.getInteger("spring-session-data-gemfire.cache.server.port", 12480);
|
|
||||||
|
|
||||||
private static final String GEMFIRE_CACHE_SERVER_HOST = "localhost";
|
|
||||||
private static final String GEMFIRE_CACHE_SERVER_HOSTNAME_FOR_CLIENTS = GEMFIRE_CACHE_SERVER_HOST;
|
|
||||||
private static final String GEMFIRE_LOG_FILENAME_PATTERN =
|
|
||||||
String.format("%s", NativeGemFireServer.class.getSimpleName()).concat("-%s.log");
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
newNativeGemFireServer(args).run();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String[] args;
|
|
||||||
|
|
||||||
private static File newGemFireLogFile(String suffix) {
|
|
||||||
return new File(String.format(GEMFIRE_LOG_FILENAME_PATTERN, suffix));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NativeGemFireServer newNativeGemFireServer(String[] args) {
|
|
||||||
return new NativeGemFireServer(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String[] nullSafeStringArray(String[] array) {
|
|
||||||
return (array != null ? array.clone() : new String[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void writeStringTo(File file, String fileContents) {
|
|
||||||
PrintWriter fileWriter = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
fileWriter = new PrintWriter(new BufferedWriter(new FileWriter(file, true)), true);
|
|
||||||
fileWriter.println(fileContents);
|
|
||||||
fileWriter.flush();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
throw new RuntimeException(String.format("Failed to write [%s] to file [%s]", fileContents, file), e);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (fileWriter != null) {
|
|
||||||
fileWriter.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private NativeGemFireServer(String[] args) {
|
|
||||||
this.args = nullSafeStringArray(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public void run() {
|
|
||||||
run(this.args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void run(String[] args) {
|
|
||||||
try {
|
|
||||||
writeStringTo(newGemFireLogFile("stdout"), "Before");
|
|
||||||
|
|
||||||
registerShutdownHook(addCacheServer(createRegion(gemfireCache(
|
|
||||||
gemfireProperties(applicationName())))));
|
|
||||||
|
|
||||||
writeStringTo(newGemFireLogFile("stdout"), "After");
|
|
||||||
}
|
|
||||||
catch (Throwable e) {
|
|
||||||
writeStringTo(newGemFireLogFile("stderr"), e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String applicationName() {
|
|
||||||
return applicationName(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String applicationName(String applicationName) {
|
|
||||||
return StringUtils.hasText(applicationName) ? applicationName
|
|
||||||
: "spring-session-data-gemfire.boot.sample." + NativeGemFireServer.class.getSimpleName();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Properties gemfireProperties(String applicationName) {
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
|
|
||||||
gemfireProperties.setProperty("name", applicationName);
|
|
||||||
gemfireProperties.setProperty("log-file", "gemfire-server.log");
|
|
||||||
gemfireProperties.setProperty("log-level", "config");
|
|
||||||
//gemfireProperties.setProperty("jmx-manager", "true");
|
|
||||||
//gemfireProperties.setProperty("jmx-manager-start", "true");
|
|
||||||
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Cache gemfireCache(Properties gemfireProperties) {
|
|
||||||
return new CacheFactory(gemfireProperties).create();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Cache createRegion(Cache gemfireCache) {
|
|
||||||
RegionFactory<Object, AbstractGemFireOperationsSessionRepository.GemFireSession> regionFactory =
|
|
||||||
gemfireCache.createRegionFactory(RegionShortcut.PARTITION);
|
|
||||||
|
|
||||||
regionFactory.setKeyConstraint(Object.class);
|
|
||||||
regionFactory.setValueConstraint(AbstractGemFireOperationsSessionRepository.GemFireSession.class);
|
|
||||||
regionFactory.setStatisticsEnabled(true);
|
|
||||||
regionFactory.setEntryIdleTimeout(newExpirationAttributes(1800, ExpirationAction.INVALIDATE));
|
|
||||||
|
|
||||||
Region region = regionFactory.create(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
|
|
||||||
return gemfireCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpirationAttributes newExpirationAttributes(int expirationTime, ExpirationAction expirationAction) {
|
|
||||||
return new ExpirationAttributes(expirationTime, expirationAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Cache addCacheServer(Cache gemfireCache) throws IOException {
|
|
||||||
CacheServer cacheServer = gemfireCache.addCacheServer();
|
|
||||||
|
|
||||||
cacheServer.setBindAddress(GEMFIRE_CACHE_SERVER_HOST);
|
|
||||||
cacheServer.setHostnameForClients(GEMFIRE_CACHE_SERVER_HOSTNAME_FOR_CLIENTS);
|
|
||||||
cacheServer.setPort(GEMFIRE_CACHE_SERVER_PORT);
|
|
||||||
cacheServer.start();
|
|
||||||
|
|
||||||
return gemfireCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Cache registerShutdownHook(final Cache gemfireCache) {
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (gemfireCache != null) {
|
|
||||||
gemfireCache.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
return gemfireCache;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-3.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
|
|
||||||
<head>
|
|
||||||
<title>Session Attributes</title>
|
|
||||||
<link rel="stylesheet" th:href="@{/webjars/bootstrap/css/bootstrap.min.css}" href="/webjars/bootstrap/css/bootstrap.min.css"/>
|
|
||||||
<style type="text/css">
|
|
||||||
body {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>Description</h1>
|
|
||||||
<p>
|
|
||||||
This application demonstrates how to use a GemFire instance to back your session. Notice that there
|
|
||||||
is no JSESSIONID cookie. We are also able to customize the way of identifying what the requested
|
|
||||||
session id is.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h1>Try it</h1>
|
|
||||||
|
|
||||||
<form class="form-inline" role="form" action="./session" method="post">
|
|
||||||
<label for="attributeName">Attribute Name</label>
|
|
||||||
<input id="attributeName" type="text" name="attributeName"/>
|
|
||||||
<label for="attributeValue">Attribute Value</label>
|
|
||||||
<input id="attributeValue" type="text" name="attributeValue"/>
|
|
||||||
<input type="submit" value="Set Attribute"/>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Attribute Name</th>
|
|
||||||
<th>Attribute Value</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr th:each="attribute: ${sessionAttributes}">
|
|
||||||
<td th:text="${attribute.key}">name</td>
|
|
||||||
<td th:text="${attribute.value}">value</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
apply plugin: 'io.spring.convention.spring-sample-war'
|
|
||||||
apply plugin: "gemfire-server"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile project(':spring-session-data-gemfire')
|
|
||||||
compile "org.springframework:spring-web"
|
|
||||||
compile "org.webjars:bootstrap"
|
|
||||||
compile "org.webjars:webjars-taglib"
|
|
||||||
compile jstlDependencies
|
|
||||||
compile slf4jDependencies
|
|
||||||
|
|
||||||
providedCompile "javax.servlet:javax.servlet-api"
|
|
||||||
|
|
||||||
runtime "org.springframework.shell:spring-shell"
|
|
||||||
|
|
||||||
testCompile "junit:junit"
|
|
||||||
testCompile "org.assertj:assertj-core"
|
|
||||||
|
|
||||||
integrationTestCompile seleniumDependencies
|
|
||||||
|
|
||||||
integrationTestRuntime "org.springframework.shell:spring-shell"
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
|
|
||||||
import sample.pages.HomePage;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Eddú Meléndez
|
|
||||||
*/
|
|
||||||
public class AttributeTests {
|
|
||||||
|
|
||||||
private WebDriver driver;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
this.driver = new HtmlUnitDriver();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
this.driver.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void noAttributes() {
|
|
||||||
HomePage home = HomePage.go(this.driver, HomePage.class);
|
|
||||||
assertThat(home.attributes().size()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void createAttribute() {
|
|
||||||
HomePage home = HomePage.go(this.driver, HomePage.class);
|
|
||||||
home = home.form()
|
|
||||||
.attributeName("a")
|
|
||||||
.attributeValue("b")
|
|
||||||
.submit(HomePage.class);
|
|
||||||
assertThat(home.attributes()).extracting("attributeName").containsOnly("a");
|
|
||||||
assertThat(home.attributes()).extracting("attributeValue").containsOnly("b");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.pages;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.openqa.selenium.By;
|
|
||||||
import org.openqa.selenium.Cookie;
|
|
||||||
import org.openqa.selenium.SearchContext;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
import org.openqa.selenium.WebElement;
|
|
||||||
import org.openqa.selenium.support.FindBy;
|
|
||||||
import org.openqa.selenium.support.PageFactory;
|
|
||||||
import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Eddú Meléndez
|
|
||||||
* @author Rob Winch
|
|
||||||
*/
|
|
||||||
public class HomePage {
|
|
||||||
|
|
||||||
private WebDriver driver;
|
|
||||||
|
|
||||||
@FindBy(tagName = "form")
|
|
||||||
WebElement form;
|
|
||||||
|
|
||||||
@FindBy(css = "table tbody tr")
|
|
||||||
List<WebElement> trs;
|
|
||||||
|
|
||||||
List<Attribute> attributes;
|
|
||||||
|
|
||||||
public HomePage(WebDriver driver) {
|
|
||||||
this.driver = driver;
|
|
||||||
this.attributes = new ArrayList<Attribute>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void get(WebDriver driver, String get) {
|
|
||||||
String baseUrl = "http://localhost:" + System.getProperty("app.port", "8080");
|
|
||||||
driver.get(baseUrl + get);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T go(WebDriver driver, Class<T> page) {
|
|
||||||
get(driver, "/");
|
|
||||||
return PageFactory.initElements(driver, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void containCookie(String cookieName) {
|
|
||||||
Set<Cookie> cookies = this.driver.manage().getCookies();
|
|
||||||
assertThat(cookies).extracting("name").contains(cookieName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doesNotContainCookie(String cookieName) {
|
|
||||||
Set<Cookie> cookies = this.driver.manage().getCookies();
|
|
||||||
assertThat(cookies).extracting("name").doesNotContain(cookieName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HomePage logout() {
|
|
||||||
WebElement logout = this.driver
|
|
||||||
.findElement(By.cssSelector("input[type=\"submit\"]"));
|
|
||||||
logout.click();
|
|
||||||
return PageFactory.initElements(this.driver, HomePage.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Attribute> attributes() {
|
|
||||||
List<Attribute> rows = new ArrayList<Attribute>();
|
|
||||||
for (WebElement tr : this.trs) {
|
|
||||||
rows.add(new Attribute(tr));
|
|
||||||
}
|
|
||||||
this.attributes.addAll(rows);
|
|
||||||
return this.attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form form() {
|
|
||||||
return new Form(this.form);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Form {
|
|
||||||
@FindBy(name = "attributeName")
|
|
||||||
WebElement attributeName;
|
|
||||||
|
|
||||||
@FindBy(name = "attributeValue")
|
|
||||||
WebElement attributeValue;
|
|
||||||
|
|
||||||
@FindBy(css = "input[type=\"submit\"]")
|
|
||||||
WebElement submit;
|
|
||||||
|
|
||||||
public Form(SearchContext context) {
|
|
||||||
PageFactory.initElements(new DefaultElementLocatorFactory(context), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form attributeName(String text) {
|
|
||||||
this.attributeName.sendKeys(text);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form attributeValue(String text) {
|
|
||||||
this.attributeValue.sendKeys(text);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T submit(Class<T> page) {
|
|
||||||
this.submit.click();
|
|
||||||
return PageFactory.initElements(HomePage.this.driver, page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Attribute {
|
|
||||||
@FindBy(xpath = ".//td[1]")
|
|
||||||
WebElement attributeName;
|
|
||||||
|
|
||||||
@FindBy(xpath = ".//td[2]")
|
|
||||||
WebElement attributeValue;
|
|
||||||
|
|
||||||
public Attribute(SearchContext context) {
|
|
||||||
PageFactory.initElements(new DefaultElementLocatorFactory(context), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the attributeName
|
|
||||||
*/
|
|
||||||
public String getAttributeName() {
|
|
||||||
return this.attributeName.getText();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the attributeValue
|
|
||||||
*/
|
|
||||||
public String getAttributeValue() {
|
|
||||||
return this.attributeValue.getText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.client.ClientCache;
|
|
||||||
import org.apache.geode.cache.client.Pool;
|
|
||||||
import org.apache.geode.cache.client.PoolManager;
|
|
||||||
import org.apache.geode.cache.client.internal.PoolImpl;
|
|
||||||
import org.apache.geode.management.membership.ClientMembership;
|
|
||||||
import org.apache.geode.management.membership.ClientMembershipEvent;
|
|
||||||
import org.apache.geode.management.membership.ClientMembershipListenerAdapter;
|
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
|
||||||
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.config.xml.GemfireConstants;
|
|
||||||
import org.springframework.data.gemfire.support.ConnectionEndpoint;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
// tag::class[]
|
|
||||||
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 30, poolName = "DEFAULT") // <1>
|
|
||||||
public class ClientConfig {
|
|
||||||
|
|
||||||
static final long DEFAULT_TIMEOUT = TimeUnit.SECONDS.toMillis(60);
|
|
||||||
|
|
||||||
static final CountDownLatch LATCH = new CountDownLatch(1);
|
|
||||||
|
|
||||||
static final String DEFAULT_GEMFIRE_LOG_LEVEL = "warning";
|
|
||||||
static final String GEMFIRE_DEFAULT_POOL_NAME = "DEFAULT";
|
|
||||||
static final String PROXY_HOST = "dummy.example.com";
|
|
||||||
static final String PROXY_PORT = "3128";
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
|
|
||||||
return new PropertySourcesPlaceholderConfigurer();
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties gemfireProperties() { // <2>
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
gemfireProperties.setProperty("name", applicationName());
|
|
||||||
// gemfireProperties.setProperty("log-file", "gemfire-client.log");
|
|
||||||
gemfireProperties.setProperty("log-level", logLevel());
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
String applicationName() {
|
|
||||||
return "spring-session-data-gemfire-clientserver-javaconfig-sample:".concat(getClass().getSimpleName());
|
|
||||||
}
|
|
||||||
|
|
||||||
String logLevel() {
|
|
||||||
return System.getProperty("spring.session.data.gemfire.log-level", DEFAULT_GEMFIRE_LOG_LEVEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
ClientCacheFactoryBean gemfireCache(
|
|
||||||
@Value("${spring.session.data.gemfire.host:" + ServerConfig.SERVER_HOST + "}") String host,
|
|
||||||
@Value("${spring.session.data.gemfire.port:" + ServerConfig.SERVER_PORT + "}") int port) { // <3>
|
|
||||||
|
|
||||||
ClientCacheFactoryBean clientCacheFactory = new ClientCacheFactoryBean();
|
|
||||||
|
|
||||||
clientCacheFactory.setClose(true);
|
|
||||||
clientCacheFactory.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
// GemFire Pool settings <4>
|
|
||||||
clientCacheFactory.setKeepAlive(false);
|
|
||||||
clientCacheFactory.setPingInterval(TimeUnit.SECONDS.toMillis(5));
|
|
||||||
clientCacheFactory.setReadTimeout(intValue(TimeUnit.SECONDS.toMillis(15)));
|
|
||||||
clientCacheFactory.setRetryAttempts(1);
|
|
||||||
clientCacheFactory.setSubscriptionEnabled(true);
|
|
||||||
clientCacheFactory.setThreadLocalConnections(false);
|
|
||||||
clientCacheFactory.setServers(Collections.singletonList(newConnectionEndpoint(host, port)));
|
|
||||||
|
|
||||||
registerClientMembershipListener(); // <5>
|
|
||||||
|
|
||||||
return clientCacheFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
int intValue(Number number) {
|
|
||||||
return number.intValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionEndpoint newConnectionEndpoint(String host, int port) {
|
|
||||||
return new ConnectionEndpoint(host, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerClientMembershipListener() {
|
|
||||||
ClientMembership.registerClientMembershipListener(
|
|
||||||
new ClientMembershipListenerAdapter() {
|
|
||||||
@Override
|
|
||||||
public void memberJoined(ClientMembershipEvent event) {
|
|
||||||
LATCH.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
BeanPostProcessor gemfireClientServerReadyBeanPostProcessor(
|
|
||||||
@Value("${spring-session-data-gemfire.cache.server.host:localhost}") final String host,
|
|
||||||
@Value("${spring-session-data-gemfire.cache.server.port:12480}") final int port) { // <5>
|
|
||||||
|
|
||||||
return new BeanPostProcessor() {
|
|
||||||
|
|
||||||
private final AtomicBoolean checkGemFireServerIsRunning = new AtomicBoolean(true);
|
|
||||||
private final AtomicReference<Pool> defaultPool = new AtomicReference<Pool>(null);
|
|
||||||
|
|
||||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
|
||||||
if (shouldCheckWhetherGemFireServerIsRunning(bean, beanName)) {
|
|
||||||
try {
|
|
||||||
validateCacheClientNotified();
|
|
||||||
validateCacheClientSubscriptionQueueConnectionEstablished();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean shouldCheckWhetherGemFireServerIsRunning(Object bean, String beanName) {
|
|
||||||
return (isGemFireRegion(bean, beanName)
|
|
||||||
? checkGemFireServerIsRunning.compareAndSet(true, false)
|
|
||||||
: whenGemFireCache(bean, beanName));
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isGemFireRegion(Object bean, String beanName) {
|
|
||||||
return (GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME.equals(beanName)
|
|
||||||
|| bean instanceof Region);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean whenGemFireCache(Object bean, String beanName) {
|
|
||||||
if (bean instanceof ClientCache) {
|
|
||||||
defaultPool.compareAndSet(null, ((ClientCache) bean).getDefaultPool());
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateCacheClientNotified() throws InterruptedException {
|
|
||||||
boolean didNotTimeout = LATCH.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
|
|
||||||
|
|
||||||
Assert.state(didNotTimeout, String.format(
|
|
||||||
"GemFire Cache Server failed to start on host [%s] and port [%d]", host, port));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("all")
|
|
||||||
private void validateCacheClientSubscriptionQueueConnectionEstablished() throws InterruptedException {
|
|
||||||
boolean cacheClientSubscriptionQueueConnectionEstablished = false;
|
|
||||||
|
|
||||||
Pool pool = defaultIfNull(this.defaultPool.get(), GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME,
|
|
||||||
GEMFIRE_DEFAULT_POOL_NAME);
|
|
||||||
|
|
||||||
if (pool instanceof PoolImpl) {
|
|
||||||
long timeout = (System.currentTimeMillis() + DEFAULT_TIMEOUT);
|
|
||||||
|
|
||||||
while (System.currentTimeMillis() < timeout
|
|
||||||
&& !((PoolImpl) pool).isPrimaryUpdaterAlive()) {
|
|
||||||
|
|
||||||
synchronized (pool) {
|
|
||||||
TimeUnit.MILLISECONDS.timedWait(pool, 500L);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheClientSubscriptionQueueConnectionEstablished |=
|
|
||||||
((PoolImpl) pool).isPrimaryUpdaterAlive();
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.state(cacheClientSubscriptionQueueConnectionEstablished, String.format(
|
|
||||||
"Cache client subscription queue connection not established; GemFire Pool was [%s];"
|
|
||||||
+ " GemFire Pool configuration was [locators = %s, servers = %s]",
|
|
||||||
pool, pool.getLocators(), pool.getServers()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Pool defaultIfNull(Pool pool, String... poolNames) {
|
|
||||||
for (String poolName : poolNames) {
|
|
||||||
pool = (pool != null ? pool : PoolManager.find(poolName));
|
|
||||||
}
|
|
||||||
|
|
||||||
return pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// end::class[]
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
|
|
||||||
|
|
||||||
// tag::class[]
|
|
||||||
public class Initializer extends AbstractHttpSessionApplicationInitializer { // <1>
|
|
||||||
|
|
||||||
public Initializer() {
|
|
||||||
super(ClientConfig.class); // <2>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// end::class[]
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Cache;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
|
||||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.server.CacheServerFactoryBean;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
|
||||||
|
|
||||||
// tag::class[]
|
|
||||||
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 30) // <1>
|
|
||||||
public class ServerConfig {
|
|
||||||
|
|
||||||
static final int SERVER_PORT = 12480;
|
|
||||||
|
|
||||||
static final String DEFAULT_GEMFIRE_LOG_LEVEL = "warning";
|
|
||||||
static final String SERVER_HOST = "localhost";
|
|
||||||
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
public static void main(String[] args) throws IOException { // <5>
|
|
||||||
new AnnotationConfigApplicationContext(ServerConfig.class).registerShutdownHook();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
|
|
||||||
return new PropertySourcesPlaceholderConfigurer();
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties gemfireProperties() { // <2>
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
|
|
||||||
gemfireProperties.setProperty("name", applicationName());
|
|
||||||
gemfireProperties.setProperty("mcast-port", "0");
|
|
||||||
// gemfireProperties.setProperty("log-file", "gemfire-server.log");
|
|
||||||
gemfireProperties.setProperty("log-level", logLevel());
|
|
||||||
// gemfireProperties.setProperty("jmx-manager", "true");
|
|
||||||
// gemfireProperties.setProperty("jmx-manager-start", "true");
|
|
||||||
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
String applicationName() {
|
|
||||||
return "samples:httpsession-gemfire-clientserver:".concat(getClass().getSimpleName());
|
|
||||||
}
|
|
||||||
|
|
||||||
String logLevel() {
|
|
||||||
return System.getProperty("sample.httpsession.gemfire.log-level", DEFAULT_GEMFIRE_LOG_LEVEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheFactoryBean gemfireCache() { // <3>
|
|
||||||
CacheFactoryBean gemfireCache = new CacheFactoryBean();
|
|
||||||
|
|
||||||
gemfireCache.setClose(true);
|
|
||||||
gemfireCache.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
return gemfireCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache,
|
|
||||||
@Value("${spring.session.data.gemfire.port:" + SERVER_PORT + "}") int port) { // <4>
|
|
||||||
|
|
||||||
CacheServerFactoryBean gemfireCacheServer = new CacheServerFactoryBean();
|
|
||||||
|
|
||||||
gemfireCacheServer.setAutoStartup(true);
|
|
||||||
gemfireCacheServer.setBindAddress(SERVER_HOST);
|
|
||||||
gemfireCacheServer.setCache(gemfireCache);
|
|
||||||
gemfireCacheServer.setHostNameForClients(SERVER_HOST);
|
|
||||||
gemfireCacheServer.setMaxTimeBetweenPings(Long.valueOf(TimeUnit.SECONDS.toMillis(60)).intValue());
|
|
||||||
gemfireCacheServer.setPort(port);
|
|
||||||
|
|
||||||
return gemfireCacheServer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// end::class[]
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.annotation.WebServlet;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
// tag::class[]
|
|
||||||
@WebServlet("/session")
|
|
||||||
public class SessionServlet extends HttpServlet {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doPost(HttpServletRequest request, HttpServletResponse response)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
String attributeName = request.getParameter("attributeName");
|
|
||||||
String attributeValue = request.getParameter("attributeValue");
|
|
||||||
request.getSession().setAttribute(attributeName, attributeValue);
|
|
||||||
response.sendRedirect(request.getContextPath() + "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 2878267318695777395L;
|
|
||||||
}
|
|
||||||
// tag::end[]
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
|
||||||
<%@ taglib prefix="wj" uri="http://www.webjars.org/tags" %>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>Session Attributes</title>
|
|
||||||
<wj:locate path="bootstrap.min.css" relativeTo="META-INF/resources" var="bootstrapCssLocation"/>
|
|
||||||
<link rel="stylesheet" href="<c:url value="${bootstrapCssLocation}"/>">
|
|
||||||
<style type="text/css">
|
|
||||||
body {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>Description</h1>
|
|
||||||
<p>This application demonstrates how to use a GemFire instance to back your session. Notice that there is no JSESSIONID cookie. We are also able to customize the way of identifying what the requested session id is.</p>
|
|
||||||
|
|
||||||
<h1>Try it</h1>
|
|
||||||
|
|
||||||
<form class="form-inline" role="form" action="./session" method="post">
|
|
||||||
<label for="attributeName">Attribute Name</label>
|
|
||||||
<input id="attributeName" type="text" name="attributeName"/>
|
|
||||||
<label for="attributeValue">Attribute Value</label>
|
|
||||||
<input id="attributeValue" type="text" name="attributeValue"/>
|
|
||||||
<input type="submit" value="Set Attribute"/>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Attribute Name</th>
|
|
||||||
<th>Attribute Value</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<c:forEach items="${sessionScope}" var="attr">
|
|
||||||
<tr>
|
|
||||||
<td><c:out value="${attr.key}"/></td>
|
|
||||||
<td><c:out value="${attr.value}"/></td>
|
|
||||||
</tr>
|
|
||||||
</c:forEach>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
apply plugin: 'io.spring.convention.spring-sample-war'
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile project(':spring-session-data-gemfire')
|
|
||||||
compile "org.springframework:spring-web"
|
|
||||||
compile "org.webjars:bootstrap"
|
|
||||||
compile "org.webjars:webjars-taglib"
|
|
||||||
compile jstlDependencies
|
|
||||||
compile slf4jDependencies
|
|
||||||
|
|
||||||
providedCompile "javax.servlet:javax.servlet-api"
|
|
||||||
|
|
||||||
testCompile "junit:junit"
|
|
||||||
|
|
||||||
integrationTestCompile seleniumDependencies
|
|
||||||
integrationTestCompile "org.assertj:assertj-core"
|
|
||||||
|
|
||||||
integrationTestRuntime "org.springframework.shell:spring-shell"
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
|
|
||||||
import sample.pages.HomePage;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Eddú Meléndez
|
|
||||||
*/
|
|
||||||
public class AttributeTests {
|
|
||||||
|
|
||||||
private WebDriver driver;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
this.driver = new HtmlUnitDriver();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
this.driver.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void noAttributes() {
|
|
||||||
HomePage home = HomePage.go(this.driver, HomePage.class);
|
|
||||||
assertThat(home.attributes().size()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void createAttribute() {
|
|
||||||
HomePage home = HomePage.go(this.driver, HomePage.class);
|
|
||||||
home = home.form()
|
|
||||||
.attributeName("a")
|
|
||||||
.attributeValue("b")
|
|
||||||
.submit(HomePage.class);
|
|
||||||
assertThat(home.attributes()).extracting("attributeName").containsOnly("a");
|
|
||||||
assertThat(home.attributes()).extracting("attributeValue").containsOnly("b");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.pages;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.openqa.selenium.By;
|
|
||||||
import org.openqa.selenium.Cookie;
|
|
||||||
import org.openqa.selenium.SearchContext;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
import org.openqa.selenium.WebElement;
|
|
||||||
import org.openqa.selenium.support.FindBy;
|
|
||||||
import org.openqa.selenium.support.PageFactory;
|
|
||||||
import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Eddú Meléndez
|
|
||||||
* @author Rob Winch
|
|
||||||
*/
|
|
||||||
public class HomePage {
|
|
||||||
|
|
||||||
private WebDriver driver;
|
|
||||||
|
|
||||||
@FindBy(tagName = "form")
|
|
||||||
WebElement form;
|
|
||||||
|
|
||||||
@FindBy(css = "table tbody tr")
|
|
||||||
List<WebElement> trs;
|
|
||||||
|
|
||||||
List<Attribute> attributes;
|
|
||||||
|
|
||||||
public HomePage(WebDriver driver) {
|
|
||||||
this.driver = driver;
|
|
||||||
this.attributes = new ArrayList<Attribute>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void get(WebDriver driver, String get) {
|
|
||||||
String baseUrl = "http://localhost:" + System.getProperty("app.port", "8080");
|
|
||||||
driver.get(baseUrl + get);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T go(WebDriver driver, Class<T> page) {
|
|
||||||
get(driver, "/");
|
|
||||||
return PageFactory.initElements(driver, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void containCookie(String cookieName) {
|
|
||||||
Set<Cookie> cookies = this.driver.manage().getCookies();
|
|
||||||
assertThat(cookies).extracting("name").contains(cookieName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doesNotContainCookie(String cookieName) {
|
|
||||||
Set<Cookie> cookies = this.driver.manage().getCookies();
|
|
||||||
assertThat(cookies).extracting("name").doesNotContain(cookieName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HomePage logout() {
|
|
||||||
WebElement logout = this.driver
|
|
||||||
.findElement(By.cssSelector("input[type=\"submit\"]"));
|
|
||||||
logout.click();
|
|
||||||
return PageFactory.initElements(this.driver, HomePage.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Attribute> attributes() {
|
|
||||||
List<Attribute> rows = new ArrayList<Attribute>();
|
|
||||||
for (WebElement tr : this.trs) {
|
|
||||||
rows.add(new Attribute(tr));
|
|
||||||
}
|
|
||||||
this.attributes.addAll(rows);
|
|
||||||
return this.attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form form() {
|
|
||||||
return new Form(this.form);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Form {
|
|
||||||
@FindBy(name = "attributeName")
|
|
||||||
WebElement attributeName;
|
|
||||||
|
|
||||||
@FindBy(name = "attributeValue")
|
|
||||||
WebElement attributeValue;
|
|
||||||
|
|
||||||
@FindBy(css = "input[type=\"submit\"]")
|
|
||||||
WebElement submit;
|
|
||||||
|
|
||||||
public Form(SearchContext context) {
|
|
||||||
PageFactory.initElements(new DefaultElementLocatorFactory(context), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form attributeName(String text) {
|
|
||||||
this.attributeName.sendKeys(text);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form attributeValue(String text) {
|
|
||||||
this.attributeValue.sendKeys(text);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T submit(Class<T> page) {
|
|
||||||
this.submit.click();
|
|
||||||
return PageFactory.initElements(HomePage.this.driver, page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Attribute {
|
|
||||||
@FindBy(xpath = ".//td[1]")
|
|
||||||
WebElement attributeName;
|
|
||||||
|
|
||||||
@FindBy(xpath = ".//td[2]")
|
|
||||||
WebElement attributeValue;
|
|
||||||
|
|
||||||
public Attribute(SearchContext context) {
|
|
||||||
PageFactory.initElements(new DefaultElementLocatorFactory(context), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the attributeName
|
|
||||||
*/
|
|
||||||
public String getAttributeName() {
|
|
||||||
return this.attributeName.getText();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the attributeValue
|
|
||||||
*/
|
|
||||||
public String getAttributeValue() {
|
|
||||||
return this.attributeValue.getText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
|
||||||
|
|
||||||
// tag::class[]
|
|
||||||
@EnableGemFireHttpSession // <1>
|
|
||||||
public class Config {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
Properties gemfireProperties() { // <2>
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
|
|
||||||
gemfireProperties.setProperty("name", "GemFireP2PHttpSessionSample");
|
|
||||||
gemfireProperties.setProperty("mcast-port", "0");
|
|
||||||
gemfireProperties.setProperty("log-level",
|
|
||||||
System.getProperty("sample.httpsession.gemfire.log-level", "warning"));
|
|
||||||
gemfireProperties.setProperty("jmx-manager", "true");
|
|
||||||
gemfireProperties.setProperty("jmx-manager-start", "true");
|
|
||||||
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheFactoryBean gemfireCache() { // <3>
|
|
||||||
CacheFactoryBean gemfireCache = new CacheFactoryBean();
|
|
||||||
|
|
||||||
gemfireCache.setClose(true);
|
|
||||||
gemfireCache.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
return gemfireCache;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// end::class[]
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
|
|
||||||
|
|
||||||
// tag::class[]
|
|
||||||
public class Initializer extends AbstractHttpSessionApplicationInitializer { // <1>
|
|
||||||
|
|
||||||
public Initializer() {
|
|
||||||
super(Config.class); // <2>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// end::class[]
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.annotation.WebServlet;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
// tag::class[]
|
|
||||||
@WebServlet("/session")
|
|
||||||
public class SessionServlet extends HttpServlet {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doPost(HttpServletRequest request, HttpServletResponse response)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
String attributeName = request.getParameter("attributeName");
|
|
||||||
String attributeValue = request.getParameter("attributeValue");
|
|
||||||
request.getSession().setAttribute(attributeName, attributeValue);
|
|
||||||
response.sendRedirect(request.getContextPath() + "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 2878267318695777395L;
|
|
||||||
}
|
|
||||||
// tag::end[]
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
|
||||||
<%@ taglib prefix="wj" uri="http://www.webjars.org/tags" %>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>Session Attributes</title>
|
|
||||||
<wj:locate path="bootstrap.min.css" relativeTo="META-INF/resources" var="bootstrapCssLocation"/>
|
|
||||||
<link rel="stylesheet" href="<c:url value="${bootstrapCssLocation}"/>">
|
|
||||||
<style type="text/css">
|
|
||||||
body {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>Description</h1>
|
|
||||||
<p>This application demonstrates how to use a GemFire instance to back your session. Notice that there is no JSESSIONID cookie. We are also able to customize the way of identifying what the requested session id is.</p>
|
|
||||||
|
|
||||||
<h1>Try it</h1>
|
|
||||||
|
|
||||||
<form class="form-inline" role="form" action="./session" method="post">
|
|
||||||
<label for="attributeName">Attribute Name</label>
|
|
||||||
<input id="attributeName" type="text" name="attributeName"/>
|
|
||||||
<label for="attributeValue">Attribute Value</label>
|
|
||||||
<input id="attributeValue" type="text" name="attributeValue"/>
|
|
||||||
<input type="submit" value="Set Attribute"/>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Attribute Name</th>
|
|
||||||
<th>Attribute Value</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<c:forEach items="${sessionScope}" var="attr">
|
|
||||||
<tr>
|
|
||||||
<td><c:out value="${attr.key}"/></td>
|
|
||||||
<td><c:out value="${attr.value}"/></td>
|
|
||||||
</tr>
|
|
||||||
</c:forEach>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
apply plugin: 'io.spring.convention.spring-sample-war'
|
|
||||||
apply plugin: "gemfire-server"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile project(':spring-session-data-gemfire')
|
|
||||||
compile "org.springframework:spring-web"
|
|
||||||
compile "org.webjars:bootstrap"
|
|
||||||
compile "org.webjars:webjars-taglib"
|
|
||||||
compile jstlDependencies
|
|
||||||
compile slf4jDependencies
|
|
||||||
|
|
||||||
providedCompile "javax.servlet:javax.servlet-api"
|
|
||||||
|
|
||||||
runtime "org.springframework.shell:spring-shell"
|
|
||||||
|
|
||||||
testCompile "junit:junit"
|
|
||||||
testCompile "org.assertj:assertj-core"
|
|
||||||
|
|
||||||
integrationTestCompile seleniumDependencies
|
|
||||||
|
|
||||||
integrationTestRuntime "org.springframework.shell:spring-shell"
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
|
|
||||||
import sample.pages.HomePage;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Eddú Meléndez
|
|
||||||
*/
|
|
||||||
public class AttributeTests {
|
|
||||||
|
|
||||||
private WebDriver driver;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
this.driver = new HtmlUnitDriver();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
this.driver.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void noAttributes() {
|
|
||||||
HomePage home = HomePage.go(this.driver, HomePage.class);
|
|
||||||
assertThat(home.attributes().size()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void createAttribute() {
|
|
||||||
HomePage home = HomePage.go(this.driver, HomePage.class);
|
|
||||||
home = home.form()
|
|
||||||
.attributeName("a")
|
|
||||||
.attributeValue("b")
|
|
||||||
.submit(HomePage.class);
|
|
||||||
assertThat(home.attributes()).extracting("attributeName").containsOnly("a");
|
|
||||||
assertThat(home.attributes()).extracting("attributeValue").containsOnly("b");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.pages;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.openqa.selenium.By;
|
|
||||||
import org.openqa.selenium.Cookie;
|
|
||||||
import org.openqa.selenium.SearchContext;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
import org.openqa.selenium.WebElement;
|
|
||||||
import org.openqa.selenium.support.FindBy;
|
|
||||||
import org.openqa.selenium.support.PageFactory;
|
|
||||||
import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Eddú Meléndez
|
|
||||||
* @author Rob Winch
|
|
||||||
*/
|
|
||||||
public class HomePage {
|
|
||||||
|
|
||||||
private WebDriver driver;
|
|
||||||
|
|
||||||
@FindBy(tagName = "form")
|
|
||||||
WebElement form;
|
|
||||||
|
|
||||||
@FindBy(css = "table tbody tr")
|
|
||||||
List<WebElement> trs;
|
|
||||||
|
|
||||||
List<Attribute> attributes;
|
|
||||||
|
|
||||||
public HomePage(WebDriver driver) {
|
|
||||||
this.driver = driver;
|
|
||||||
this.attributes = new ArrayList<Attribute>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void get(WebDriver driver, String get) {
|
|
||||||
String baseUrl = "http://localhost:" + System.getProperty("app.port", "8080");
|
|
||||||
driver.get(baseUrl + get);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T go(WebDriver driver, Class<T> page) {
|
|
||||||
get(driver, "/");
|
|
||||||
return PageFactory.initElements(driver, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void containCookie(String cookieName) {
|
|
||||||
Set<Cookie> cookies = this.driver.manage().getCookies();
|
|
||||||
assertThat(cookies).extracting("name").contains(cookieName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doesNotContainCookie(String cookieName) {
|
|
||||||
Set<Cookie> cookies = this.driver.manage().getCookies();
|
|
||||||
assertThat(cookies).extracting("name").doesNotContain(cookieName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HomePage logout() {
|
|
||||||
WebElement logout = this.driver
|
|
||||||
.findElement(By.cssSelector("input[type=\"submit\"]"));
|
|
||||||
logout.click();
|
|
||||||
return PageFactory.initElements(this.driver, HomePage.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Attribute> attributes() {
|
|
||||||
List<Attribute> rows = new ArrayList<Attribute>();
|
|
||||||
for (WebElement tr : this.trs) {
|
|
||||||
rows.add(new Attribute(tr));
|
|
||||||
}
|
|
||||||
this.attributes.addAll(rows);
|
|
||||||
return this.attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form form() {
|
|
||||||
return new Form(this.form);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Form {
|
|
||||||
@FindBy(name = "attributeName")
|
|
||||||
WebElement attributeName;
|
|
||||||
|
|
||||||
@FindBy(name = "attributeValue")
|
|
||||||
WebElement attributeValue;
|
|
||||||
|
|
||||||
@FindBy(css = "input[type=\"submit\"]")
|
|
||||||
WebElement submit;
|
|
||||||
|
|
||||||
public Form(SearchContext context) {
|
|
||||||
PageFactory.initElements(new DefaultElementLocatorFactory(context), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form attributeName(String text) {
|
|
||||||
this.attributeName.sendKeys(text);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form attributeValue(String text) {
|
|
||||||
this.attributeValue.sendKeys(text);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T submit(Class<T> page) {
|
|
||||||
this.submit.click();
|
|
||||||
return PageFactory.initElements(HomePage.this.driver, page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Attribute {
|
|
||||||
@FindBy(xpath = ".//td[1]")
|
|
||||||
WebElement attributeName;
|
|
||||||
|
|
||||||
@FindBy(xpath = ".//td[2]")
|
|
||||||
WebElement attributeValue;
|
|
||||||
|
|
||||||
public Attribute(SearchContext context) {
|
|
||||||
PageFactory.initElements(new DefaultElementLocatorFactory(context), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the attributeName
|
|
||||||
*/
|
|
||||||
public String getAttributeName() {
|
|
||||||
return this.attributeName.getText();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the attributeValue
|
|
||||||
*/
|
|
||||||
public String getAttributeValue() {
|
|
||||||
return this.attributeValue.getText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.ImportResource;
|
|
||||||
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
// tag::class[]
|
|
||||||
@Configuration // <1>
|
|
||||||
@ImportResource("META-INF/spring/session-server.xml") // <2>
|
|
||||||
public class Application {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
new AnnotationConfigApplicationContext(Application.class).registerShutdownHook();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// tag::end[]
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.client.Pool;
|
|
||||||
import org.apache.geode.cache.client.PoolManager;
|
|
||||||
import org.apache.geode.cache.client.internal.PoolImpl;
|
|
||||||
import org.apache.geode.management.membership.ClientMembership;
|
|
||||||
import org.apache.geode.management.membership.ClientMembershipEvent;
|
|
||||||
import org.apache.geode.management.membership.ClientMembershipListenerAdapter;
|
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
|
||||||
import org.springframework.data.gemfire.config.xml.GemfireConstants;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
public class GemFireClientServerReadyBeanPostProcessor implements BeanPostProcessor {
|
|
||||||
|
|
||||||
private static final long DEFAULT_TIMEOUT = TimeUnit.SECONDS.toMillis(60);
|
|
||||||
|
|
||||||
private static final CountDownLatch LATCH = new CountDownLatch(1);
|
|
||||||
|
|
||||||
private static final String GEMFIRE_DEFAULT_POOL_NAME = "DEFAULT";
|
|
||||||
|
|
||||||
static {
|
|
||||||
ClientMembership.registerClientMembershipListener(
|
|
||||||
new ClientMembershipListenerAdapter() {
|
|
||||||
@Override
|
|
||||||
public void memberJoined(ClientMembershipEvent event) {
|
|
||||||
LATCH.countDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Value("${spring.session.data.gemfire.port:${application.gemfire.client-server.port}}")
|
|
||||||
private int port;
|
|
||||||
|
|
||||||
@Value("${application.gemfire.client-server.host:localhost}")
|
|
||||||
private String host;
|
|
||||||
|
|
||||||
private final AtomicBoolean checkGemFireServerIsRunning = new AtomicBoolean(true);
|
|
||||||
private final AtomicReference<Pool> gemfirePool = new AtomicReference<Pool>(null);
|
|
||||||
|
|
||||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
|
||||||
if (shouldCheckWhetherGemFireServerIsRunning(bean, beanName)) {
|
|
||||||
try {
|
|
||||||
validateCacheClientNotified();
|
|
||||||
validateCacheClientSubscriptionQueueConnectionEstablished();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean shouldCheckWhetherGemFireServerIsRunning(Object bean, String beanName) {
|
|
||||||
return (isGemFireRegion(bean, beanName)
|
|
||||||
? this.checkGemFireServerIsRunning.compareAndSet(true, false)
|
|
||||||
: whenGemFirePool(bean, beanName));
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isGemFireRegion(Object bean, String beanName) {
|
|
||||||
return (GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME.equals(beanName)
|
|
||||||
|| bean instanceof Region);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean whenGemFirePool(Object bean, String beanName) {
|
|
||||||
if (bean instanceof Pool) {
|
|
||||||
this.gemfirePool.compareAndSet(null, (Pool) bean);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateCacheClientNotified() throws InterruptedException {
|
|
||||||
boolean didNotTimeout = LATCH.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
|
|
||||||
|
|
||||||
Assert.state(didNotTimeout, String.format(
|
|
||||||
"GemFire Cache Server failed to start on host [%s] and port [%d]", this.host, this.port));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("all")
|
|
||||||
private void validateCacheClientSubscriptionQueueConnectionEstablished() throws InterruptedException {
|
|
||||||
boolean cacheClientSubscriptionQueueConnectionEstablished = false;
|
|
||||||
|
|
||||||
Pool pool = defaultIfNull(this.gemfirePool.get(),
|
|
||||||
GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME, GEMFIRE_DEFAULT_POOL_NAME);
|
|
||||||
|
|
||||||
if (pool instanceof PoolImpl) {
|
|
||||||
long timeout = (System.currentTimeMillis() + DEFAULT_TIMEOUT);
|
|
||||||
|
|
||||||
while (System.currentTimeMillis() < timeout
|
|
||||||
&& !((PoolImpl) pool).isPrimaryUpdaterAlive()) {
|
|
||||||
|
|
||||||
synchronized (pool) {
|
|
||||||
TimeUnit.MILLISECONDS.timedWait(pool, 500L);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheClientSubscriptionQueueConnectionEstablished |=
|
|
||||||
((PoolImpl) pool).isPrimaryUpdaterAlive();
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.state(cacheClientSubscriptionQueueConnectionEstablished, String.format(
|
|
||||||
"Cache client subscription queue connection not established; GemFire Pool was [%s];"
|
|
||||||
+ " GemFire Pool configuration was [locators = %s, servers = %s]",
|
|
||||||
pool, pool.getLocators(), pool.getServers()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Pool defaultIfNull(Pool pool, String... poolNames) {
|
|
||||||
for (String poolName : poolNames) {
|
|
||||||
pool = (pool != null ? pool : PoolManager.find(poolName));
|
|
||||||
}
|
|
||||||
|
|
||||||
return pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// tag::end[]
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
// tag::class[]
|
|
||||||
public class SessionServlet extends HttpServlet {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doPost(HttpServletRequest request, HttpServletResponse response)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
String attributeName = request.getParameter("attributeName");
|
|
||||||
String attributeValue = request.getParameter("attributeValue");
|
|
||||||
request.getSession().setAttribute(attributeName, attributeValue);
|
|
||||||
response.sendRedirect(request.getContextPath() + "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 2878267318695777395L;
|
|
||||||
}
|
|
||||||
// tag::end[]
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
application.gemfire.client-server.host=localhost
|
|
||||||
application.gemfire.client-server.port=12480
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
||||||
xmlns:context="http://www.springframework.org/schema/context"
|
|
||||||
xmlns:gfe="http://www.springframework.org/schema/gemfire"
|
|
||||||
xmlns:p="http://www.springframework.org/schema/p"
|
|
||||||
xmlns:util="http://www.springframework.org/schema/util"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="
|
|
||||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
|
||||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
|
|
||||||
http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
|
|
||||||
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
|
|
||||||
">
|
|
||||||
|
|
||||||
<!-- tag::beans[] -->
|
|
||||||
<!--1-->
|
|
||||||
<context:annotation-config/>
|
|
||||||
|
|
||||||
<!--2-->
|
|
||||||
<context:property-placeholder location="classpath:META-INF/spring/application.properties"/>
|
|
||||||
|
|
||||||
<!--3-->
|
|
||||||
<util:properties id="gemfireProperties">
|
|
||||||
<prop key="name">GemFireClientServerHttpSessionXmlSample</prop>
|
|
||||||
<prop key="mcast-port">0</prop>
|
|
||||||
<!--<prop key="log-file">gemfire-server.log</prop>-->
|
|
||||||
<prop key="log-level">${spring.session.data.gemfire.log-level:warning}</prop>
|
|
||||||
<!--
|
|
||||||
<prop key="jmx-manager">true</prop>
|
|
||||||
<prop key="jmx-manager-start">true</prop>
|
|
||||||
-->
|
|
||||||
</util:properties>
|
|
||||||
|
|
||||||
<!--4-->
|
|
||||||
<gfe:cache properties-ref="gemfireProperties"/>
|
|
||||||
|
|
||||||
<!--5-->
|
|
||||||
<gfe:cache-server auto-startup="true"
|
|
||||||
bind-address="${application.gemfire.client-server.host}"
|
|
||||||
host-name-for-clients="${application.gemfire.client-server.host}"
|
|
||||||
port="${spring.session.data.gemfire.port:${application.gemfire.client-server.port}}"/>
|
|
||||||
|
|
||||||
<!--6-->
|
|
||||||
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
|
|
||||||
p:maxInactiveIntervalInSeconds="30"/>
|
|
||||||
<!-- end::beans[] -->
|
|
||||||
|
|
||||||
</beans>
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
||||||
xmlns:context="http://www.springframework.org/schema/context"
|
|
||||||
xmlns:gfe="http://www.springframework.org/schema/gemfire"
|
|
||||||
xmlns:p="http://www.springframework.org/schema/p"
|
|
||||||
xmlns:util="http://www.springframework.org/schema/util"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="
|
|
||||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
|
||||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
|
|
||||||
http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
|
|
||||||
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
|
|
||||||
">
|
|
||||||
|
|
||||||
<!-- tag::beans[] -->
|
|
||||||
<!--1-->
|
|
||||||
<context:annotation-config/>
|
|
||||||
|
|
||||||
<!--2-->
|
|
||||||
<context:property-placeholder location="classpath:META-INF/spring/application.properties"/>
|
|
||||||
|
|
||||||
<!--3-->
|
|
||||||
<bean class="sample.GemFireClientServerReadyBeanPostProcessor"/>
|
|
||||||
|
|
||||||
<!--4-->
|
|
||||||
<util:properties id="gemfireProperties">
|
|
||||||
<!--<prop key="log-file">gemfire-client.log</prop>-->
|
|
||||||
<prop key="log-level">${spring.session.data.gemfire.log-level:warning}</prop>
|
|
||||||
</util:properties>
|
|
||||||
|
|
||||||
<!--5-->
|
|
||||||
<gfe:client-cache properties-ref="gemfireProperties" pool-name="gemfirePool"/>
|
|
||||||
|
|
||||||
<!--6-->
|
|
||||||
<gfe:pool keep-alive="false"
|
|
||||||
ping-interval="5000"
|
|
||||||
read-timeout="15000"
|
|
||||||
retry-attempts="1"
|
|
||||||
subscription-enabled="true"
|
|
||||||
thread-local-connections="false">
|
|
||||||
<gfe:server host="${application.gemfire.client-server.host}"
|
|
||||||
port="${spring.session.data.gemfire.port:${application.gemfire.client-server.port}}"/>
|
|
||||||
</gfe:pool>
|
|
||||||
|
|
||||||
<!--7-->
|
|
||||||
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
|
|
||||||
p:maxInactiveIntervalInSeconds="30" p:poolName="DEFAULT"/>
|
|
||||||
<!-- end::beans[] -->
|
|
||||||
|
|
||||||
</beans>
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
|
|
||||||
<!--
|
|
||||||
- Location of the XML file that defines the root application context
|
|
||||||
- Applied by ContextLoaderListener.
|
|
||||||
-->
|
|
||||||
<!-- tag::context-param[] -->
|
|
||||||
<context-param>
|
|
||||||
<param-name>contextConfigLocation</param-name>
|
|
||||||
<param-value>/WEB-INF/spring/session-client.xml</param-value>
|
|
||||||
</context-param>
|
|
||||||
<!-- end::context-param[] -->
|
|
||||||
|
|
||||||
<!-- tag::springSessionRepositoryFilter[] -->
|
|
||||||
<filter>
|
|
||||||
<filter-name>springSessionRepositoryFilter</filter-name>
|
|
||||||
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
|
|
||||||
</filter>
|
|
||||||
<filter-mapping>
|
|
||||||
<filter-name>springSessionRepositoryFilter</filter-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
<dispatcher>REQUEST</dispatcher>
|
|
||||||
<dispatcher>ERROR</dispatcher>
|
|
||||||
</filter-mapping>
|
|
||||||
<!-- end::springSessionRepositoryFilter[] -->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
- Loads the root application context of this web app at startup.
|
|
||||||
- The application context is then available via
|
|
||||||
- WebApplicationContextUtils.getWebApplicationContext(servletContext).
|
|
||||||
-->
|
|
||||||
<!-- tag::listeners[] -->
|
|
||||||
<listener>
|
|
||||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
|
||||||
</listener>
|
|
||||||
<!-- end::listeners[] -->
|
|
||||||
|
|
||||||
<servlet>
|
|
||||||
<servlet-name>session</servlet-name>
|
|
||||||
<servlet-class>sample.SessionServlet</servlet-class>
|
|
||||||
</servlet>
|
|
||||||
|
|
||||||
<servlet-mapping>
|
|
||||||
<servlet-name>session</servlet-name>
|
|
||||||
<url-pattern>/session</url-pattern>
|
|
||||||
</servlet-mapping>
|
|
||||||
|
|
||||||
<welcome-file-list>
|
|
||||||
<welcome-file>index.jsp</welcome-file>
|
|
||||||
</welcome-file-list>
|
|
||||||
</web-app>
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
|
||||||
<%@ taglib prefix="wj" uri="http://www.webjars.org/tags" %>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>Session Attributes</title>
|
|
||||||
<wj:locate path="bootstrap.min.css" relativeTo="META-INF/resources" var="bootstrapCssLocation"/>
|
|
||||||
<link rel="stylesheet" href="<c:url value="${bootstrapCssLocation}"/>">
|
|
||||||
<style type="text/css">
|
|
||||||
body {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>Description</h1>
|
|
||||||
<p>This application demonstrates how to use a GemFire instance to back your session. Notice that there is no JSESSIONID cookie. We are also able to customize the way of identifying what the requested session id is.</p>
|
|
||||||
|
|
||||||
<h1>Try it</h1>
|
|
||||||
|
|
||||||
<form class="form-inline" role="form" action="./session" method="post">
|
|
||||||
<label for="attributeName">Attribute Name</label>
|
|
||||||
<input id="attributeName" type="text" name="attributeName"/>
|
|
||||||
<label for="attributeValue">Attribute Value</label>
|
|
||||||
<input id="attributeValue" type="text" name="attributeValue"/>
|
|
||||||
<input type="submit" value="Set Attribute"/>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Attribute Name</th>
|
|
||||||
<th>Attribute Value</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<c:forEach items="${sessionScope}" var="attr">
|
|
||||||
<tr>
|
|
||||||
<td><c:out value="${attr.key}"/></td>
|
|
||||||
<td><c:out value="${attr.value}"/></td>
|
|
||||||
</tr>
|
|
||||||
</c:forEach>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
apply plugin: 'io.spring.convention.spring-sample-war'
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile project(':spring-session-data-gemfire')
|
|
||||||
compile "org.springframework:spring-web"
|
|
||||||
compile "org.webjars:bootstrap"
|
|
||||||
compile "org.webjars:webjars-taglib"
|
|
||||||
compile jstlDependencies
|
|
||||||
compile slf4jDependencies
|
|
||||||
|
|
||||||
providedCompile "javax.servlet:javax.servlet-api"
|
|
||||||
|
|
||||||
testCompile "junit:junit"
|
|
||||||
|
|
||||||
integrationTestCompile seleniumDependencies
|
|
||||||
integrationTestCompile "org.assertj:assertj-core"
|
|
||||||
|
|
||||||
integrationTestRuntime "org.springframework.shell:spring-shell"
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
|
|
||||||
import sample.pages.HomePage;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Eddú Meléndez
|
|
||||||
*/
|
|
||||||
public class AttributeTests {
|
|
||||||
|
|
||||||
private WebDriver driver;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
this.driver = new HtmlUnitDriver();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
this.driver.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void noAttributes() {
|
|
||||||
HomePage home = HomePage.go(this.driver, HomePage.class);
|
|
||||||
assertThat(home.attributes().size()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void createAttribute() {
|
|
||||||
HomePage home = HomePage.go(this.driver, HomePage.class);
|
|
||||||
home = home.form()
|
|
||||||
.attributeName("a")
|
|
||||||
.attributeValue("b")
|
|
||||||
.submit(HomePage.class);
|
|
||||||
assertThat(home.attributes()).extracting("attributeName").containsOnly("a");
|
|
||||||
assertThat(home.attributes()).extracting("attributeValue").containsOnly("b");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.pages;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.openqa.selenium.By;
|
|
||||||
import org.openqa.selenium.Cookie;
|
|
||||||
import org.openqa.selenium.SearchContext;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
import org.openqa.selenium.WebElement;
|
|
||||||
import org.openqa.selenium.support.FindBy;
|
|
||||||
import org.openqa.selenium.support.PageFactory;
|
|
||||||
import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Eddú Meléndez
|
|
||||||
* @author Rob Winch
|
|
||||||
*/
|
|
||||||
public class HomePage {
|
|
||||||
|
|
||||||
private WebDriver driver;
|
|
||||||
|
|
||||||
@FindBy(tagName = "form")
|
|
||||||
WebElement form;
|
|
||||||
|
|
||||||
@FindBy(css = "table tbody tr")
|
|
||||||
List<WebElement> trs;
|
|
||||||
|
|
||||||
List<Attribute> attributes;
|
|
||||||
|
|
||||||
public HomePage(WebDriver driver) {
|
|
||||||
this.driver = driver;
|
|
||||||
this.attributes = new ArrayList<Attribute>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void get(WebDriver driver, String get) {
|
|
||||||
String baseUrl = "http://localhost:" + System.getProperty("app.port", "8080");
|
|
||||||
driver.get(baseUrl + get);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T go(WebDriver driver, Class<T> page) {
|
|
||||||
get(driver, "/");
|
|
||||||
return PageFactory.initElements(driver, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void containCookie(String cookieName) {
|
|
||||||
Set<Cookie> cookies = this.driver.manage().getCookies();
|
|
||||||
assertThat(cookies).extracting("name").contains(cookieName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doesNotContainCookie(String cookieName) {
|
|
||||||
Set<Cookie> cookies = this.driver.manage().getCookies();
|
|
||||||
assertThat(cookies).extracting("name").doesNotContain(cookieName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HomePage logout() {
|
|
||||||
WebElement logout = this.driver
|
|
||||||
.findElement(By.cssSelector("input[type=\"submit\"]"));
|
|
||||||
logout.click();
|
|
||||||
return PageFactory.initElements(this.driver, HomePage.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Attribute> attributes() {
|
|
||||||
List<Attribute> rows = new ArrayList<Attribute>();
|
|
||||||
for (WebElement tr : this.trs) {
|
|
||||||
rows.add(new Attribute(tr));
|
|
||||||
}
|
|
||||||
this.attributes.addAll(rows);
|
|
||||||
return this.attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form form() {
|
|
||||||
return new Form(this.form);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Form {
|
|
||||||
@FindBy(name = "attributeName")
|
|
||||||
WebElement attributeName;
|
|
||||||
|
|
||||||
@FindBy(name = "attributeValue")
|
|
||||||
WebElement attributeValue;
|
|
||||||
|
|
||||||
@FindBy(css = "input[type=\"submit\"]")
|
|
||||||
WebElement submit;
|
|
||||||
|
|
||||||
public Form(SearchContext context) {
|
|
||||||
PageFactory.initElements(new DefaultElementLocatorFactory(context), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form attributeName(String text) {
|
|
||||||
this.attributeName.sendKeys(text);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form attributeValue(String text) {
|
|
||||||
this.attributeValue.sendKeys(text);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T submit(Class<T> page) {
|
|
||||||
this.submit.click();
|
|
||||||
return PageFactory.initElements(HomePage.this.driver, page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Attribute {
|
|
||||||
@FindBy(xpath = ".//td[1]")
|
|
||||||
WebElement attributeName;
|
|
||||||
|
|
||||||
@FindBy(xpath = ".//td[2]")
|
|
||||||
WebElement attributeValue;
|
|
||||||
|
|
||||||
public Attribute(SearchContext context) {
|
|
||||||
PageFactory.initElements(new DefaultElementLocatorFactory(context), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the attributeName
|
|
||||||
*/
|
|
||||||
public String getAttributeName() {
|
|
||||||
return this.attributeName.getText();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the attributeValue
|
|
||||||
*/
|
|
||||||
public String getAttributeValue() {
|
|
||||||
return this.attributeValue.getText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
// tag::class[]
|
|
||||||
public class SessionServlet extends HttpServlet {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doPost(HttpServletRequest request, HttpServletResponse response)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
String attributeName = request.getParameter("attributeName");
|
|
||||||
String attributeValue = request.getParameter("attributeValue");
|
|
||||||
request.getSession().setAttribute(attributeName, attributeValue);
|
|
||||||
response.sendRedirect(request.getContextPath() + "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 2878267318695777395L;
|
|
||||||
}
|
|
||||||
// end::class[]
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
||||||
xmlns:context="http://www.springframework.org/schema/context"
|
|
||||||
xmlns:gfe="http://www.springframework.org/schema/gemfire"
|
|
||||||
xmlns:util="http://www.springframework.org/schema/util"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="
|
|
||||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
|
||||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
|
|
||||||
http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
|
|
||||||
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
|
|
||||||
">
|
|
||||||
|
|
||||||
<!-- tag::beans[] -->
|
|
||||||
<!--1-->
|
|
||||||
<context:annotation-config/>
|
|
||||||
<context:property-placeholder/>
|
|
||||||
|
|
||||||
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"/>
|
|
||||||
|
|
||||||
<!--2-->
|
|
||||||
<util:properties id="gemfireProperties">
|
|
||||||
<prop key="name">GemFireP2PHttpSessionXmlSample</prop>
|
|
||||||
<prop key="mcast-port">0</prop>
|
|
||||||
<prop key="log-level">${sample.httpsession.gemfire.log-level:warning}</prop>
|
|
||||||
<prop key="jmx-manager">true</prop>
|
|
||||||
<prop key="jmx-manager-start">true</prop>
|
|
||||||
</util:properties>
|
|
||||||
|
|
||||||
<!--3-->
|
|
||||||
<gfe:cache properties-ref="gemfireProperties" use-bean-factory-locator="false"/>
|
|
||||||
<!-- end::beans[] -->
|
|
||||||
|
|
||||||
</beans>
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
|
|
||||||
<!--
|
|
||||||
- Location of the XML file that defines the root application context
|
|
||||||
- Applied by ContextLoaderListener.
|
|
||||||
-->
|
|
||||||
<!-- tag::context-param[] -->
|
|
||||||
<context-param>
|
|
||||||
<param-name>contextConfigLocation</param-name>
|
|
||||||
<param-value>
|
|
||||||
/WEB-INF/spring/*.xml
|
|
||||||
</param-value>
|
|
||||||
</context-param>
|
|
||||||
<!-- end::context-param[] -->
|
|
||||||
|
|
||||||
<!-- tag::springSessionRepositoryFilter[] -->
|
|
||||||
<filter>
|
|
||||||
<filter-name>springSessionRepositoryFilter</filter-name>
|
|
||||||
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
|
|
||||||
</filter>
|
|
||||||
<filter-mapping>
|
|
||||||
<filter-name>springSessionRepositoryFilter</filter-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
<dispatcher>REQUEST</dispatcher>
|
|
||||||
<dispatcher>ERROR</dispatcher>
|
|
||||||
</filter-mapping>
|
|
||||||
<!-- end::springSessionRepositoryFilter[] -->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
- Loads the root application context of this web app at startup.
|
|
||||||
- The application context is then available via
|
|
||||||
- WebApplicationContextUtils.getWebApplicationContext(servletContext).
|
|
||||||
-->
|
|
||||||
<!-- tag::listeners[] -->
|
|
||||||
<listener>
|
|
||||||
<listener-class>
|
|
||||||
org.springframework.web.context.ContextLoaderListener
|
|
||||||
</listener-class>
|
|
||||||
</listener>
|
|
||||||
<!-- end::listeners[] -->
|
|
||||||
|
|
||||||
<servlet>
|
|
||||||
<servlet-name>session</servlet-name>
|
|
||||||
<servlet-class>sample.SessionServlet</servlet-class>
|
|
||||||
</servlet>
|
|
||||||
|
|
||||||
<servlet-mapping>
|
|
||||||
<servlet-name>session</servlet-name>
|
|
||||||
<url-pattern>/session</url-pattern>
|
|
||||||
</servlet-mapping>
|
|
||||||
|
|
||||||
<welcome-file-list>
|
|
||||||
<welcome-file>index.jsp</welcome-file>
|
|
||||||
</welcome-file-list>
|
|
||||||
</web-app>
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
|
||||||
<%@ taglib prefix="wj" uri="http://www.webjars.org/tags" %>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>Session Attributes</title>
|
|
||||||
<wj:locate path="bootstrap.min.css" relativeTo="META-INF/resources" var="bootstrapCssLocation"/>
|
|
||||||
<link rel="stylesheet" href="<c:url value="${bootstrapCssLocation}"/>">
|
|
||||||
<style type="text/css">
|
|
||||||
body {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>Description</h1>
|
|
||||||
<p>This application demonstrates how to use a GemFire instance to back your session. Notice that there is no JSESSIONID cookie. We are also able to customize the way of identifying what the requested session id is.</p>
|
|
||||||
|
|
||||||
<h1>Try it</h1>
|
|
||||||
|
|
||||||
<form class="form-inline" role="form" action="./session" method="post">
|
|
||||||
<label for="attributeName">Attribute Name</label>
|
|
||||||
<input id="attributeName" type="text" name="attributeName"/>
|
|
||||||
<label for="attributeValue">Attribute Value</label>
|
|
||||||
<input id="attributeValue" type="text" name="attributeValue"/>
|
|
||||||
<input type="submit" value="Set Attribute"/>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Attribute Name</th>
|
|
||||||
<th>Attribute Value</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<c:forEach items="${sessionScope}" var="attr">
|
|
||||||
<tr>
|
|
||||||
<td><c:out value="${attr.key}"/></td>
|
|
||||||
<td><c:out value="${attr.value}"/></td>
|
|
||||||
</tr>
|
|
||||||
</c:forEach>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
apply plugin: 'io.spring.convention.spring-pom'
|
|
||||||
|
|
||||||
description = "Aggregator for Spring Session and Spring Data GemFire"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile project(':spring-session')
|
|
||||||
compile("org.springframework.data:spring-data-gemfire") {
|
|
||||||
exclude group: "org.slf4j", module: 'slf4j-api'
|
|
||||||
exclude group: "org.slf4j", module: 'jcl-over-slf4j'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ dependencies {
|
|||||||
optional "org.springframework:spring-messaging"
|
optional "org.springframework:spring-messaging"
|
||||||
optional "org.springframework:spring-web"
|
optional "org.springframework:spring-web"
|
||||||
optional "org.springframework:spring-websocket"
|
optional "org.springframework:spring-websocket"
|
||||||
optional "org.springframework.data:spring-data-gemfire"
|
|
||||||
optional "org.springframework.data:spring-data-mongodb"
|
optional "org.springframework.data:spring-data-mongodb"
|
||||||
optional "org.springframework.data:spring-data-redis"
|
optional "org.springframework.data:spring-data-redis"
|
||||||
optional "org.springframework.security:spring-security-core"
|
optional "org.springframework.security:spring-security-core"
|
||||||
@@ -27,7 +26,6 @@ dependencies {
|
|||||||
integrationTestCompile "org.hsqldb:hsqldb"
|
integrationTestCompile "org.hsqldb:hsqldb"
|
||||||
integrationTestCompile "de.flapdoodle.embed:de.flapdoodle.embed.mongo"
|
integrationTestCompile "de.flapdoodle.embed:de.flapdoodle.embed.mongo"
|
||||||
|
|
||||||
integrationTestRuntime "org.springframework.shell:spring-shell"
|
|
||||||
|
|
||||||
testCompile "junit:junit"
|
testCompile "junit:junit"
|
||||||
testCompile "org.mockito:mockito-core"
|
testCompile "org.mockito:mockito-core"
|
||||||
|
|||||||
@@ -1,460 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Cache;
|
|
||||||
import org.apache.geode.cache.CacheClosedException;
|
|
||||||
import org.apache.geode.cache.DataPolicy;
|
|
||||||
import org.apache.geode.cache.ExpirationAction;
|
|
||||||
import org.apache.geode.cache.ExpirationAttributes;
|
|
||||||
import org.apache.geode.cache.GemFireCache;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.client.ClientCache;
|
|
||||||
import org.apache.geode.cache.client.ClientCacheFactory;
|
|
||||||
import org.apache.geode.cache.query.Index;
|
|
||||||
import org.apache.geode.cache.server.CacheServer;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.ApplicationListener;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
|
||||||
import org.springframework.session.events.AbstractSessionEvent;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link AbstractGemFireIntegrationTests} is an abstract base class encapsulating common functionality
|
|
||||||
* for writing Spring Session GemFire integration tests.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.1.0
|
|
||||||
* @see org.apache.geode.cache.Cache
|
|
||||||
* @see org.apache.geode.cache.GemFireCache
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
* @see org.apache.geode.cache.client.ClientCache
|
|
||||||
* @see org.apache.geode.cache.query.Index
|
|
||||||
* @see org.apache.geode.cache.server.CacheServer
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
*/
|
|
||||||
public abstract class AbstractGemFireIntegrationTests {
|
|
||||||
|
|
||||||
protected static final boolean DEFAULT_ENABLE_QUERY_DEBUGGING = false;
|
|
||||||
|
|
||||||
protected static final boolean GEMFIRE_QUERY_DEBUG = Boolean.getBoolean("spring.session.data.gemfire.query.debug");
|
|
||||||
|
|
||||||
protected static final int DEFAULT_GEMFIRE_SERVER_PORT = CacheServer.DEFAULT_PORT;
|
|
||||||
|
|
||||||
protected static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(20);
|
|
||||||
protected static final long DEFAULT_WAIT_INTERVAL = 500L;
|
|
||||||
|
|
||||||
protected static final File WORKING_DIRECTORY = new File(System.getProperty("user.dir"));
|
|
||||||
|
|
||||||
protected static final String DEFAULT_PROCESS_CONTROL_FILENAME = "process.ctl";
|
|
||||||
|
|
||||||
protected static final String GEMFIRE_LOG_FILE_NAME =
|
|
||||||
System.getProperty("spring.session.data.gemfire.log-file", "server.log");
|
|
||||||
|
|
||||||
protected static final String GEMFIRE_LOG_LEVEL =
|
|
||||||
System.getProperty("spring.session.data.gemfire.log-level", "error");
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected Cache gemfireCache;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected GemFireOperationsSessionRepository gemfireSessionRepository;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
System.setProperty("gemfire.Query.VERBOSE", String.valueOf(isQueryDebuggingEnabled()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static File createDirectory(String pathname) {
|
|
||||||
File directory = new File(WORKING_DIRECTORY, pathname);
|
|
||||||
|
|
||||||
assertThat(directory.isDirectory() || directory.mkdirs())
|
|
||||||
.as(String.format("Failed to create directory (%1$s)", directory)).isTrue();
|
|
||||||
|
|
||||||
directory.deleteOnExit();
|
|
||||||
|
|
||||||
return directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static List<String> createJavaProcessCommandLine(Class<?> type, String... args) {
|
|
||||||
List<String> commandLine = new ArrayList<String>();
|
|
||||||
|
|
||||||
String javaHome = System.getProperty("java.home");
|
|
||||||
String javaExe = new File(new File(javaHome, "bin"), "java").getAbsolutePath();
|
|
||||||
|
|
||||||
commandLine.add(javaExe);
|
|
||||||
commandLine.add("-server");
|
|
||||||
commandLine.add("-ea");
|
|
||||||
commandLine.add(String.format("-Dgemfire.log-file=%1$s", GEMFIRE_LOG_FILE_NAME));
|
|
||||||
commandLine.add(String.format("-Dgemfire.log-level=%1$s", GEMFIRE_LOG_LEVEL));
|
|
||||||
commandLine.add(String.format("-Dgemfire.Query.VERBOSE=%1$s", GEMFIRE_QUERY_DEBUG));
|
|
||||||
commandLine.addAll(extractJvmArguments(args));
|
|
||||||
commandLine.add("-classpath");
|
|
||||||
commandLine.add(System.getProperty("java.class.path"));
|
|
||||||
commandLine.add(type.getName());
|
|
||||||
commandLine.addAll(extractProgramArguments(args));
|
|
||||||
|
|
||||||
// System.err.printf("Java process command-line is (%1$s)%n", commandLine);
|
|
||||||
|
|
||||||
return commandLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static List<String> extractJvmArguments(final String... args) {
|
|
||||||
List<String> jvmArgs = new ArrayList<String>(args.length);
|
|
||||||
|
|
||||||
for (String arg : args) {
|
|
||||||
if (arg.startsWith("-")) {
|
|
||||||
jvmArgs.add(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return jvmArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static List<String> extractProgramArguments(final String... args) {
|
|
||||||
List<String> jvmArgs = new ArrayList<String>(args.length);
|
|
||||||
|
|
||||||
for (String arg : args) {
|
|
||||||
if (!arg.startsWith("-")) {
|
|
||||||
jvmArgs.add(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return jvmArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static Process run(Class<?> type, File directory, String... args) throws IOException {
|
|
||||||
return new ProcessBuilder().command(createJavaProcessCommandLine(type, args)).directory(directory).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean waitForCacheServerToStart(CacheServer cacheServer) {
|
|
||||||
return waitForCacheServerToStart(cacheServer, DEFAULT_WAIT_DURATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean waitForCacheServerToStart(CacheServer cacheServer, long duration) {
|
|
||||||
return waitForCacheServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean waitForCacheServerToStart(String host, int port) {
|
|
||||||
return waitForCacheServerToStart(host, port, DEFAULT_WAIT_DURATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean waitForCacheServerToStart(final String host, final int port, long duration) {
|
|
||||||
return waitOnCondition(new Condition() {
|
|
||||||
AtomicBoolean connected = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
public boolean evaluate() {
|
|
||||||
Socket socket = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!connected.get()) {
|
|
||||||
socket = new Socket(host, port);
|
|
||||||
connected.set(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException ignore) {
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
GemFireUtils.close(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
return connected.get();
|
|
||||||
}
|
|
||||||
}, duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE this method would not be necessary except Spring Sessions' build does not fork
|
|
||||||
// the test JVM
|
|
||||||
// for every test class.
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean waitForClientCacheToClose() {
|
|
||||||
return waitForClientCacheToClose(DEFAULT_WAIT_DURATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean waitForClientCacheToClose(long duration) {
|
|
||||||
try {
|
|
||||||
final ClientCache clientCache = ClientCacheFactory.getAnyInstance();
|
|
||||||
|
|
||||||
clientCache.close();
|
|
||||||
|
|
||||||
waitOnCondition(new Condition() {
|
|
||||||
public boolean evaluate() {
|
|
||||||
return clientCache.isClosed();
|
|
||||||
}
|
|
||||||
}, duration);
|
|
||||||
|
|
||||||
return clientCache.isClosed();
|
|
||||||
}
|
|
||||||
catch (CacheClosedException ignore) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean waitForProcessToStart(Process process, File directory) {
|
|
||||||
return waitForProcessToStart(process, directory, DEFAULT_WAIT_DURATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
@SuppressWarnings("all")
|
|
||||||
protected static boolean waitForProcessToStart(Process process, File directory, long duration) {
|
|
||||||
final File processControl = new File(directory, DEFAULT_PROCESS_CONTROL_FILENAME);
|
|
||||||
|
|
||||||
waitOnCondition(new Condition() {
|
|
||||||
public boolean evaluate() {
|
|
||||||
return processControl.isFile();
|
|
||||||
}
|
|
||||||
}, duration);
|
|
||||||
|
|
||||||
return process.isAlive();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static int waitForProcessToStop(Process process, File directory) {
|
|
||||||
return waitForProcessToStop(process, directory, DEFAULT_WAIT_DURATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static int waitForProcessToStop(Process process, File directory, long duration) {
|
|
||||||
long timeout = (System.currentTimeMillis() + duration);
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (process.isAlive() && System.currentTimeMillis() < timeout) {
|
|
||||||
if (process.waitFor(DEFAULT_WAIT_INTERVAL, TimeUnit.MILLISECONDS)) {
|
|
||||||
return process.exitValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (process.isAlive() ? -1 : process.exitValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean waitOnCondition(Condition condition) {
|
|
||||||
return waitOnCondition(condition, DEFAULT_WAIT_DURATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
@SuppressWarnings("all")
|
|
||||||
protected static boolean waitOnCondition(Condition condition, long duration) {
|
|
||||||
long timeout = (System.currentTimeMillis() + duration);
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (!condition.evaluate() && System.currentTimeMillis() < timeout) {
|
|
||||||
synchronized (condition) {
|
|
||||||
TimeUnit.MILLISECONDS.timedWait(condition, DEFAULT_WAIT_INTERVAL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
return condition.evaluate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static File writeProcessControlFile(File path) throws IOException {
|
|
||||||
assertThat(path != null && path.isDirectory()).isTrue();
|
|
||||||
|
|
||||||
File processControl = new File(path, DEFAULT_PROCESS_CONTROL_FILENAME);
|
|
||||||
|
|
||||||
assertThat(processControl.createNewFile()).isTrue();
|
|
||||||
|
|
||||||
processControl.deleteOnExit();
|
|
||||||
|
|
||||||
return processControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected void assertValidSession(ExpiringSession session) {
|
|
||||||
assertThat(session).isNotNull();
|
|
||||||
assertThat(session.getId()).isNotEmpty();
|
|
||||||
assertThat(session.isExpired()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected void assertRegion(Region<?, ?> actualRegion, String expectedName, DataPolicy expectedDataPolicy) {
|
|
||||||
assertThat(actualRegion).isNotNull();
|
|
||||||
assertThat(actualRegion.getName()).isEqualTo(expectedName);
|
|
||||||
assertThat(actualRegion.getFullPath()).isEqualTo(GemFireUtils.toRegionPath(expectedName));
|
|
||||||
assertThat(actualRegion.getAttributes()).isNotNull();
|
|
||||||
assertThat(actualRegion.getAttributes().getDataPolicy()).isEqualTo(expectedDataPolicy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected void assertIndex(Index index, String expectedExpression, String expectedFromClause) {
|
|
||||||
assertThat(index).isNotNull();
|
|
||||||
assertThat(index.getIndexedExpression()).isEqualTo(expectedExpression);
|
|
||||||
assertThat(index.getFromClause()).isEqualTo(expectedFromClause);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected void assertEntryIdleTimeout(Region<?, ?> region, ExpirationAction expectedAction, int expectedTimeout) {
|
|
||||||
assertEntryIdleTimeout(region.getAttributes().getEntryIdleTimeout(), expectedAction, expectedTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected void assertEntryIdleTimeout(ExpirationAttributes actualExpirationAttributes,
|
|
||||||
ExpirationAction expectedAction, int expectedTimeout) {
|
|
||||||
|
|
||||||
assertThat(actualExpirationAttributes).isNotNull();
|
|
||||||
assertThat(actualExpirationAttributes.getAction()).isEqualTo(expectedAction);
|
|
||||||
assertThat(actualExpirationAttributes.getTimeout()).isEqualTo(expectedTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected boolean enableQueryDebugging() {
|
|
||||||
return DEFAULT_ENABLE_QUERY_DEBUGGING;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected boolean isQueryDebuggingEnabled() {
|
|
||||||
return (GEMFIRE_QUERY_DEBUG || enableQueryDebugging());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected List<String> listRegions(GemFireCache gemfireCache) {
|
|
||||||
Set<Region<?, ?>> regions = gemfireCache.rootRegions();
|
|
||||||
|
|
||||||
List<String> regionList = new ArrayList<String>(regions.size());
|
|
||||||
|
|
||||||
for (Region<?, ?> region : regions) {
|
|
||||||
regionList.add(region.getFullPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
return regionList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected <T extends ExpiringSession> T createSession() {
|
|
||||||
T expiringSession = (T) this.gemfireSessionRepository.createSession();
|
|
||||||
assertThat(expiringSession).isNotNull();
|
|
||||||
return expiringSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected <T extends ExpiringSession> T createSession(String principalName) {
|
|
||||||
GemFireOperationsSessionRepository.GemFireSession session = createSession();
|
|
||||||
session.setPrincipalName(principalName);
|
|
||||||
return (T) session;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected <T extends ExpiringSession> T delete(T session) {
|
|
||||||
this.gemfireSessionRepository.delete(session);
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected <T extends ExpiringSession> T expire(T session) {
|
|
||||||
session.setLastAccessedTime(0L);
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected <T extends ExpiringSession> T get(String sessionId) {
|
|
||||||
return (T) this.gemfireSessionRepository.getSession(sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected <T extends ExpiringSession> T save(T session) {
|
|
||||||
this.gemfireSessionRepository.save(session);
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected <T extends ExpiringSession> T touch(T session) {
|
|
||||||
session.setLastAccessedTime(System.currentTimeMillis());
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The SessionEventListener class is a Spring {@link ApplicationListener} listening
|
|
||||||
* for Spring HTTP Session application events.
|
|
||||||
*
|
|
||||||
* @see org.springframework.context.ApplicationListener
|
|
||||||
* @see org.springframework.session.events.AbstractSessionEvent
|
|
||||||
*/
|
|
||||||
public static class SessionEventListener implements ApplicationListener<AbstractSessionEvent> {
|
|
||||||
|
|
||||||
private volatile AbstractSessionEvent sessionEvent;
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <T extends AbstractSessionEvent> T getSessionEvent() {
|
|
||||||
T sessionEvent = (T) this.sessionEvent;
|
|
||||||
this.sessionEvent = null;
|
|
||||||
return sessionEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void onApplicationEvent(AbstractSessionEvent event) {
|
|
||||||
this.sessionEvent = event;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public <T extends AbstractSessionEvent> T waitForSessionEvent(long duration) {
|
|
||||||
waitOnCondition(new Condition() {
|
|
||||||
public boolean evaluate() {
|
|
||||||
return (SessionEventListener.this.sessionEvent != null);
|
|
||||||
}
|
|
||||||
}, duration);
|
|
||||||
|
|
||||||
return getSessionEvent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Condition interface defines a logical condition that must be satisfied before
|
|
||||||
* it is safe to proceed.
|
|
||||||
*/
|
|
||||||
protected interface Condition {
|
|
||||||
boolean evaluate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,374 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Cache;
|
|
||||||
import org.apache.geode.cache.DataPolicy;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.RegionAttributes;
|
|
||||||
import org.apache.geode.cache.client.ClientCache;
|
|
||||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.AfterClass;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
|
||||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.client.PoolFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.server.CacheServerFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.support.ConnectionEndpoint;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
|
||||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
|
||||||
import org.springframework.session.events.AbstractSessionEvent;
|
|
||||||
import org.springframework.session.events.SessionCreatedEvent;
|
|
||||||
import org.springframework.session.events.SessionDeletedEvent;
|
|
||||||
import org.springframework.session.events.SessionExpiredEvent;
|
|
||||||
import org.springframework.test.annotation.DirtiesContext;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
|
||||||
import org.springframework.util.FileSystemUtils;
|
|
||||||
import org.springframework.util.SocketUtils;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration tests to test the functionality of GemFire-backed Spring Sessions using
|
|
||||||
* the GemFire client-server topology.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.1.0
|
|
||||||
* @see org.junit.Test
|
|
||||||
* @see org.junit.runner.RunWith
|
|
||||||
* @see org.springframework.context.ConfigurableApplicationContext
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
|
||||||
* @see org.springframework.test.annotation.DirtiesContext
|
|
||||||
* @see org.springframework.test.context.ContextConfiguration
|
|
||||||
* @see org.springframework.test.context.junit4.SpringRunner
|
|
||||||
* @see org.springframework.test.context.web.WebAppConfiguration
|
|
||||||
* @see org.apache.geode.cache.Cache
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
* @see org.apache.geode.cache.client.ClientCache
|
|
||||||
* @see org.apache.geode.cache.client.Pool
|
|
||||||
* @see org.apache.geode.cache.server.CacheServer
|
|
||||||
*/
|
|
||||||
@RunWith(SpringRunner.class)
|
|
||||||
@ContextConfiguration(classes =
|
|
||||||
ClientServerGemFireOperationsSessionRepositoryIntegrationTests.SpringSessionDataGemFireClientConfiguration.class)
|
|
||||||
@DirtiesContext
|
|
||||||
@WebAppConfiguration
|
|
||||||
public class ClientServerGemFireOperationsSessionRepositoryIntegrationTests
|
|
||||||
extends AbstractGemFireIntegrationTests {
|
|
||||||
|
|
||||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1;
|
|
||||||
|
|
||||||
private static final DateFormat TIMESTAMP = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
|
|
||||||
|
|
||||||
private static File processWorkingDirectory;
|
|
||||||
|
|
||||||
private static Process gemfireServer;
|
|
||||||
|
|
||||||
private static final String SPRING_SESSION_GEMFIRE_REGION_NAME = "TestClientServerSessions";
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private SessionEventListener sessionEventListener;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void startGemFireServer() throws IOException {
|
|
||||||
long t0 = System.currentTimeMillis();
|
|
||||||
|
|
||||||
int port = SocketUtils.findAvailableTcpPort();
|
|
||||||
|
|
||||||
System.err.printf("Starting a GemFire Server running on host [%1$s] listening on port [%2$d]%n",
|
|
||||||
SpringSessionDataGemFireServerConfiguration.SERVER_HOSTNAME, port);
|
|
||||||
|
|
||||||
System.setProperty("spring.session.data.gemfire.port", String.valueOf(port));
|
|
||||||
|
|
||||||
String processWorkingDirectoryPathname =
|
|
||||||
String.format("gemfire-client-server-tests-%1$s", TIMESTAMP.format(new Date()));
|
|
||||||
|
|
||||||
processWorkingDirectory = createDirectory(processWorkingDirectoryPathname);
|
|
||||||
|
|
||||||
gemfireServer = run(SpringSessionDataGemFireServerConfiguration.class, processWorkingDirectory,
|
|
||||||
String.format("-Dspring.session.data.gemfire.port=%1$d", port));
|
|
||||||
|
|
||||||
assertThat(waitForCacheServerToStart(SpringSessionDataGemFireServerConfiguration.SERVER_HOSTNAME, port))
|
|
||||||
.isTrue();
|
|
||||||
|
|
||||||
System.err.printf("GemFire Server [startup time = %1$d ms]%n", System.currentTimeMillis() - t0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass
|
|
||||||
public static void stopGemFireServer() {
|
|
||||||
if (gemfireServer != null) {
|
|
||||||
gemfireServer.destroy();
|
|
||||||
System.err.printf("GemFire Server [exit code = %1$d]%n",
|
|
||||||
waitForProcessToStop(gemfireServer, processWorkingDirectory));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Boolean.valueOf(System.getProperty("spring.session.data.gemfire.fork.clean", Boolean.TRUE.toString()))) {
|
|
||||||
FileSystemUtils.deleteRecursively(processWorkingDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(waitForClientCacheToClose(DEFAULT_WAIT_DURATION)).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
assertThat(GemFireUtils.isClient(gemfireCache)).isTrue();
|
|
||||||
|
|
||||||
Region<Object, ExpiringSession> springSessionGemFireRegion =
|
|
||||||
gemfireCache.getRegion(SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
|
|
||||||
assertThat(springSessionGemFireRegion).isNotNull();
|
|
||||||
|
|
||||||
RegionAttributes<Object, ExpiringSession> springSessionGemFireRegionAttributes =
|
|
||||||
springSessionGemFireRegion.getAttributes();
|
|
||||||
|
|
||||||
assertThat(springSessionGemFireRegionAttributes).isNotNull();
|
|
||||||
assertThat(springSessionGemFireRegionAttributes.getDataPolicy()).isEqualTo(DataPolicy.NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
this.sessionEventListener.getSessionEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void createSessionFiresSessionCreatedEvent() {
|
|
||||||
long beforeOrAtCreationTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
ExpiringSession expectedSession = save(createSession());
|
|
||||||
|
|
||||||
AbstractSessionEvent sessionEvent = this.sessionEventListener.waitForSessionEvent(500);
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
|
|
||||||
|
|
||||||
ExpiringSession createdSession = sessionEvent.getSession();
|
|
||||||
|
|
||||||
assertThat(createdSession.getId()).isEqualTo(expectedSession.getId());
|
|
||||||
assertThat(createdSession.getCreationTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime);
|
|
||||||
assertThat(createdSession.getLastAccessedTime()).isEqualTo(createdSession.getCreationTime());
|
|
||||||
assertThat(createdSession.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
|
||||||
|
|
||||||
createdSession.setAttribute("attrOne", 1);
|
|
||||||
|
|
||||||
assertThat(save(touch(createdSession)).<Integer>getAttribute("attrOne")).isEqualTo(1);
|
|
||||||
|
|
||||||
sessionEvent = this.sessionEventListener.waitForSessionEvent(500);
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isNull();
|
|
||||||
|
|
||||||
this.gemfireSessionRepository.delete(expectedSession.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getExistingNonExpiredSessionBeforeAndAfterExpiration() {
|
|
||||||
ExpiringSession expectedSession = save(touch(createSession()));
|
|
||||||
|
|
||||||
AbstractSessionEvent sessionEvent = this.sessionEventListener.waitForSessionEvent(500);
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
|
|
||||||
assertThat(sessionEvent.<ExpiringSession>getSession()).isEqualTo(expectedSession);
|
|
||||||
assertThat(this.sessionEventListener.<SessionCreatedEvent>getSessionEvent()).isNull();
|
|
||||||
|
|
||||||
ExpiringSession savedSession = this.gemfireSessionRepository.getSession(expectedSession.getId());
|
|
||||||
|
|
||||||
assertThat(savedSession).isEqualTo(expectedSession);
|
|
||||||
|
|
||||||
sessionEvent = this.sessionEventListener.waitForSessionEvent(
|
|
||||||
TimeUnit.SECONDS.toMillis(MAX_INACTIVE_INTERVAL_IN_SECONDS + 1));
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionExpiredEvent.class);
|
|
||||||
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSession.getId());
|
|
||||||
|
|
||||||
ExpiringSession expiredSession = this.gemfireSessionRepository.getSession(expectedSession.getId());
|
|
||||||
|
|
||||||
assertThat(expiredSession).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deleteExistingNonExpiredSessionFiresSessionDeletedEventAndReturnsNullOnGet() {
|
|
||||||
ExpiringSession expectedSession = save(touch(createSession()));
|
|
||||||
|
|
||||||
AbstractSessionEvent sessionEvent = this.sessionEventListener.waitForSessionEvent(500);
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
|
|
||||||
assertThat(sessionEvent.<ExpiringSession>getSession()).isEqualTo(expectedSession);
|
|
||||||
|
|
||||||
this.gemfireSessionRepository.delete(expectedSession.getId());
|
|
||||||
|
|
||||||
sessionEvent = this.sessionEventListener.waitForSessionEvent(500);
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class);
|
|
||||||
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSession.getId());
|
|
||||||
|
|
||||||
ExpiringSession deletedSession = this.gemfireSessionRepository.getSession(expectedSession.getId());
|
|
||||||
|
|
||||||
assertThat(deletedSession).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnableGemFireHttpSession(regionName = SPRING_SESSION_GEMFIRE_REGION_NAME,
|
|
||||||
clientRegionShortcut = ClientRegionShortcut.CACHING_PROXY,
|
|
||||||
maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
|
||||||
static class SpringSessionDataGemFireClientConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
|
||||||
return new PropertySourcesPlaceholderConfigurer();
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties gemfireProperties() {
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
ClientCacheFactoryBean gemfireCache() {
|
|
||||||
ClientCacheFactoryBean clientCacheFactory = new ClientCacheFactoryBean();
|
|
||||||
|
|
||||||
clientCacheFactory.setClose(true);
|
|
||||||
clientCacheFactory.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
return clientCacheFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
PoolFactoryBean gemfirePool(
|
|
||||||
@Value("${spring.session.data.gemfire.port:" + DEFAULT_GEMFIRE_SERVER_PORT + "}") int port) {
|
|
||||||
|
|
||||||
PoolFactoryBean poolFactory = new PoolFactoryBean();
|
|
||||||
|
|
||||||
poolFactory.setKeepAlive(false);
|
|
||||||
poolFactory.setPingInterval(TimeUnit.SECONDS.toMillis(5));
|
|
||||||
poolFactory.setReadTimeout(2000); // 2 seconds
|
|
||||||
poolFactory.setRetryAttempts(1);
|
|
||||||
poolFactory.setSubscriptionEnabled(true);
|
|
||||||
|
|
||||||
poolFactory.setServers(Collections.singletonList(new ConnectionEndpoint(
|
|
||||||
SpringSessionDataGemFireServerConfiguration.SERVER_HOSTNAME, port)));
|
|
||||||
|
|
||||||
return poolFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public SessionEventListener sessionEventListener() {
|
|
||||||
return new SessionEventListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
// used for debugging purposes
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
public static void main(String[] args) {
|
|
||||||
ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(
|
|
||||||
SpringSessionDataGemFireClientConfiguration.class);
|
|
||||||
|
|
||||||
applicationContext.registerShutdownHook();
|
|
||||||
|
|
||||||
ClientCache clientCache = applicationContext.getBean(ClientCache.class);
|
|
||||||
|
|
||||||
for (InetSocketAddress server : clientCache.getCurrentServers()) {
|
|
||||||
System.err.printf("GemFire Server [host: %1$s, port: %2$d]%n",
|
|
||||||
server.getHostName(), server.getPort());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnableGemFireHttpSession(regionName = SPRING_SESSION_GEMFIRE_REGION_NAME,
|
|
||||||
maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
|
||||||
static class SpringSessionDataGemFireServerConfiguration {
|
|
||||||
|
|
||||||
static final String SERVER_HOSTNAME = "localhost";
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
|
||||||
return new PropertySourcesPlaceholderConfigurer();
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties gemfireProperties() {
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
|
|
||||||
gemfireProperties.setProperty("name", name());
|
|
||||||
gemfireProperties.setProperty("mcast-port", "0");
|
|
||||||
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
|
|
||||||
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
String name() {
|
|
||||||
return ClientServerGemFireOperationsSessionRepositoryIntegrationTests.class.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheFactoryBean gemfireCache() {
|
|
||||||
CacheFactoryBean gemfireCache = new CacheFactoryBean();
|
|
||||||
|
|
||||||
gemfireCache.setClose(true);
|
|
||||||
gemfireCache.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
return gemfireCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache,
|
|
||||||
@Value("${spring.session.data.gemfire.port:" + DEFAULT_GEMFIRE_SERVER_PORT + "}") int port) {
|
|
||||||
|
|
||||||
CacheServerFactoryBean cacheServerFactory = new CacheServerFactoryBean();
|
|
||||||
|
|
||||||
cacheServerFactory.setCache(gemfireCache);
|
|
||||||
cacheServerFactory.setAutoStartup(true);
|
|
||||||
cacheServerFactory.setBindAddress(SERVER_HOSTNAME);
|
|
||||||
cacheServerFactory.setPort(port);
|
|
||||||
|
|
||||||
return cacheServerFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
public static void main(String[] args) throws IOException {
|
|
||||||
AnnotationConfigApplicationContext context =
|
|
||||||
new AnnotationConfigApplicationContext(SpringSessionDataGemFireServerConfiguration.class);
|
|
||||||
|
|
||||||
context.registerShutdownHook();
|
|
||||||
|
|
||||||
writeProcessControlFile(WORKING_DIRECTORY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Cache;
|
|
||||||
import org.apache.geode.cache.client.ClientCache;
|
|
||||||
|
|
||||||
import org.junit.AfterClass;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
|
||||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.client.PoolFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.server.CacheServerFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.support.ConnectionEndpoint;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
|
||||||
import org.springframework.util.FileSystemUtils;
|
|
||||||
import org.springframework.util.SocketUtils;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration tests testing the addition/removal of HTTP Session Attributes
|
|
||||||
* and the proper persistence of the HTTP Session state in a GemFire cache
|
|
||||||
* across a client/server topology.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @see org.junit.Test
|
|
||||||
* @see org.junit.runner.RunWith
|
|
||||||
* @see org.springframework.context.ConfigurableApplicationContext
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
|
||||||
* @see org.springframework.test.context.ContextConfiguration
|
|
||||||
* @see org.springframework.test.context.junit4.SpringRunner
|
|
||||||
* @see org.apache.geode.cache.Cache
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
* @see org.apache.geode.cache.client.ClientCache
|
|
||||||
* @since 1.3.1
|
|
||||||
*/
|
|
||||||
@RunWith(SpringRunner.class)
|
|
||||||
@ContextConfiguration(classes =
|
|
||||||
ClientServerHttpSessionAttributesDeltaIntegrationTests.SpringSessionDataGemFireClientConfiguration.class)
|
|
||||||
public class ClientServerHttpSessionAttributesDeltaIntegrationTests extends AbstractGemFireIntegrationTests {
|
|
||||||
|
|
||||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1;
|
|
||||||
|
|
||||||
private static final DateFormat TIMESTAMP = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
|
|
||||||
|
|
||||||
private static File processWorkingDirectory;
|
|
||||||
|
|
||||||
private static Process gemfireServer;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void startGemFireServer() throws IOException {
|
|
||||||
long t0 = System.currentTimeMillis();
|
|
||||||
|
|
||||||
int port = SocketUtils.findAvailableTcpPort();
|
|
||||||
|
|
||||||
System.err.printf("Starting a GemFire Server running on host [%1$s] listening on port [%2$d]%n",
|
|
||||||
SpringSessionDataGemFireServerConfiguration.SERVER_HOSTNAME, port);
|
|
||||||
|
|
||||||
System.setProperty("spring.session.data.gemfire.port", String.valueOf(port));
|
|
||||||
|
|
||||||
String processWorkingDirectoryPathname =
|
|
||||||
String.format("gemfire-client-server-tests-%1$s", TIMESTAMP.format(new Date()));
|
|
||||||
|
|
||||||
processWorkingDirectory = createDirectory(processWorkingDirectoryPathname);
|
|
||||||
|
|
||||||
gemfireServer = run(SpringSessionDataGemFireServerConfiguration.class, processWorkingDirectory,
|
|
||||||
String.format("-Dspring.session.data.gemfire.port=%1$d", port));
|
|
||||||
|
|
||||||
assertThat(waitForCacheServerToStart(SpringSessionDataGemFireServerConfiguration.SERVER_HOSTNAME, port))
|
|
||||||
.isTrue();
|
|
||||||
|
|
||||||
System.err.printf("GemFire Server [startup time = %1$d ms]%n", System.currentTimeMillis() - t0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass
|
|
||||||
public static void stopGemFireServer() {
|
|
||||||
if (gemfireServer != null) {
|
|
||||||
gemfireServer.destroy();
|
|
||||||
System.err.printf("GemFire Server [exit code = %1$d]%n",
|
|
||||||
waitForProcessToStop(gemfireServer, processWorkingDirectory));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Boolean.valueOf(System.getProperty("spring.session.data.gemfire.fork.clean", Boolean.TRUE.toString()))) {
|
|
||||||
FileSystemUtils.deleteRecursively(processWorkingDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(waitForClientCacheToClose(DEFAULT_WAIT_DURATION)).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void sessionCreationAndAccessIsSuccessful() {
|
|
||||||
ExpiringSession session = save(touch(createSession()));
|
|
||||||
|
|
||||||
assertThat(session).isNotNull();
|
|
||||||
assertThat(session.isExpired()).isFalse();
|
|
||||||
|
|
||||||
session.setAttribute("attrOne", 1);
|
|
||||||
session.setAttribute("attrTwo", 2);
|
|
||||||
|
|
||||||
save(touch(session));
|
|
||||||
|
|
||||||
ExpiringSession loadedSession = get(session.getId());
|
|
||||||
|
|
||||||
assertThat(loadedSession).isNotNull();
|
|
||||||
assertThat(loadedSession.isExpired()).isFalse();
|
|
||||||
assertThat(loadedSession).isNotSameAs(session);
|
|
||||||
assertThat(loadedSession.getId()).isEqualTo(session.getId());
|
|
||||||
assertThat(loadedSession.<Integer>getAttribute("attrOne")).isEqualTo(1);
|
|
||||||
assertThat(loadedSession.<Integer>getAttribute("attrTwo")).isEqualTo(2);
|
|
||||||
|
|
||||||
loadedSession.removeAttribute("attrTwo");
|
|
||||||
|
|
||||||
assertThat(loadedSession.getAttributeNames()).doesNotContain("attrTwo");
|
|
||||||
assertThat(loadedSession.getAttributeNames()).hasSize(1);
|
|
||||||
|
|
||||||
save(touch(loadedSession));
|
|
||||||
|
|
||||||
ExpiringSession reloadedSession = get(loadedSession.getId());
|
|
||||||
|
|
||||||
assertThat(reloadedSession).isNotNull();
|
|
||||||
assertThat(reloadedSession.isExpired()).isFalse();
|
|
||||||
assertThat(reloadedSession).isNotSameAs(loadedSession);
|
|
||||||
assertThat(reloadedSession.getId()).isEqualTo(loadedSession.getId());
|
|
||||||
assertThat(reloadedSession.getAttributeNames()).hasSize(1);
|
|
||||||
assertThat(reloadedSession.getAttributeNames()).doesNotContain("attrTwo");
|
|
||||||
assertThat(reloadedSession.<Integer>getAttribute("attrOne")).isEqualTo(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnableGemFireHttpSession
|
|
||||||
static class SpringSessionDataGemFireClientConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
|
||||||
return new PropertySourcesPlaceholderConfigurer();
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties gemfireProperties() {
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
ClientCacheFactoryBean gemfireCache() {
|
|
||||||
ClientCacheFactoryBean clientCacheFactory = new ClientCacheFactoryBean();
|
|
||||||
|
|
||||||
clientCacheFactory.setClose(true);
|
|
||||||
clientCacheFactory.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
return clientCacheFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
PoolFactoryBean gemfirePool(@Value("${spring.session.data.gemfire.port:"
|
|
||||||
+ DEFAULT_GEMFIRE_SERVER_PORT + "}") int port) {
|
|
||||||
|
|
||||||
PoolFactoryBean poolFactory = new PoolFactoryBean();
|
|
||||||
|
|
||||||
poolFactory.setKeepAlive(false);
|
|
||||||
poolFactory.setPingInterval(TimeUnit.SECONDS.toMillis(5));
|
|
||||||
poolFactory.setReadTimeout(2000); // 2 seconds
|
|
||||||
poolFactory.setRetryAttempts(1);
|
|
||||||
poolFactory.setSubscriptionEnabled(true);
|
|
||||||
|
|
||||||
poolFactory.setServers(Collections.singletonList(new ConnectionEndpoint(
|
|
||||||
SpringSessionDataGemFireServerConfiguration.SERVER_HOSTNAME, port)));
|
|
||||||
|
|
||||||
return poolFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
// used for debugging purposes
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
public static void main(String[] args) {
|
|
||||||
ConfigurableApplicationContext applicationContext =
|
|
||||||
new AnnotationConfigApplicationContext(SpringSessionDataGemFireClientConfiguration.class);
|
|
||||||
|
|
||||||
applicationContext.registerShutdownHook();
|
|
||||||
|
|
||||||
ClientCache clientCache = applicationContext.getBean(ClientCache.class);
|
|
||||||
|
|
||||||
for (InetSocketAddress server : clientCache.getCurrentServers()) {
|
|
||||||
System.err.printf("GemFire Server [host: %1$s, port: %2$d]%n",
|
|
||||||
server.getHostName(), server.getPort());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
|
||||||
static class SpringSessionDataGemFireServerConfiguration {
|
|
||||||
|
|
||||||
static final String SERVER_HOSTNAME = "localhost";
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
|
||||||
return new PropertySourcesPlaceholderConfigurer();
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties gemfireProperties() {
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
|
|
||||||
gemfireProperties.setProperty("name", name());
|
|
||||||
gemfireProperties.setProperty("mcast-port", "0");
|
|
||||||
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
|
|
||||||
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
String name() {
|
|
||||||
return ClientServerHttpSessionAttributesDeltaIntegrationTests.class.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheFactoryBean gemfireCache() {
|
|
||||||
CacheFactoryBean gemfireCache = new CacheFactoryBean();
|
|
||||||
|
|
||||||
gemfireCache.setClose(true);
|
|
||||||
gemfireCache.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
return gemfireCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache,
|
|
||||||
@Value("${spring.session.data.gemfire.port:" + DEFAULT_GEMFIRE_SERVER_PORT + "}") int port) {
|
|
||||||
|
|
||||||
CacheServerFactoryBean cacheServerFactory = new CacheServerFactoryBean();
|
|
||||||
|
|
||||||
cacheServerFactory.setCache(gemfireCache);
|
|
||||||
cacheServerFactory.setAutoStartup(true);
|
|
||||||
cacheServerFactory.setBindAddress(SERVER_HOSTNAME);
|
|
||||||
cacheServerFactory.setPort(port);
|
|
||||||
|
|
||||||
return cacheServerFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
public static void main(String[] args) throws IOException {
|
|
||||||
AnnotationConfigApplicationContext context =
|
|
||||||
new AnnotationConfigApplicationContext(SpringSessionDataGemFireServerConfiguration.class);
|
|
||||||
|
|
||||||
context.registerShutdownHook();
|
|
||||||
|
|
||||||
writeProcessControlFile(WORKING_DIRECTORY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,309 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Cache;
|
|
||||||
import org.apache.geode.cache.client.ClientCache;
|
|
||||||
import org.junit.AfterClass;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
|
||||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.client.PoolFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.server.CacheServerFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.support.ConnectionEndpoint;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
|
||||||
import org.springframework.session.events.AbstractSessionEvent;
|
|
||||||
import org.springframework.session.events.SessionCreatedEvent;
|
|
||||||
import org.springframework.session.events.SessionDeletedEvent;
|
|
||||||
import org.springframework.session.events.SessionExpiredEvent;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
|
||||||
import org.springframework.util.FileSystemUtils;
|
|
||||||
import org.springframework.util.SocketUtils;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ClientServerProxyRegionSessionOperationsIntegrationTests class...
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
@RunWith(SpringRunner.class)
|
|
||||||
@ContextConfiguration(classes =
|
|
||||||
ClientServerProxyRegionSessionOperationsIntegrationTests.SpringSessionDataGemFireClientConfiguration.class)
|
|
||||||
public class ClientServerProxyRegionSessionOperationsIntegrationTests extends AbstractGemFireIntegrationTests {
|
|
||||||
|
|
||||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1;
|
|
||||||
|
|
||||||
private static final DateFormat TIMESTAMP = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
|
|
||||||
|
|
||||||
private static File processWorkingDirectory;
|
|
||||||
|
|
||||||
private static Process gemfireServer;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private SessionEventListener sessionEventListener;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void startGemFireServer() throws IOException {
|
|
||||||
long t0 = System.currentTimeMillis();
|
|
||||||
|
|
||||||
int port = SocketUtils.findAvailableTcpPort();
|
|
||||||
|
|
||||||
System.err.printf("Starting a GemFire Server running on host [%1$s] listening on port [%2$d]%n",
|
|
||||||
SpringSessionDataGemFireServerConfiguration.SERVER_HOSTNAME, port);
|
|
||||||
|
|
||||||
System.setProperty("spring.session.data.gemfire.port", String.valueOf(port));
|
|
||||||
|
|
||||||
String processWorkingDirectoryPathname =
|
|
||||||
String.format("gemfire-client-server-tests-%1$s", TIMESTAMP.format(new Date()));
|
|
||||||
|
|
||||||
processWorkingDirectory = createDirectory(processWorkingDirectoryPathname);
|
|
||||||
|
|
||||||
gemfireServer = run(SpringSessionDataGemFireServerConfiguration.class, processWorkingDirectory,
|
|
||||||
String.format("-Dspring.session.data.gemfire.port=%1$d", port));
|
|
||||||
|
|
||||||
assertThat(waitForCacheServerToStart(SpringSessionDataGemFireServerConfiguration.SERVER_HOSTNAME, port))
|
|
||||||
.isTrue();
|
|
||||||
|
|
||||||
System.err.printf("GemFire Server [startup time = %1$d ms]%n", System.currentTimeMillis() - t0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass
|
|
||||||
public static void stopGemFireServer() {
|
|
||||||
if (gemfireServer != null) {
|
|
||||||
gemfireServer.destroy();
|
|
||||||
System.err.printf("GemFire Server [exit code = %1$d]%n",
|
|
||||||
waitForProcessToStop(gemfireServer, processWorkingDirectory));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Boolean.valueOf(System.getProperty("spring.session.data.gemfire.fork.clean", Boolean.TRUE.toString()))) {
|
|
||||||
FileSystemUtils.deleteRecursively(processWorkingDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(waitForClientCacheToClose(DEFAULT_WAIT_DURATION)).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void createReadUpdateExpireRecreateDeleteRecreateSessionResultsCorrectSessionCreatedEvents() {
|
|
||||||
ExpiringSession session = save(touch(createSession()));
|
|
||||||
|
|
||||||
assertValidSession(session);
|
|
||||||
|
|
||||||
AbstractSessionEvent sessionEvent = this.sessionEventListener.waitForSessionEvent(500);
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
|
|
||||||
assertThat(sessionEvent.getSessionId()).isEqualTo(session.getId());
|
|
||||||
|
|
||||||
// GET
|
|
||||||
ExpiringSession loadedSession = get(session.getId());
|
|
||||||
|
|
||||||
assertThat(loadedSession).isNotNull();
|
|
||||||
assertThat(loadedSession.getId()).isEqualTo(session.getId());
|
|
||||||
assertThat(loadedSession.getCreationTime()).isEqualTo(session.getCreationTime());
|
|
||||||
assertThat(loadedSession.getLastAccessedTime()).isGreaterThanOrEqualTo((session.getLastAccessedTime()));
|
|
||||||
|
|
||||||
sessionEvent = this.sessionEventListener.waitForSessionEvent(500);
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isNull();
|
|
||||||
|
|
||||||
loadedSession.setAttribute("attrOne", 1);
|
|
||||||
loadedSession.setAttribute("attrTwo", 2);
|
|
||||||
|
|
||||||
// UPDATE
|
|
||||||
save(touch(loadedSession));
|
|
||||||
|
|
||||||
sessionEvent = this.sessionEventListener.waitForSessionEvent(500);
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isNull();
|
|
||||||
|
|
||||||
// EXPIRE
|
|
||||||
sessionEvent = this.sessionEventListener.waitForSessionEvent(
|
|
||||||
TimeUnit.SECONDS.toMillis(MAX_INACTIVE_INTERVAL_IN_SECONDS + 1));
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionExpiredEvent.class);
|
|
||||||
assertThat(sessionEvent.getSessionId()).isEqualTo(session.getId());
|
|
||||||
|
|
||||||
// RECREATE
|
|
||||||
save(touch(session));
|
|
||||||
|
|
||||||
sessionEvent = this.sessionEventListener.waitForSessionEvent(500);
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
|
|
||||||
assertThat(sessionEvent.getSessionId()).isEqualTo(session.getId());
|
|
||||||
|
|
||||||
// DELETE
|
|
||||||
delete(session);
|
|
||||||
|
|
||||||
sessionEvent = this.sessionEventListener.waitForSessionEvent(500);
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class);
|
|
||||||
assertThat(sessionEvent.getSessionId()).isEqualTo(session.getId());
|
|
||||||
|
|
||||||
// RECREATE
|
|
||||||
save(touch(session));
|
|
||||||
|
|
||||||
sessionEvent = this.sessionEventListener.waitForSessionEvent(500);
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
|
|
||||||
assertThat(sessionEvent.getSessionId()).isEqualTo(session.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnableGemFireHttpSession
|
|
||||||
static class SpringSessionDataGemFireClientConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
|
||||||
return new PropertySourcesPlaceholderConfigurer();
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties gemfireProperties() {
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
ClientCacheFactoryBean gemfireCache() {
|
|
||||||
ClientCacheFactoryBean clientCacheFactory = new ClientCacheFactoryBean();
|
|
||||||
|
|
||||||
clientCacheFactory.setClose(true);
|
|
||||||
clientCacheFactory.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
return clientCacheFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
PoolFactoryBean gemfirePool(@Value("${spring.session.data.gemfire.port:"
|
|
||||||
+ DEFAULT_GEMFIRE_SERVER_PORT + "}") int port) {
|
|
||||||
|
|
||||||
PoolFactoryBean poolFactory = new PoolFactoryBean();
|
|
||||||
|
|
||||||
poolFactory.setKeepAlive(false);
|
|
||||||
poolFactory.setPingInterval(TimeUnit.SECONDS.toMillis(5));
|
|
||||||
poolFactory.setReadTimeout(2000); // 2 seconds
|
|
||||||
poolFactory.setRetryAttempts(1);
|
|
||||||
poolFactory.setSubscriptionEnabled(true);
|
|
||||||
|
|
||||||
poolFactory.setServers(Collections.singletonList(new ConnectionEndpoint(
|
|
||||||
SpringSessionDataGemFireServerConfiguration.SERVER_HOSTNAME, port)));
|
|
||||||
|
|
||||||
return poolFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public SessionEventListener sessionEventListener() {
|
|
||||||
return new SessionEventListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
// used for debugging purposes
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
public static void main(String[] args) {
|
|
||||||
ConfigurableApplicationContext applicationContext =
|
|
||||||
new AnnotationConfigApplicationContext(SpringSessionDataGemFireClientConfiguration.class);
|
|
||||||
|
|
||||||
applicationContext.registerShutdownHook();
|
|
||||||
|
|
||||||
ClientCache clientCache = applicationContext.getBean(ClientCache.class);
|
|
||||||
|
|
||||||
for (InetSocketAddress server : clientCache.getCurrentServers()) {
|
|
||||||
System.err.printf("GemFire Server [host: %1$s, port: %2$d]%n",
|
|
||||||
server.getHostName(), server.getPort());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
|
||||||
static class SpringSessionDataGemFireServerConfiguration {
|
|
||||||
|
|
||||||
static final String SERVER_HOSTNAME = "localhost";
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
|
||||||
return new PropertySourcesPlaceholderConfigurer();
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties gemfireProperties() {
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
|
|
||||||
gemfireProperties.setProperty("name", name());
|
|
||||||
gemfireProperties.setProperty("mcast-port", "0");
|
|
||||||
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
|
|
||||||
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
String name() {
|
|
||||||
return ClientServerProxyRegionSessionOperationsIntegrationTests.class.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheFactoryBean gemfireCache() {
|
|
||||||
CacheFactoryBean gemfireCache = new CacheFactoryBean();
|
|
||||||
|
|
||||||
gemfireCache.setClose(true);
|
|
||||||
gemfireCache.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
return gemfireCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache,
|
|
||||||
@Value("${spring.session.data.gemfire.port:" + DEFAULT_GEMFIRE_SERVER_PORT + "}") int port) {
|
|
||||||
|
|
||||||
CacheServerFactoryBean cacheServerFactory = new CacheServerFactoryBean();
|
|
||||||
|
|
||||||
cacheServerFactory.setCache(gemfireCache);
|
|
||||||
cacheServerFactory.setAutoStartup(true);
|
|
||||||
cacheServerFactory.setBindAddress(SERVER_HOSTNAME);
|
|
||||||
cacheServerFactory.setPort(port);
|
|
||||||
|
|
||||||
return cacheServerFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
public static void main(String[] args) throws IOException {
|
|
||||||
AnnotationConfigApplicationContext context =
|
|
||||||
new AnnotationConfigApplicationContext(SpringSessionDataGemFireServerConfiguration.class);
|
|
||||||
|
|
||||||
context.registerShutdownHook();
|
|
||||||
|
|
||||||
writeProcessControlFile(WORKING_DIRECTORY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,459 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.DataPolicy;
|
|
||||||
import org.apache.geode.cache.ExpirationAction;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.query.Query;
|
|
||||||
import org.apache.geode.cache.query.QueryService;
|
|
||||||
import org.apache.geode.cache.query.SelectResults;
|
|
||||||
import org.apache.geode.pdx.PdxReader;
|
|
||||||
import org.apache.geode.pdx.PdxSerializable;
|
|
||||||
import org.apache.geode.pdx.PdxWriter;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
|
||||||
import org.springframework.test.annotation.DirtiesContext;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.ObjectUtils;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration test to test the {@code findByPrincipalName} query method
|
|
||||||
* on {@link GemFireOperationsSessionRepository}.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.1.0
|
|
||||||
* @see org.junit.Test
|
|
||||||
* @see org.junit.runner.RunWith
|
|
||||||
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
|
|
||||||
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
|
|
||||||
* @see org.springframework.test.annotation.DirtiesContext
|
|
||||||
* @see org.springframework.test.context.ContextConfiguration
|
|
||||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
|
||||||
* @see org.springframework.test.context.web.WebAppConfiguration
|
|
||||||
* @see org.apache.geode.cache.Cache
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
*/
|
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
|
||||||
@ContextConfiguration
|
|
||||||
@DirtiesContext
|
|
||||||
@WebAppConfiguration
|
|
||||||
public class GemFireOperationsSessionRepositoryIntegrationTests extends AbstractGemFireIntegrationTests {
|
|
||||||
|
|
||||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 300;
|
|
||||||
|
|
||||||
private static final String GEMFIRE_LOG_LEVEL = "warning";
|
|
||||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
|
||||||
private static final String SPRING_SESSION_GEMFIRE_REGION_NAME = "TestPartitionedSessions";
|
|
||||||
|
|
||||||
SecurityContext context;
|
|
||||||
|
|
||||||
SecurityContext changedContext;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
this.context = SecurityContextHolder.createEmptyContext();
|
|
||||||
this.context.setAuthentication(
|
|
||||||
new UsernamePasswordAuthenticationToken("username-" + UUID.randomUUID(),
|
|
||||||
"na", AuthorityUtils.createAuthorityList("ROLE_USER")));
|
|
||||||
|
|
||||||
this.changedContext = SecurityContextHolder.createEmptyContext();
|
|
||||||
this.changedContext.setAuthentication(new UsernamePasswordAuthenticationToken(
|
|
||||||
"changedContext-" + UUID.randomUUID(), "na",
|
|
||||||
AuthorityUtils.createAuthorityList("ROLE_USER")));
|
|
||||||
|
|
||||||
assertThat(this.gemfireCache).isNotNull();
|
|
||||||
assertThat(this.gemfireSessionRepository).isNotNull();
|
|
||||||
assertThat(this.gemfireSessionRepository.getMaxInactiveIntervalInSeconds())
|
|
||||||
.isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
|
||||||
|
|
||||||
Region<Object, ExpiringSession> sessionRegion = this.gemfireCache.getRegion(SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
|
|
||||||
assertRegion(sessionRegion, SPRING_SESSION_GEMFIRE_REGION_NAME, DataPolicy.PARTITION);
|
|
||||||
assertEntryIdleTimeout(sessionRegion, ExpirationAction.INVALIDATE, MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Map<String, ExpiringSession> doFindByIndexNameAndIndexValue(
|
|
||||||
String indexName, String indexValue) {
|
|
||||||
|
|
||||||
return this.gemfireSessionRepository.findByIndexNameAndIndexValue(indexName,
|
|
||||||
indexValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Map<String, ExpiringSession> doFindByPrincipalName(String principalName) {
|
|
||||||
return doFindByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
|
|
||||||
principalName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({ "unchecked" })
|
|
||||||
protected Map<String, ExpiringSession> doFindByPrincipalName(String regionName,
|
|
||||||
String principalName) {
|
|
||||||
try {
|
|
||||||
Region<String, ExpiringSession> region = this.gemfireCache.getRegion(regionName);
|
|
||||||
|
|
||||||
assertThat(region).isNotNull();
|
|
||||||
|
|
||||||
QueryService queryService = region.getRegionService().getQueryService();
|
|
||||||
|
|
||||||
String queryString = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
|
|
||||||
region.getFullPath());
|
|
||||||
|
|
||||||
Query query = queryService.newQuery(queryString);
|
|
||||||
|
|
||||||
SelectResults<ExpiringSession> results =
|
|
||||||
(SelectResults<ExpiringSession>) query.execute(new Object[] { principalName });
|
|
||||||
|
|
||||||
Map<String, ExpiringSession> sessions = new HashMap<String, ExpiringSession>(results.size());
|
|
||||||
|
|
||||||
for (ExpiringSession session : results.asList()) {
|
|
||||||
sessions.put(session.getId(), session);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sessions;
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean enableQueryDebugging() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ExpiringSession setAttribute(ExpiringSession session, String attributeName,
|
|
||||||
Object attributeValue) {
|
|
||||||
|
|
||||||
session.setAttribute(attributeName, attributeValue);
|
|
||||||
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void findSessionsByIndexedSessionAttributeNameValues() {
|
|
||||||
ExpiringSession johnBlumSession = save(touch(setAttribute(
|
|
||||||
createSession("johnBlum"), "vip", "yes")));
|
|
||||||
ExpiringSession robWinchSession = save(touch(setAttribute(
|
|
||||||
createSession("robWinch"), "vip", "yes")));
|
|
||||||
ExpiringSession jonDoeSession = save(touch(setAttribute(
|
|
||||||
createSession("jonDoe"), "vip", "no")));
|
|
||||||
ExpiringSession pieDoeSession = save(touch(setAttribute(
|
|
||||||
createSession("pieDoe"), "viper", "true")));
|
|
||||||
ExpiringSession sourDoeSession = save(touch(createSession("sourDoe")));
|
|
||||||
|
|
||||||
assertThat(this.<ExpiringSession>get(johnBlumSession.getId())).isEqualTo(johnBlumSession);
|
|
||||||
assertThat(johnBlumSession.<String>getAttribute("vip")).isEqualTo("yes");
|
|
||||||
assertThat(this.<ExpiringSession>get(robWinchSession.getId())).isEqualTo(robWinchSession);
|
|
||||||
assertThat(robWinchSession.<String>getAttribute("vip")).isEqualTo("yes");
|
|
||||||
assertThat(this.<ExpiringSession>get(jonDoeSession.getId())).isEqualTo(jonDoeSession);
|
|
||||||
assertThat(jonDoeSession.<String>getAttribute("vip")).isEqualTo("no");
|
|
||||||
assertThat(this.<ExpiringSession>get(pieDoeSession.getId())).isEqualTo(pieDoeSession);
|
|
||||||
assertThat(pieDoeSession.getAttributeNames().contains("vip")).isFalse();
|
|
||||||
assertThat(this.<ExpiringSession>get(sourDoeSession.getId())).isEqualTo(sourDoeSession);
|
|
||||||
assertThat(sourDoeSession.getAttributeNames().contains("vip")).isFalse();
|
|
||||||
|
|
||||||
Map<String, ExpiringSession> vipSessions = doFindByIndexNameAndIndexValue("vip", "yes");
|
|
||||||
|
|
||||||
assertThat(vipSessions).isNotNull();
|
|
||||||
assertThat(vipSessions.size()).isEqualTo(2);
|
|
||||||
assertThat(vipSessions.get(johnBlumSession.getId())).isEqualTo(johnBlumSession);
|
|
||||||
assertThat(vipSessions.get(robWinchSession.getId())).isEqualTo(robWinchSession);
|
|
||||||
assertThat(vipSessions.containsKey(jonDoeSession.getId()));
|
|
||||||
assertThat(vipSessions.containsKey(pieDoeSession.getId()));
|
|
||||||
assertThat(vipSessions.containsKey(sourDoeSession.getId()));
|
|
||||||
|
|
||||||
Map<String, ExpiringSession> nonVipSessions = doFindByIndexNameAndIndexValue(
|
|
||||||
"vip", "no");
|
|
||||||
|
|
||||||
assertThat(nonVipSessions).isNotNull();
|
|
||||||
assertThat(nonVipSessions.size()).isEqualTo(1);
|
|
||||||
assertThat(nonVipSessions.get(jonDoeSession.getId())).isEqualTo(jonDoeSession);
|
|
||||||
assertThat(nonVipSessions.containsKey(johnBlumSession.getId()));
|
|
||||||
assertThat(nonVipSessions.containsKey(robWinchSession.getId()));
|
|
||||||
assertThat(nonVipSessions.containsKey(pieDoeSession.getId()));
|
|
||||||
assertThat(nonVipSessions.containsKey(sourDoeSession.getId()));
|
|
||||||
|
|
||||||
Map<String, ExpiringSession> noSessions = doFindByIndexNameAndIndexValue(
|
|
||||||
"nonExistingAttribute", "test");
|
|
||||||
|
|
||||||
assertThat(noSessions).isNotNull();
|
|
||||||
assertThat(noSessions.isEmpty()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void findSessionsByPrincipalName() {
|
|
||||||
ExpiringSession sessionOne = save(touch(createSession("robWinch")));
|
|
||||||
ExpiringSession sessionTwo = save(touch(createSession("johnBlum")));
|
|
||||||
ExpiringSession sessionThree = save(touch(createSession("robWinch")));
|
|
||||||
ExpiringSession sessionFour = save(touch(createSession("johnBlum")));
|
|
||||||
ExpiringSession sessionFive = save(touch(createSession("robWinch")));
|
|
||||||
|
|
||||||
assertThat(this.<ExpiringSession>get(sessionOne.getId())).isEqualTo(sessionOne);
|
|
||||||
assertThat(this.<ExpiringSession>get(sessionTwo.getId())).isEqualTo(sessionTwo);
|
|
||||||
assertThat(this.<ExpiringSession>get(sessionThree.getId())).isEqualTo(sessionThree);
|
|
||||||
assertThat(this.<ExpiringSession>get(sessionFour.getId())).isEqualTo(sessionFour);
|
|
||||||
assertThat(this.<ExpiringSession>get(sessionFive.getId())).isEqualTo(sessionFive);
|
|
||||||
|
|
||||||
Map<String, ExpiringSession> johnBlumSessions = doFindByPrincipalName("johnBlum");
|
|
||||||
|
|
||||||
assertThat(johnBlumSessions).isNotNull();
|
|
||||||
assertThat(johnBlumSessions.size()).isEqualTo(2);
|
|
||||||
assertThat(johnBlumSessions.containsKey(sessionOne.getId())).isFalse();
|
|
||||||
assertThat(johnBlumSessions.containsKey(sessionThree.getId())).isFalse();
|
|
||||||
assertThat(johnBlumSessions.containsKey(sessionFive.getId())).isFalse();
|
|
||||||
assertThat(johnBlumSessions.get(sessionTwo.getId())).isEqualTo(sessionTwo);
|
|
||||||
assertThat(johnBlumSessions.get(sessionFour.getId())).isEqualTo(sessionFour);
|
|
||||||
|
|
||||||
Map<String, ExpiringSession> robWinchSessions = doFindByPrincipalName("robWinch");
|
|
||||||
|
|
||||||
assertThat(robWinchSessions).isNotNull();
|
|
||||||
assertThat(robWinchSessions.size()).isEqualTo(3);
|
|
||||||
assertThat(robWinchSessions.containsKey(sessionTwo.getId())).isFalse();
|
|
||||||
assertThat(robWinchSessions.containsKey(sessionFour.getId())).isFalse();
|
|
||||||
assertThat(robWinchSessions.get(sessionOne.getId())).isEqualTo(sessionOne);
|
|
||||||
assertThat(robWinchSessions.get(sessionThree.getId())).isEqualTo(sessionThree);
|
|
||||||
assertThat(robWinchSessions.get(sessionFive.getId())).isEqualTo(sessionFive);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void findSessionsBySecurityPrincipalName() {
|
|
||||||
ExpiringSession toSave = this.gemfireSessionRepository.createSession();
|
|
||||||
|
|
||||||
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
|
|
||||||
|
|
||||||
save(toSave);
|
|
||||||
|
|
||||||
Map<String, ExpiringSession> findByPrincipalName = doFindByPrincipalName(getSecurityName());
|
|
||||||
|
|
||||||
assertThat(findByPrincipalName).hasSize(1);
|
|
||||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void findSessionsByChangedSecurityPrincipalName() {
|
|
||||||
ExpiringSession toSave = this.gemfireSessionRepository.createSession();
|
|
||||||
|
|
||||||
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
|
|
||||||
|
|
||||||
save(toSave);
|
|
||||||
|
|
||||||
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext);
|
|
||||||
|
|
||||||
save(toSave);
|
|
||||||
|
|
||||||
Map<String, ExpiringSession> findByPrincipalName = doFindByPrincipalName(getSecurityName());
|
|
||||||
|
|
||||||
assertThat(findByPrincipalName).isEmpty();
|
|
||||||
|
|
||||||
findByPrincipalName = doFindByPrincipalName(getChangedSecurityName());
|
|
||||||
|
|
||||||
assertThat(findByPrincipalName).hasSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void findsNoSessionsByNonExistingPrincipal() {
|
|
||||||
Map<String, ExpiringSession> nonExistingPrincipalSessions =
|
|
||||||
doFindByPrincipalName("nonExistingPrincipalName");
|
|
||||||
|
|
||||||
assertThat(nonExistingPrincipalSessions).isNotNull();
|
|
||||||
assertThat(nonExistingPrincipalSessions.isEmpty()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void findsNoSessionsAfterPrincipalIsRemoved() {
|
|
||||||
String username = "doesNotFindAfterPrincipalRemoved";
|
|
||||||
ExpiringSession session = save(touch(createSession(username)));
|
|
||||||
|
|
||||||
session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, null);
|
|
||||||
|
|
||||||
save(session);
|
|
||||||
|
|
||||||
Map<String, ExpiringSession> nonExistingPrincipalSessions = doFindByPrincipalName(username);
|
|
||||||
|
|
||||||
assertThat(nonExistingPrincipalSessions).isNotNull();
|
|
||||||
assertThat(nonExistingPrincipalSessions.isEmpty()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void saveAndReadSessionWithAttributes() {
|
|
||||||
ExpiringSession expectedSession = this.gemfireSessionRepository.createSession();
|
|
||||||
|
|
||||||
assertThat(expectedSession).isInstanceOf(AbstractGemFireOperationsSessionRepository.GemFireSession.class);
|
|
||||||
|
|
||||||
((AbstractGemFireOperationsSessionRepository.GemFireSession) expectedSession).setPrincipalName("jblum");
|
|
||||||
|
|
||||||
List<String> expectedAttributeNames = Arrays.asList("booleanAttribute", "numericAttribute", "stringAttribute",
|
|
||||||
"personAttribute");
|
|
||||||
|
|
||||||
Person jonDoe = new Person("Jon", "Doe");
|
|
||||||
|
|
||||||
expectedSession.setAttribute(expectedAttributeNames.get(0), true);
|
|
||||||
expectedSession.setAttribute(expectedAttributeNames.get(1), Math.PI);
|
|
||||||
expectedSession.setAttribute(expectedAttributeNames.get(2), "test");
|
|
||||||
expectedSession.setAttribute(expectedAttributeNames.get(3), jonDoe);
|
|
||||||
|
|
||||||
this.gemfireSessionRepository.save(touch(expectedSession));
|
|
||||||
|
|
||||||
ExpiringSession savedSession = this.gemfireSessionRepository.getSession(expectedSession.getId());
|
|
||||||
|
|
||||||
assertThat(savedSession).isEqualTo(expectedSession);
|
|
||||||
assertThat(savedSession).isInstanceOf(AbstractGemFireOperationsSessionRepository.GemFireSession.class);
|
|
||||||
assertThat(((AbstractGemFireOperationsSessionRepository.GemFireSession) savedSession).getPrincipalName())
|
|
||||||
.isEqualTo("jblum");
|
|
||||||
|
|
||||||
assertThat(savedSession.getAttributeNames().containsAll(expectedAttributeNames))
|
|
||||||
.as(String.format("Expected (%1$s); but was (%2$s)", expectedAttributeNames, savedSession.getAttributeNames()))
|
|
||||||
.isTrue();
|
|
||||||
|
|
||||||
assertThat(savedSession.<Boolean>getAttribute(expectedAttributeNames.get(0))).isTrue();
|
|
||||||
assertThat(savedSession.<Double>getAttribute(expectedAttributeNames.get(1))).isEqualTo(Math.PI);
|
|
||||||
assertThat(savedSession.<String>getAttribute(expectedAttributeNames.get(2))).isEqualTo("test");
|
|
||||||
assertThat(savedSession.<Person>getAttribute(expectedAttributeNames.get(3))).isEqualTo(jonDoe);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getSecurityName() {
|
|
||||||
return this.context.getAuthentication().getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getChangedSecurityName() {
|
|
||||||
return this.changedContext.getAuthentication().getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnableGemFireHttpSession(regionName = SPRING_SESSION_GEMFIRE_REGION_NAME, maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
|
||||||
static class SpringSessionGemFireConfiguration {
|
|
||||||
|
|
||||||
Properties gemfireProperties() {
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
|
|
||||||
gemfireProperties.setProperty("name", GemFireOperationsSessionRepositoryIntegrationTests.class.getName());
|
|
||||||
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
|
|
||||||
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheFactoryBean gemfireCache() {
|
|
||||||
CacheFactoryBean gemfireCache = new CacheFactoryBean();
|
|
||||||
|
|
||||||
gemfireCache.setClose(true);
|
|
||||||
gemfireCache.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
return gemfireCache;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Person implements Comparable<Person>, PdxSerializable {
|
|
||||||
|
|
||||||
private String firstName;
|
|
||||||
private String lastName;
|
|
||||||
|
|
||||||
public Person() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Person(String firstName, String lastName) {
|
|
||||||
this.firstName = validate(firstName);
|
|
||||||
this.lastName = validate(lastName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String validate(String value) {
|
|
||||||
Assert.hasText(value, String.format("The String value (%1$s) must be specified!", value));
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFirstName() {
|
|
||||||
return this.firstName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastName() {
|
|
||||||
return this.lastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return String.format("%1$s %2$s", getFirstName(), getLastName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void toData(PdxWriter pdxWriter) {
|
|
||||||
pdxWriter.writeString("firstName", getFirstName());
|
|
||||||
pdxWriter.writeString("lastName", getLastName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fromData(final PdxReader pdxReader) {
|
|
||||||
this.firstName = pdxReader.readString("firstName");
|
|
||||||
this.lastName = pdxReader.readString("lastName");
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("all")
|
|
||||||
public int compareTo(final Person person) {
|
|
||||||
int compareValue = getLastName().compareTo(person.getLastName());
|
|
||||||
return (compareValue != 0 ? compareValue
|
|
||||||
: getFirstName().compareTo(person.getFirstName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(final Object obj) {
|
|
||||||
if (obj == this) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(obj instanceof Person)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Person that = (Person) obj;
|
|
||||||
|
|
||||||
return ObjectUtils.nullSafeEquals(this.getFirstName(), that.getFirstName())
|
|
||||||
&& ObjectUtils.nullSafeEquals(this.getLastName(), that.getLastName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int hashValue = 17;
|
|
||||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getFirstName());
|
|
||||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getLastName());
|
|
||||||
return hashValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return getName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,344 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Cache;
|
|
||||||
import org.apache.geode.cache.DataPolicy;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.RegionAttributes;
|
|
||||||
import org.apache.geode.cache.client.ClientCache;
|
|
||||||
|
|
||||||
import org.junit.AfterClass;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
|
||||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.client.PoolFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.server.CacheServerFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.support.ConnectionEndpoint;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
|
||||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
|
||||||
import org.springframework.session.events.AbstractSessionEvent;
|
|
||||||
import org.springframework.session.events.SessionCreatedEvent;
|
|
||||||
import org.springframework.session.events.SessionExpiredEvent;
|
|
||||||
import org.springframework.test.annotation.DirtiesContext;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
|
||||||
import org.springframework.util.FileSystemUtils;
|
|
||||||
import org.springframework.util.SocketUtils;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration test to test the functionality of a GemFire cache client in a Spring Session application
|
|
||||||
* using a specifically named GemFire {@link org.apache.geode.cache.client.Pool} configured with
|
|
||||||
* the 'poolName' attribute in the Spring Session Data GemFire {@link EnableGemFireHttpSession} annotation.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @see org.junit.Test
|
|
||||||
* @see org.junit.runner.RunWith
|
|
||||||
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
|
||||||
* @see org.springframework.test.annotation.DirtiesContext
|
|
||||||
* @see org.springframework.test.context.ContextConfiguration
|
|
||||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
|
||||||
* @see org.springframework.test.context.web.WebAppConfiguration
|
|
||||||
* @see org.apache.geode.cache.Cache
|
|
||||||
* @see org.apache.geode.cache.client.ClientCache
|
|
||||||
* @see org.apache.geode.cache.client.Pool
|
|
||||||
* @see org.apache.geode.cache.server.CacheServer
|
|
||||||
* @since 1.3.0
|
|
||||||
*/
|
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
|
||||||
@ContextConfiguration(classes = MultiPoolClientServerGemFireOperationsSessionRepositoryIntegrationTests
|
|
||||||
.SpringSessionGemFireClientConfiguration.class)
|
|
||||||
@DirtiesContext
|
|
||||||
@WebAppConfiguration
|
|
||||||
public class MultiPoolClientServerGemFireOperationsSessionRepositoryIntegrationTests
|
|
||||||
extends AbstractGemFireIntegrationTests {
|
|
||||||
|
|
||||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1;
|
|
||||||
|
|
||||||
private static final DateFormat TIMESTAMP = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
|
|
||||||
|
|
||||||
private static File processWorkingDirectory;
|
|
||||||
|
|
||||||
private static Process gemfireServer;
|
|
||||||
|
|
||||||
private static final String SPRING_SESSION_GEMFIRE_REGION_NAME = "TestMultiPoolClientServerSessions";
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private SessionEventListener sessionEventListener;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void startGemFireServer() throws IOException {
|
|
||||||
final long t0 = System.currentTimeMillis();
|
|
||||||
|
|
||||||
final int port = SocketUtils.findAvailableTcpPort();
|
|
||||||
|
|
||||||
System.err.printf("Starting a GemFire Server on [%1$s] listening on port [%2$d]%n",
|
|
||||||
SpringSessionGemFireServerConfiguration.SERVER_HOSTNAME, port);
|
|
||||||
|
|
||||||
System.setProperty("spring.session.data.gemfire.port", String.valueOf(port));
|
|
||||||
|
|
||||||
String processWorkingDirectoryPathname = String.format("gemfire-multipool-client-server-tests-%1$s",
|
|
||||||
TIMESTAMP.format(new Date()));
|
|
||||||
|
|
||||||
processWorkingDirectory = createDirectory(processWorkingDirectoryPathname);
|
|
||||||
|
|
||||||
gemfireServer = run(SpringSessionGemFireServerConfiguration.class, processWorkingDirectory,
|
|
||||||
String.format("-Dspring.session.data.gemfire.port=%1$d", port));
|
|
||||||
|
|
||||||
assertThat(waitForCacheServerToStart(SpringSessionGemFireServerConfiguration.SERVER_HOSTNAME, port)).isTrue();
|
|
||||||
|
|
||||||
System.err.printf("GemFire Server [startup time = %1$d ms]%n", System.currentTimeMillis() - t0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass
|
|
||||||
public static void stopGemFireServerAndDeleteArtifacts() {
|
|
||||||
if (gemfireServer != null) {
|
|
||||||
gemfireServer.destroyForcibly();
|
|
||||||
System.err.printf("GemFire Server [exit code = %1$d]%n",
|
|
||||||
waitForProcessToStop(gemfireServer, processWorkingDirectory));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Boolean.valueOf(System.getProperty("spring.session.data.gemfire.fork.clean", Boolean.TRUE.toString()))) {
|
|
||||||
FileSystemUtils.deleteRecursively(processWorkingDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(waitForClientCacheToClose(DEFAULT_WAIT_DURATION)).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
assertThat(GemFireUtils.isClient(gemfireCache)).isTrue();
|
|
||||||
|
|
||||||
Region<Object, ExpiringSession> springSessionGemFireRegion =
|
|
||||||
gemfireCache.getRegion(SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
|
|
||||||
assertThat(springSessionGemFireRegion).isNotNull();
|
|
||||||
|
|
||||||
RegionAttributes<Object, ExpiringSession> springSessionGemFireRegionAttributes =
|
|
||||||
springSessionGemFireRegion.getAttributes();
|
|
||||||
|
|
||||||
assertThat(springSessionGemFireRegionAttributes).isNotNull();
|
|
||||||
assertThat(springSessionGemFireRegionAttributes.getDataPolicy()).isEqualTo(DataPolicy.EMPTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static ConnectionEndpoint newConnectionEndpoint(String host, int port) {
|
|
||||||
return new ConnectionEndpoint(host, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getExistingNonExpiredSessionBeforeAndAfterExpiration() {
|
|
||||||
ExpiringSession expectedSession = save(touch(createSession()));
|
|
||||||
|
|
||||||
AbstractSessionEvent sessionEvent = this.sessionEventListener.waitForSessionEvent(500);
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
|
|
||||||
assertThat(sessionEvent.<ExpiringSession>getSession()).isEqualTo(expectedSession);
|
|
||||||
assertThat(this.sessionEventListener.<SessionCreatedEvent>getSessionEvent()).isNull();
|
|
||||||
|
|
||||||
ExpiringSession savedSession = this.gemfireSessionRepository.getSession(expectedSession.getId());
|
|
||||||
|
|
||||||
assertThat(savedSession).isEqualTo(expectedSession);
|
|
||||||
|
|
||||||
this.sessionEventListener.getSessionEvent();
|
|
||||||
|
|
||||||
sessionEvent = this.sessionEventListener.waitForSessionEvent(
|
|
||||||
TimeUnit.SECONDS.toMillis(MAX_INACTIVE_INTERVAL_IN_SECONDS + 1));
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionExpiredEvent.class);
|
|
||||||
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSession.getId());
|
|
||||||
|
|
||||||
ExpiringSession expiredSession = this.gemfireSessionRepository.getSession(expectedSession.getId());
|
|
||||||
|
|
||||||
assertThat(expiredSession).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnableGemFireHttpSession(regionName = SPRING_SESSION_GEMFIRE_REGION_NAME, poolName = "serverPool",
|
|
||||||
maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
|
||||||
static class SpringSessionGemFireClientConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
|
||||||
return new PropertySourcesPlaceholderConfigurer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
Properties gemfireProperties() {
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
gemfireProperties.setProperty("name", name());
|
|
||||||
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
String name() {
|
|
||||||
return SpringSessionGemFireClientConfiguration.class.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
ClientCacheFactoryBean gemfireCache() {
|
|
||||||
ClientCacheFactoryBean gemfireCache = new ClientCacheFactoryBean();
|
|
||||||
|
|
||||||
gemfireCache.setClose(true);
|
|
||||||
gemfireCache.setPoolName("gemfirePool");
|
|
||||||
gemfireCache.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
return gemfireCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
PoolFactoryBean gemfirePool() {
|
|
||||||
PoolFactoryBean poolFactory = new PoolFactoryBean();
|
|
||||||
|
|
||||||
poolFactory.setFreeConnectionTimeout(5000); // 5 seconds
|
|
||||||
poolFactory.setKeepAlive(false);
|
|
||||||
poolFactory.setMinConnections(0);
|
|
||||||
poolFactory.setReadTimeout(500);
|
|
||||||
|
|
||||||
// deliberately set to a non-existing GemFire (Cache) Server
|
|
||||||
poolFactory.addServers(newConnectionEndpoint("localhost", 53135));
|
|
||||||
|
|
||||||
return poolFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
PoolFactoryBean serverPool(@Value("${spring.session.data.gemfire.port:"
|
|
||||||
+ DEFAULT_GEMFIRE_SERVER_PORT + "}") int port) {
|
|
||||||
|
|
||||||
PoolFactoryBean poolFactory = new PoolFactoryBean();
|
|
||||||
|
|
||||||
poolFactory.setFreeConnectionTimeout(5000); // 5 seconds
|
|
||||||
poolFactory.setKeepAlive(false);
|
|
||||||
poolFactory.setMaxConnections(SpringSessionGemFireServerConfiguration.MAX_CONNECTIONS);
|
|
||||||
poolFactory.setPingInterval(TimeUnit.SECONDS.toMillis(5));
|
|
||||||
poolFactory.setReadTimeout(2000); // 2 seconds
|
|
||||||
poolFactory.setRetryAttempts(1);
|
|
||||||
poolFactory.setSubscriptionEnabled(true);
|
|
||||||
poolFactory.setThreadLocalConnections(false);
|
|
||||||
|
|
||||||
poolFactory.addServers(newConnectionEndpoint(
|
|
||||||
SpringSessionGemFireServerConfiguration.SERVER_HOSTNAME, port));
|
|
||||||
|
|
||||||
return poolFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public AbstractGemFireIntegrationTests.SessionEventListener sessionEventListener() {
|
|
||||||
return new AbstractGemFireIntegrationTests.SessionEventListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
// used for debugging purposes
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
public static void main(final String[] args) {
|
|
||||||
ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(
|
|
||||||
SpringSessionGemFireClientConfiguration.class);
|
|
||||||
|
|
||||||
applicationContext.registerShutdownHook();
|
|
||||||
|
|
||||||
ClientCache clientCache = applicationContext.getBean(ClientCache.class);
|
|
||||||
|
|
||||||
for (InetSocketAddress server : clientCache.getCurrentServers()) {
|
|
||||||
System.err.printf("GemFire Server [host: %1$s, port: %2$d]%n",
|
|
||||||
server.getHostName(), server.getPort());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnableGemFireHttpSession(regionName = SPRING_SESSION_GEMFIRE_REGION_NAME, maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
|
||||||
static class SpringSessionGemFireServerConfiguration {
|
|
||||||
|
|
||||||
static final int MAX_CONNECTIONS = 50;
|
|
||||||
static final String SERVER_HOSTNAME = "localhost";
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
|
||||||
return new PropertySourcesPlaceholderConfigurer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
Properties gemfireProperties() {
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
|
|
||||||
gemfireProperties.setProperty("name", name());
|
|
||||||
gemfireProperties.setProperty("mcast-port", "0");
|
|
||||||
gemfireProperties.setProperty("log-file", "server.log");
|
|
||||||
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
|
|
||||||
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
String name() {
|
|
||||||
return SpringSessionGemFireServerConfiguration.class.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheFactoryBean gemfireCache() {
|
|
||||||
CacheFactoryBean gemfireCache = new CacheFactoryBean();
|
|
||||||
|
|
||||||
gemfireCache.setClose(true);
|
|
||||||
gemfireCache.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
return gemfireCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache,
|
|
||||||
@Value("${spring.session.data.gemfire.port:" + DEFAULT_GEMFIRE_SERVER_PORT + "}") int port) {
|
|
||||||
|
|
||||||
CacheServerFactoryBean cacheServerFactory = new CacheServerFactoryBean();
|
|
||||||
|
|
||||||
cacheServerFactory.setAutoStartup(true);
|
|
||||||
cacheServerFactory.setBindAddress(SERVER_HOSTNAME);
|
|
||||||
cacheServerFactory.setCache(gemfireCache);
|
|
||||||
cacheServerFactory.setMaxConnections(MAX_CONNECTIONS);
|
|
||||||
cacheServerFactory.setPort(port);
|
|
||||||
|
|
||||||
return cacheServerFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
public static void main(final String[] args) throws IOException {
|
|
||||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
|
||||||
SpringSessionGemFireServerConfiguration.class);
|
|
||||||
context.registerShutdownHook();
|
|
||||||
writeProcessControlFile(WORKING_DIRECTORY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire.config.annotation.web.http;
|
|
||||||
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.DataPolicy;
|
|
||||||
import org.apache.geode.cache.ExpirationAction;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.RegionShortcut;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests;
|
|
||||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
|
||||||
import org.springframework.session.events.AbstractSessionEvent;
|
|
||||||
import org.springframework.session.events.SessionCreatedEvent;
|
|
||||||
import org.springframework.session.events.SessionDeletedEvent;
|
|
||||||
import org.springframework.session.events.SessionExpiredEvent;
|
|
||||||
import org.springframework.test.annotation.DirtiesContext;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The EnableGemFireHttpSessionEventsIntegrationTests class is a test suite of test cases
|
|
||||||
* testing the Session Event functionality and behavior of the
|
|
||||||
* GemFireOperationsSessionRepository and GemFire's configuration.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.1.0
|
|
||||||
* @see org.junit.Test
|
|
||||||
* @see org.junit.runner.RunWith
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
|
|
||||||
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
|
|
||||||
* @see org.springframework.session.events.SessionCreatedEvent
|
|
||||||
* @see org.springframework.session.events.SessionDeletedEvent
|
|
||||||
* @see org.springframework.session.events.SessionExpiredEvent
|
|
||||||
* @see org.springframework.test.annotation.DirtiesContext
|
|
||||||
* @see org.springframework.test.context.ContextConfiguration
|
|
||||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
|
||||||
* @see org.springframework.test.context.web.WebAppConfiguration
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
*/
|
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
|
||||||
@ContextConfiguration
|
|
||||||
@DirtiesContext
|
|
||||||
@WebAppConfiguration
|
|
||||||
public class EnableGemFireHttpSessionEventsIntegrationTests extends AbstractGemFireIntegrationTests {
|
|
||||||
|
|
||||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1;
|
|
||||||
|
|
||||||
private static final String GEMFIRE_LOG_LEVEL = "warning";
|
|
||||||
private static final String SPRING_SESSION_GEMFIRE_REGION_NAME = "TestReplicatedSessions";
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private SessionEventListener sessionEventListener;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
assertThat(GemFireUtils.isPeer(this.gemfireCache)).isTrue();
|
|
||||||
assertThat(this.gemfireSessionRepository).isNotNull();
|
|
||||||
assertThat(this.gemfireSessionRepository.getMaxInactiveIntervalInSeconds())
|
|
||||||
.isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
|
||||||
assertThat(this.sessionEventListener).isNotNull();
|
|
||||||
|
|
||||||
Region<Object, ExpiringSession> sessionRegion = this.gemfireCache.getRegion(SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
|
|
||||||
assertRegion(sessionRegion, SPRING_SESSION_GEMFIRE_REGION_NAME, DataPolicy.REPLICATE);
|
|
||||||
assertEntryIdleTimeout(sessionRegion, ExpirationAction.INVALIDATE, MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
this.sessionEventListener.getSessionEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void sessionCreatedEvent() {
|
|
||||||
final long beforeOrAtCreationTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
ExpiringSession expectedSession = save(createSession());
|
|
||||||
|
|
||||||
AbstractSessionEvent sessionEvent = this.sessionEventListener.getSessionEvent();
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
|
|
||||||
|
|
||||||
ExpiringSession createdSession = sessionEvent.getSession();
|
|
||||||
|
|
||||||
assertThat(createdSession).isEqualTo(expectedSession);
|
|
||||||
assertThat(createdSession.getId()).isNotNull();
|
|
||||||
assertThat(createdSession.getCreationTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime);
|
|
||||||
assertThat(createdSession.getLastAccessedTime()).isEqualTo(createdSession.getCreationTime());
|
|
||||||
assertThat(createdSession.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
|
||||||
assertThat(createdSession.isExpired()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getExistingNonExpiredSession() {
|
|
||||||
ExpiringSession expectedSession = save(touch(createSession()));
|
|
||||||
|
|
||||||
assertThat(expectedSession.isExpired()).isFalse();
|
|
||||||
|
|
||||||
// NOTE though unlikely, a possible race condition exists between save and get...
|
|
||||||
ExpiringSession savedSession = this.gemfireSessionRepository.getSession(expectedSession.getId());
|
|
||||||
|
|
||||||
assertThat(savedSession).isEqualTo(expectedSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getExistingExpiredSession() {
|
|
||||||
ExpiringSession expectedSession = save(expire(createSession()));
|
|
||||||
|
|
||||||
AbstractSessionEvent sessionEvent = this.sessionEventListener.getSessionEvent();
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
|
|
||||||
|
|
||||||
ExpiringSession createdSession = sessionEvent.getSession();
|
|
||||||
|
|
||||||
assertThat(createdSession).isEqualTo(expectedSession);
|
|
||||||
assertThat(createdSession.isExpired()).isTrue();
|
|
||||||
assertThat(this.gemfireSessionRepository.getSession(createdSession.getId())).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getNonExistingSession() {
|
|
||||||
assertThat(this.gemfireSessionRepository.getSession(UUID.randomUUID().toString())).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deleteExistingNonExpiredSession() {
|
|
||||||
ExpiringSession expectedSession = save(touch(createSession()));
|
|
||||||
ExpiringSession savedSession = this.gemfireSessionRepository.getSession(expectedSession.getId());
|
|
||||||
|
|
||||||
assertThat(savedSession).isEqualTo(expectedSession);
|
|
||||||
assertThat(savedSession.isExpired()).isFalse();
|
|
||||||
|
|
||||||
this.gemfireSessionRepository.delete(savedSession.getId());
|
|
||||||
|
|
||||||
AbstractSessionEvent sessionEvent = this.sessionEventListener.getSessionEvent();
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class);
|
|
||||||
assertThat(sessionEvent.getSessionId()).isEqualTo(savedSession.getId());
|
|
||||||
|
|
||||||
ExpiringSession deletedSession = sessionEvent.getSession();
|
|
||||||
|
|
||||||
assertThat(deletedSession).isEqualTo(savedSession);
|
|
||||||
assertThat(this.gemfireSessionRepository.getSession(deletedSession.getId())).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deleteExistingExpiredSession() {
|
|
||||||
ExpiringSession expectedSession = save(createSession());
|
|
||||||
|
|
||||||
AbstractSessionEvent sessionEvent = this.sessionEventListener.getSessionEvent();
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
|
|
||||||
|
|
||||||
ExpiringSession createdSession = sessionEvent.getSession();
|
|
||||||
|
|
||||||
assertThat(createdSession).isEqualTo(expectedSession);
|
|
||||||
|
|
||||||
sessionEvent = this.sessionEventListener.waitForSessionEvent(
|
|
||||||
TimeUnit.SECONDS.toMillis(this.gemfireSessionRepository.getMaxInactiveIntervalInSeconds() + 1));
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionExpiredEvent.class);
|
|
||||||
|
|
||||||
ExpiringSession expiredSession = sessionEvent.getSession();
|
|
||||||
|
|
||||||
assertThat(expiredSession).isEqualTo(createdSession);
|
|
||||||
assertThat(expiredSession.isExpired()).isTrue();
|
|
||||||
|
|
||||||
this.gemfireSessionRepository.delete(expectedSession.getId());
|
|
||||||
|
|
||||||
sessionEvent = this.sessionEventListener.getSessionEvent();
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class);
|
|
||||||
assertThat(sessionEvent.<ExpiringSession>getSession()).isNull();
|
|
||||||
assertThat(sessionEvent.getSessionId()).isEqualTo(expiredSession.getId());
|
|
||||||
assertThat(this.gemfireSessionRepository.<ExpiringSession>getSession(sessionEvent.getSessionId())).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deleteNonExistingSession() {
|
|
||||||
String expectedSessionId = UUID.randomUUID().toString();
|
|
||||||
|
|
||||||
assertThat(this.gemfireSessionRepository.<ExpiringSession>getSession(expectedSessionId)).isNull();
|
|
||||||
|
|
||||||
this.gemfireSessionRepository.delete(expectedSessionId);
|
|
||||||
|
|
||||||
AbstractSessionEvent sessionEvent = this.sessionEventListener.getSessionEvent();
|
|
||||||
|
|
||||||
assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class);
|
|
||||||
assertThat(sessionEvent.<ExpiringSession>getSession()).isNull();
|
|
||||||
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnableGemFireHttpSession(regionName = SPRING_SESSION_GEMFIRE_REGION_NAME, maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS, serverRegionShortcut = RegionShortcut.REPLICATE)
|
|
||||||
static class SpringSessionGemFireConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
Properties gemfireProperties() {
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
|
|
||||||
gemfireProperties.setProperty("name",
|
|
||||||
EnableGemFireHttpSessionEventsIntegrationTests.class.getName());
|
|
||||||
gemfireProperties.setProperty("mcast-port", "0");
|
|
||||||
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
|
|
||||||
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheFactoryBean gemfireCache() {
|
|
||||||
CacheFactoryBean gemfireCache = new CacheFactoryBean();
|
|
||||||
|
|
||||||
gemfireCache.setClose(true);
|
|
||||||
gemfireCache.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
return gemfireCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
SessionEventListener sessionEventListener() {
|
|
||||||
return new SessionEventListener();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire.config.annotation.web.http;
|
|
||||||
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Cache;
|
|
||||||
import org.apache.geode.cache.DataPolicy;
|
|
||||||
import org.apache.geode.cache.ExpirationAction;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.RegionShortcut;
|
|
||||||
import org.apache.geode.cache.query.Index;
|
|
||||||
import org.apache.geode.cache.query.QueryService;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests;
|
|
||||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
|
||||||
import org.springframework.test.annotation.DirtiesContext;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration test to test the configuration of Spring Session backed by GemFire
|
|
||||||
* using Java-based configuration meta-data.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.1.0
|
|
||||||
* @see org.junit.Test
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
|
|
||||||
* @see org.springframework.test.annotation.DirtiesContext
|
|
||||||
* @see org.springframework.test.context.ContextConfiguration
|
|
||||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
|
||||||
* @see org.springframework.test.context.web.WebAppConfiguration
|
|
||||||
* @see org.apache.geode.cache.Cache
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
*/
|
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
|
||||||
@ContextConfiguration
|
|
||||||
@DirtiesContext
|
|
||||||
@WebAppConfiguration
|
|
||||||
public class GemFireHttpSessionJavaConfigurationTests extends AbstractGemFireIntegrationTests {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private Cache gemfireCache;
|
|
||||||
|
|
||||||
protected <K, V> Region<K, V> assertCacheAndRegion(Cache gemfireCache,
|
|
||||||
String regionName, DataPolicy dataPolicy) {
|
|
||||||
|
|
||||||
assertThat(GemFireUtils.isPeer(gemfireCache)).isTrue();
|
|
||||||
|
|
||||||
Region<K, V> region = gemfireCache.getRegion(regionName);
|
|
||||||
|
|
||||||
assertRegion(region, regionName, dataPolicy);
|
|
||||||
|
|
||||||
return region;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void gemfireCacheConfigurationIsValid() {
|
|
||||||
Region<Object, ExpiringSession> example =
|
|
||||||
assertCacheAndRegion(this.gemfireCache, "JavaExample", DataPolicy.REPLICATE);
|
|
||||||
|
|
||||||
assertEntryIdleTimeout(example, ExpirationAction.INVALIDATE, 900);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void verifyGemFireExampleCacheRegionPrincipalNameIndexWasCreatedSuccessfully() {
|
|
||||||
Region<Object, ExpiringSession> example =
|
|
||||||
assertCacheAndRegion(this.gemfireCache, "JavaExample", DataPolicy.REPLICATE);
|
|
||||||
|
|
||||||
QueryService queryService = example.getRegionService().getQueryService();
|
|
||||||
|
|
||||||
assertThat(queryService).isNotNull();
|
|
||||||
|
|
||||||
Index principalNameIndex = queryService.getIndex(example, "principalNameIndex");
|
|
||||||
|
|
||||||
assertIndex(principalNameIndex, "principalName", example.getFullPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void verifyGemFireExampleCacheRegionSessionAttributesIndexWasNotCreated() {
|
|
||||||
Region<Object, ExpiringSession> example =
|
|
||||||
assertCacheAndRegion(this.gemfireCache, "JavaExample", DataPolicy.REPLICATE);
|
|
||||||
|
|
||||||
QueryService queryService = example.getRegionService().getQueryService();
|
|
||||||
|
|
||||||
assertThat(queryService).isNotNull();
|
|
||||||
|
|
||||||
Index sessionAttributesIndex = queryService.getIndex(example, "sessionAttributesIndex");
|
|
||||||
|
|
||||||
assertThat(sessionAttributesIndex).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnableGemFireHttpSession(indexableSessionAttributes = {}, maxInactiveIntervalInSeconds = 900,
|
|
||||||
regionName = "JavaExample", serverRegionShortcut = RegionShortcut.REPLICATE)
|
|
||||||
public static class GemFireConfiguration {
|
|
||||||
|
|
||||||
Properties gemfireProperties() {
|
|
||||||
Properties gemfireProperties = new Properties();
|
|
||||||
gemfireProperties.setProperty("name", applicationName());
|
|
||||||
gemfireProperties.setProperty("mcast-port", "0");
|
|
||||||
gemfireProperties.setProperty("log-level", logLevel());
|
|
||||||
return gemfireProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
String applicationName() {
|
|
||||||
return GemFireHttpSessionJavaConfigurationTests.class.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
String logLevel() {
|
|
||||||
return "warning";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CacheFactoryBean gemfireCache() {
|
|
||||||
CacheFactoryBean cacheFactory = new CacheFactoryBean();
|
|
||||||
|
|
||||||
cacheFactory.setClose(true);
|
|
||||||
cacheFactory.setProperties(gemfireProperties());
|
|
||||||
|
|
||||||
return cacheFactory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire.config.annotation.web.http;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Cache;
|
|
||||||
import org.apache.geode.cache.DataPolicy;
|
|
||||||
import org.apache.geode.cache.ExpirationAction;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.query.Index;
|
|
||||||
import org.apache.geode.cache.query.QueryService;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests;
|
|
||||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
|
||||||
import org.springframework.test.annotation.DirtiesContext;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test suite of test cases testing the configuration of Spring Session backed by GemFire
|
|
||||||
* using XML configuration meta-data.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.1.0
|
|
||||||
* @see org.junit.Test
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
|
|
||||||
* @see org.springframework.test.annotation.DirtiesContext
|
|
||||||
* @see org.springframework.test.context.ContextConfiguration
|
|
||||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
|
||||||
* @see org.springframework.test.context.web.WebAppConfiguration
|
|
||||||
* @see org.apache.geode.cache.Cache
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
*/
|
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
|
||||||
@ContextConfiguration
|
|
||||||
@DirtiesContext
|
|
||||||
@WebAppConfiguration
|
|
||||||
public class GemFireHttpSessionXmlConfigurationTests extends AbstractGemFireIntegrationTests {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private Cache gemfireCache;
|
|
||||||
|
|
||||||
protected <K, V> Region<K, V> assertCacheAndRegion(Cache gemfireCache,
|
|
||||||
String regionName, DataPolicy dataPolicy) {
|
|
||||||
|
|
||||||
assertThat(GemFireUtils.isPeer(gemfireCache)).isTrue();
|
|
||||||
|
|
||||||
Region<K, V> region = gemfireCache.getRegion(regionName);
|
|
||||||
|
|
||||||
assertRegion(region, regionName, dataPolicy);
|
|
||||||
|
|
||||||
return region;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void gemfireCacheConfigurationIsValid() {
|
|
||||||
Region<Object, ExpiringSession> example =
|
|
||||||
assertCacheAndRegion(this.gemfireCache, "XmlExample", DataPolicy.NORMAL);
|
|
||||||
|
|
||||||
assertEntryIdleTimeout(example, ExpirationAction.INVALIDATE, 3600);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void verifyGemFireExampleCacheRegionPrincipalNameIndexWasCreatedSuccessfully() {
|
|
||||||
Region<Object, ExpiringSession> example =
|
|
||||||
assertCacheAndRegion(this.gemfireCache, "XmlExample", DataPolicy.NORMAL);
|
|
||||||
|
|
||||||
QueryService queryService = example.getRegionService().getQueryService();
|
|
||||||
|
|
||||||
assertThat(queryService).isNotNull();
|
|
||||||
|
|
||||||
Index principalNameIndex = queryService.getIndex(example, "principalNameIndex");
|
|
||||||
|
|
||||||
assertIndex(principalNameIndex, "principalName", example.getFullPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void verifyGemFireExampleCacheRegionSessionAttributesIndexWasCreatedSuccessfully() {
|
|
||||||
Region<Object, ExpiringSession> example =
|
|
||||||
assertCacheAndRegion(this.gemfireCache, "XmlExample", DataPolicy.NORMAL);
|
|
||||||
|
|
||||||
QueryService queryService = example.getRegionService().getQueryService();
|
|
||||||
|
|
||||||
assertThat(queryService).isNotNull();
|
|
||||||
|
|
||||||
Index sessionAttributesIndex = queryService.getIndex(example, "sessionAttributesIndex");
|
|
||||||
|
|
||||||
assertIndex(sessionAttributesIndex, "s.attributes['one', 'two', 'three']",
|
|
||||||
String.format("%1$s s", example.getFullPath()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,956 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire;
|
|
||||||
|
|
||||||
import java.io.DataInput;
|
|
||||||
import java.io.DataOutput;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.AbstractMap;
|
|
||||||
import java.util.AbstractSet;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.geode.DataSerializable;
|
|
||||||
import org.apache.geode.DataSerializer;
|
|
||||||
import org.apache.geode.Delta;
|
|
||||||
import org.apache.geode.Instantiator;
|
|
||||||
import org.apache.geode.InvalidDeltaException;
|
|
||||||
import org.apache.geode.cache.EntryEvent;
|
|
||||||
import org.apache.geode.cache.Operation;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.util.CacheListenerAdapter;
|
|
||||||
import org.apache.geode.internal.concurrent.ConcurrentHashSet;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
|
||||||
import org.springframework.context.ApplicationEventPublisherAware;
|
|
||||||
import org.springframework.data.gemfire.GemfireAccessor;
|
|
||||||
import org.springframework.data.gemfire.GemfireOperations;
|
|
||||||
import org.springframework.expression.Expression;
|
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
|
||||||
import org.springframework.session.Session;
|
|
||||||
import org.springframework.session.SessionRepository;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
|
|
||||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
|
||||||
import org.springframework.session.events.SessionCreatedEvent;
|
|
||||||
import org.springframework.session.events.SessionDeletedEvent;
|
|
||||||
import org.springframework.session.events.SessionDestroyedEvent;
|
|
||||||
import org.springframework.session.events.SessionExpiredEvent;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.ObjectUtils;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link AbstractGemFireOperationsSessionRepository} is an abstract base class encapsulating functionality
|
|
||||||
* common to all implementations that support {@link SessionRepository} operations backed by GemFire.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.1.0
|
|
||||||
* @see org.apache.geode.DataSerializable
|
|
||||||
* @see org.apache.geode.DataSerializer
|
|
||||||
* @see org.apache.geode.Delta
|
|
||||||
* @see org.apache.geode.Instantiator
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
* @see org.apache.geode.cache.util.CacheListenerAdapter
|
|
||||||
* @see org.springframework.beans.factory.InitializingBean
|
|
||||||
* @see org.springframework.context.ApplicationEventPublisher
|
|
||||||
* @see org.springframework.context.ApplicationEventPublisherAware
|
|
||||||
* @see org.springframework.data.gemfire.GemfireOperations
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see org.springframework.session.Session
|
|
||||||
* @see org.springframework.session.SessionRepository
|
|
||||||
* @see org.springframework.session.FindByIndexNameSessionRepository
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
|
|
||||||
*/
|
|
||||||
public abstract class AbstractGemFireOperationsSessionRepository extends CacheListenerAdapter<Object, ExpiringSession>
|
|
||||||
implements ApplicationEventPublisherAware, FindByIndexNameSessionRepository<ExpiringSession>, InitializingBean {
|
|
||||||
|
|
||||||
private int maxInactiveIntervalInSeconds = GemFireHttpSessionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS;
|
|
||||||
|
|
||||||
private ApplicationEventPublisher applicationEventPublisher = new ApplicationEventPublisher() {
|
|
||||||
public void publishEvent(ApplicationEvent event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void publishEvent(Object event) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Set<Integer> cachedSessionIds = new ConcurrentHashSet<Integer>();
|
|
||||||
|
|
||||||
private final GemfireOperations template;
|
|
||||||
|
|
||||||
protected final Log logger = newLogger();
|
|
||||||
|
|
||||||
private String fullyQualifiedRegionName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance of AbstractGemFireOperationsSessionRepository with a
|
|
||||||
* required GemfireOperations instance used to perform GemFire data access operations
|
|
||||||
* and interactions supporting the SessionRepository operations.
|
|
||||||
*
|
|
||||||
* @param template the GemfireOperations instance used to interact with GemFire.
|
|
||||||
* @see org.springframework.data.gemfire.GemfireOperations
|
|
||||||
*/
|
|
||||||
public AbstractGemFireOperationsSessionRepository(GemfireOperations template) {
|
|
||||||
Assert.notNull(template, "GemfireOperations must not be null");
|
|
||||||
this.template = template;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for testing purposes only to override the Log implementation with a mock.
|
|
||||||
*
|
|
||||||
* @return an instance of Log constructed from Apache commons-logging LogFactory.
|
|
||||||
* @see org.apache.commons.logging.LogFactory#getLog(Class)
|
|
||||||
*/
|
|
||||||
Log newLogger() {
|
|
||||||
return LogFactory.getLog(getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the ApplicationEventPublisher used to publish Session events corresponding to
|
|
||||||
* GemFire cache events.
|
|
||||||
*
|
|
||||||
* @param applicationEventPublisher the Spring ApplicationEventPublisher used to
|
|
||||||
* publish Session-based events.
|
|
||||||
* @see org.springframework.context.ApplicationEventPublisher
|
|
||||||
*/
|
|
||||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
|
||||||
Assert.notNull(applicationEventPublisher, "ApplicationEventPublisher must not be null");
|
|
||||||
this.applicationEventPublisher = applicationEventPublisher;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the ApplicationEventPublisher used to publish Session events corresponding to
|
|
||||||
* GemFire cache events.
|
|
||||||
*
|
|
||||||
* @return the Spring ApplicationEventPublisher used to publish Session-based events.
|
|
||||||
* @see org.springframework.context.ApplicationEventPublisher
|
|
||||||
*/
|
|
||||||
protected ApplicationEventPublisher getApplicationEventPublisher() {
|
|
||||||
return this.applicationEventPublisher;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the fully-qualified name of the GemFire cache {@link Region} used to store and
|
|
||||||
* manage Session data.
|
|
||||||
*
|
|
||||||
* @return a String indicating the fully qualified name of the GemFire cache
|
|
||||||
* {@link Region} used to store and manage Session data.
|
|
||||||
*/
|
|
||||||
protected String getFullyQualifiedRegionName() {
|
|
||||||
return this.fullyQualifiedRegionName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the maximum interval in seconds in which a Session can remain inactive before
|
|
||||||
* it is considered expired.
|
|
||||||
*
|
|
||||||
* @param maxInactiveIntervalInSeconds an integer value specifying the maximum
|
|
||||||
* interval in seconds that a Session can remain inactive before it is considered
|
|
||||||
* expired.
|
|
||||||
*/
|
|
||||||
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
|
||||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the maximum interval in seconds in which a Session can remain inactive before
|
|
||||||
* it is considered expired.
|
|
||||||
*
|
|
||||||
* @return an integer value specifying the maximum interval in seconds that a Session
|
|
||||||
* can remain inactive before it is considered expired.
|
|
||||||
*/
|
|
||||||
public int getMaxInactiveIntervalInSeconds() {
|
|
||||||
return this.maxInactiveIntervalInSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a reference to the GemfireOperations (template) used to perform data access
|
|
||||||
* operations and other interactions on the GemFire cache {@link Region} backing this
|
|
||||||
* SessionRepository.
|
|
||||||
*
|
|
||||||
* @return a reference to the GemfireOperations used to interact with GemFire.
|
|
||||||
* @see org.springframework.data.gemfire.GemfireOperations
|
|
||||||
*/
|
|
||||||
public GemfireOperations getTemplate() {
|
|
||||||
return this.template;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback method during Spring bean initialization that will capture the fully-qualified name
|
|
||||||
* of the GemFire cache {@link Region} used to manage Session state and register this SessionRepository
|
|
||||||
* as a GemFire {@link org.apache.geode.cache.CacheListener}.
|
|
||||||
*
|
|
||||||
* Additionally, this method registers GemFire {@link Instantiator}s for the {@link GemFireSession}
|
|
||||||
* and {@link GemFireSessionAttributes} types to optimize GemFire's instantiation logic on deserialization
|
|
||||||
* using the data serialization framework when accessing the {@link Session}'s state stored in GemFire.
|
|
||||||
*
|
|
||||||
* @throws Exception if an error occurs during the initialization process.
|
|
||||||
*/
|
|
||||||
public void afterPropertiesSet() throws Exception {
|
|
||||||
GemfireOperations template = getTemplate();
|
|
||||||
|
|
||||||
Assert.isInstanceOf(GemfireAccessor.class, template);
|
|
||||||
|
|
||||||
Region<Object, ExpiringSession> region = ((GemfireAccessor) template).getRegion();
|
|
||||||
|
|
||||||
this.fullyQualifiedRegionName = region.getFullPath();
|
|
||||||
|
|
||||||
region.getAttributesMutator().addCacheListener(this);
|
|
||||||
|
|
||||||
Instantiator.register(GemFireSessionInstantiator.create());
|
|
||||||
Instantiator.register(GemFireSessionAttributesInstantiator.create());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
boolean isCreate(EntryEvent<?, ?> event) {
|
|
||||||
return (isCreate(event.getOperation()) && isNotUpdate(event) && isExpiringSessionOrNull(event.getNewValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
private boolean isCreate(Operation operation) {
|
|
||||||
return (operation.isCreate() && !Operation.LOCAL_LOAD_CREATE.equals(operation));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to determine whether the developer is storing (HTTP) Sessions with other, arbitrary application
|
|
||||||
* domain objects in the same GemFire cache {@link Region}; crazier things have happened!
|
|
||||||
*
|
|
||||||
* @param obj {@link Object} to evaluate.
|
|
||||||
* @return a boolean value indicating whether the {@link Object} from the entry event is indeed
|
|
||||||
* a {@link ExpiringSession}.
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
*/
|
|
||||||
private boolean isExpiringSessionOrNull(Object obj) {
|
|
||||||
return (obj instanceof ExpiringSession || obj == null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
private boolean isNotUpdate(EntryEvent event) {
|
|
||||||
return (isNotProxyRegion() || !this.cachedSessionIds.contains(ObjectUtils.nullSafeHashCode(event.getKey())));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
private boolean isNotProxyRegion() {
|
|
||||||
return !isProxyRegion();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
private boolean isProxyRegion() {
|
|
||||||
return GemFireUtils.isProxy(((GemfireAccessor) getTemplate()).getRegion());
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean forget(Object sessionId) {
|
|
||||||
return this.cachedSessionIds.remove(ObjectUtils.nullSafeHashCode(sessionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean remember(Object sessionId) {
|
|
||||||
return (isProxyRegion() && this.cachedSessionIds.add(ObjectUtils.nullSafeHashCode(sessionId)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
ExpiringSession toExpiringSession(Object obj) {
|
|
||||||
return (obj instanceof ExpiringSession ? (ExpiringSession) obj : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback method triggered when an entry is created in the GemFire cache {@link Region}.
|
|
||||||
*
|
|
||||||
* @param event {@link EntryEvent} containing the details of the cache {@link Region} operation.
|
|
||||||
* @see org.apache.geode.cache.EntryEvent
|
|
||||||
* @see #handleCreated(String, ExpiringSession)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void afterCreate(EntryEvent<Object, ExpiringSession> event) {
|
|
||||||
if (isCreate(event)) {
|
|
||||||
handleCreated(event.getKey().toString(), toExpiringSession(event.getNewValue()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback method triggered when an entry is destroyed in the GemFire cache
|
|
||||||
* {@link Region}.
|
|
||||||
*
|
|
||||||
* @param event an EntryEvent containing the details of the cache operation.
|
|
||||||
* @see org.apache.geode.cache.EntryEvent
|
|
||||||
* @see #handleDestroyed(String, ExpiringSession)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void afterDestroy(EntryEvent<Object, ExpiringSession> event) {
|
|
||||||
handleDestroyed(event.getKey().toString(), toExpiringSession(event.getOldValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback method triggered when an entry is invalidated in the GemFire cache
|
|
||||||
* {@link Region}.
|
|
||||||
*
|
|
||||||
* @param event an EntryEvent containing the details of the cache operation.
|
|
||||||
* @see org.apache.geode.cache.EntryEvent
|
|
||||||
* @see #handleExpired(String, ExpiringSession)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void afterInvalidate(EntryEvent<Object, ExpiringSession> event) {
|
|
||||||
handleExpired(event.getKey().toString(), toExpiringSession(event.getOldValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the given {@link Session} from GemFire.
|
|
||||||
*
|
|
||||||
* @param session {@link Session} to delete.
|
|
||||||
* @return {@literal null}.
|
|
||||||
* @see org.springframework.session.Session
|
|
||||||
* @see #delete(String)
|
|
||||||
*/
|
|
||||||
protected ExpiringSession delete(Session session) {
|
|
||||||
delete(session.getId());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Causes Session created events to be published to the Spring application context.
|
|
||||||
*
|
|
||||||
* @param sessionId a String indicating the ID of the Session.
|
|
||||||
* @param session a reference to the Session triggering the event.
|
|
||||||
* @see org.springframework.session.events.SessionCreatedEvent
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see #publishEvent(ApplicationEvent)
|
|
||||||
*/
|
|
||||||
protected void handleCreated(String sessionId, ExpiringSession session) {
|
|
||||||
remember(sessionId);
|
|
||||||
|
|
||||||
publishEvent(session != null ? new SessionCreatedEvent(this, session)
|
|
||||||
: new SessionCreatedEvent(this, sessionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Causes Session deleted events to be published to the Spring application context.
|
|
||||||
*
|
|
||||||
* @param sessionId a String indicating the ID of the Session.
|
|
||||||
* @param session a reference to the Session triggering the event.
|
|
||||||
* @see org.springframework.session.events.SessionDeletedEvent
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see #publishEvent(ApplicationEvent)
|
|
||||||
*/
|
|
||||||
protected void handleDeleted(String sessionId, ExpiringSession session) {
|
|
||||||
forget(sessionId);
|
|
||||||
|
|
||||||
publishEvent(session != null ? new SessionDeletedEvent(this, session)
|
|
||||||
: new SessionDeletedEvent(this, sessionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Causes Session destroyed events to be published to the Spring application context.
|
|
||||||
*
|
|
||||||
* @param sessionId a String indicating the ID of the Session.
|
|
||||||
* @param session a reference to the Session triggering the event.
|
|
||||||
* @see org.springframework.session.events.SessionDestroyedEvent
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see #publishEvent(ApplicationEvent)
|
|
||||||
*/
|
|
||||||
protected void handleDestroyed(String sessionId, ExpiringSession session) {
|
|
||||||
forget(sessionId);
|
|
||||||
|
|
||||||
publishEvent(session != null ? new SessionDestroyedEvent(this, session)
|
|
||||||
: new SessionDestroyedEvent(this, sessionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Causes Session expired events to be published to the Spring application context.
|
|
||||||
*
|
|
||||||
* @param sessionId a String indicating the ID of the Session.
|
|
||||||
* @param session a reference to the Session triggering the event.
|
|
||||||
* @see org.springframework.session.events.SessionExpiredEvent
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see #publishEvent(ApplicationEvent)
|
|
||||||
*/
|
|
||||||
protected void handleExpired(String sessionId, ExpiringSession session) {
|
|
||||||
forget(sessionId);
|
|
||||||
|
|
||||||
publishEvent(session != null ? new SessionExpiredEvent(this, session)
|
|
||||||
: new SessionExpiredEvent(this, sessionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Publishes the specified ApplicationEvent to the Spring application context.
|
|
||||||
*
|
|
||||||
* @param event the ApplicationEvent to publish.
|
|
||||||
* @see org.springframework.context.ApplicationEventPublisher#publishEvent(ApplicationEvent)
|
|
||||||
* @see org.springframework.context.ApplicationEvent
|
|
||||||
*/
|
|
||||||
protected void publishEvent(ApplicationEvent event) {
|
|
||||||
try {
|
|
||||||
getApplicationEventPublisher().publishEvent(event);
|
|
||||||
}
|
|
||||||
catch (Throwable t) {
|
|
||||||
this.logger.error(String.format("Error occurred publishing event [%s]", event), t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the {@link ExpiringSession#setLastAccessedTime(long)} property of the {@link ExpiringSession}.
|
|
||||||
*
|
|
||||||
* @param <T> {@link Class} sub-type of the {@link ExpiringSession}.
|
|
||||||
* @param expiringSession {@link ExpiringSession} to touch.
|
|
||||||
* @return the {@link ExpiringSession}.
|
|
||||||
* @see org.springframework.session.ExpiringSession#setLastAccessedTime(long)
|
|
||||||
*/
|
|
||||||
protected <T extends ExpiringSession> T touch(T expiringSession) {
|
|
||||||
expiringSession.setLastAccessedTime(System.currentTimeMillis());
|
|
||||||
return expiringSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GemFireSession is a GemFire representation model of a Spring {@link ExpiringSession}
|
|
||||||
* that stores and manages Session state information in GemFire. This class implements
|
|
||||||
* GemFire's {@link DataSerializable} interface to better handle replication of Session
|
|
||||||
* state information across the GemFire cluster.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public static class GemFireSession implements Comparable<ExpiringSession>,
|
|
||||||
DataSerializable, Delta, ExpiringSession {
|
|
||||||
|
|
||||||
protected static final boolean DEFAULT_ALLOW_JAVA_SERIALIZATION = true;
|
|
||||||
|
|
||||||
protected static final DateFormat TO_STRING_DATE_FORMAT = new SimpleDateFormat("YYYY-MM-dd-HH-mm-ss");
|
|
||||||
|
|
||||||
protected static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
|
||||||
|
|
||||||
private transient boolean delta = false;
|
|
||||||
|
|
||||||
private int maxInactiveIntervalInSeconds;
|
|
||||||
|
|
||||||
private long creationTime;
|
|
||||||
private long lastAccessedTime;
|
|
||||||
|
|
||||||
private transient final GemFireSessionAttributes sessionAttributes = new GemFireSessionAttributes(this);
|
|
||||||
|
|
||||||
private transient final SpelExpressionParser parser = new SpelExpressionParser();
|
|
||||||
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected GemFireSession() {
|
|
||||||
this(UUID.randomUUID().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected GemFireSession(String id) {
|
|
||||||
this.id = validateId(id);
|
|
||||||
this.creationTime = System.currentTimeMillis();
|
|
||||||
this.lastAccessedTime = this.creationTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected GemFireSession(ExpiringSession session) {
|
|
||||||
Assert.notNull(session, "The ExpiringSession to copy cannot be null");
|
|
||||||
|
|
||||||
this.id = session.getId();
|
|
||||||
this.creationTime = session.getCreationTime();
|
|
||||||
this.lastAccessedTime = session.getLastAccessedTime();
|
|
||||||
this.maxInactiveIntervalInSeconds = session.getMaxInactiveIntervalInSeconds();
|
|
||||||
this.sessionAttributes.from(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public static GemFireSession create(int maxInactiveIntervalInSeconds) {
|
|
||||||
GemFireSession session = new GemFireSession();
|
|
||||||
session.setMaxInactiveIntervalInSeconds(maxInactiveIntervalInSeconds);
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GemFireSession copy(ExpiringSession session) {
|
|
||||||
return new GemFireSession(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public static GemFireSession from(ExpiringSession session) {
|
|
||||||
return (session instanceof GemFireSession ? (GemFireSession) session : copy(session));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
private String validateId(String id) {
|
|
||||||
Assert.hasText(id, "ID must be specified");
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected boolean allowJavaSerialization() {
|
|
||||||
return DEFAULT_ALLOW_JAVA_SERIALIZATION;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public synchronized String getId() {
|
|
||||||
return this.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public synchronized long getCreationTime() {
|
|
||||||
return this.creationTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void setAttribute(String attributeName, Object attributeValue) {
|
|
||||||
this.sessionAttributes.setAttribute(attributeName, attributeValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void removeAttribute(String attributeName) {
|
|
||||||
this.sessionAttributes.removeAttribute(attributeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public <T> T getAttribute(String attributeName) {
|
|
||||||
return this.sessionAttributes.getAttribute(attributeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public Set<String> getAttributeNames() {
|
|
||||||
return this.sessionAttributes.getAttributeNames();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public GemFireSessionAttributes getAttributes() {
|
|
||||||
return this.sessionAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public synchronized boolean isExpired() {
|
|
||||||
long lastAccessedTime = getLastAccessedTime();
|
|
||||||
long maxInactiveIntervalInSeconds = getMaxInactiveIntervalInSeconds();
|
|
||||||
|
|
||||||
return (maxInactiveIntervalInSeconds >= 0
|
|
||||||
&& (idleTimeout(maxInactiveIntervalInSeconds) >= lastAccessedTime));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
private long idleTimeout(long maxInactiveIntervalInSeconds) {
|
|
||||||
return (System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(maxInactiveIntervalInSeconds));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public synchronized void setLastAccessedTime(long lastAccessedTime) {
|
|
||||||
this.delta |= (this.lastAccessedTime != lastAccessedTime);
|
|
||||||
this.lastAccessedTime = lastAccessedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public synchronized long getLastAccessedTime() {
|
|
||||||
return this.lastAccessedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public synchronized void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
|
||||||
this.delta |= (this.maxInactiveIntervalInSeconds != maxInactiveIntervalInSeconds);
|
|
||||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public synchronized int getMaxInactiveIntervalInSeconds() {
|
|
||||||
return this.maxInactiveIntervalInSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public synchronized void setPrincipalName(String principalName) {
|
|
||||||
setAttribute(PRINCIPAL_NAME_INDEX_NAME, principalName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public synchronized String getPrincipalName() {
|
|
||||||
String principalName = getAttribute(PRINCIPAL_NAME_INDEX_NAME);
|
|
||||||
|
|
||||||
if (principalName == null) {
|
|
||||||
Object authentication = getAttribute(SPRING_SECURITY_CONTEXT);
|
|
||||||
|
|
||||||
if (authentication != null) {
|
|
||||||
Expression expression = this.parser.parseExpression("authentication?.name");
|
|
||||||
principalName = expression.getValue(authentication, String.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return principalName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public synchronized void toData(DataOutput out) throws IOException {
|
|
||||||
out.writeUTF(getId());
|
|
||||||
out.writeLong(getCreationTime());
|
|
||||||
out.writeLong(getLastAccessedTime());
|
|
||||||
out.writeInt(getMaxInactiveIntervalInSeconds());
|
|
||||||
|
|
||||||
String principalName = getPrincipalName();
|
|
||||||
|
|
||||||
int length = (StringUtils.hasText(principalName) ? principalName.length() : 0);
|
|
||||||
|
|
||||||
out.writeInt(length);
|
|
||||||
|
|
||||||
if (length > 0) {
|
|
||||||
out.writeUTF(principalName);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeObject(this.sessionAttributes, out);
|
|
||||||
|
|
||||||
this.delta = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
void writeObject(Object obj, DataOutput out) throws IOException {
|
|
||||||
DataSerializer.writeObject(obj, out, allowJavaSerialization());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public synchronized void fromData(DataInput in) throws ClassNotFoundException, IOException {
|
|
||||||
this.id = in.readUTF();
|
|
||||||
this.creationTime = in.readLong();
|
|
||||||
setLastAccessedTime(in.readLong());
|
|
||||||
setMaxInactiveIntervalInSeconds(in.readInt());
|
|
||||||
|
|
||||||
int principalNameLength = in.readInt();
|
|
||||||
|
|
||||||
if (principalNameLength > 0) {
|
|
||||||
setPrincipalName(in.readUTF());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sessionAttributes.from(this.<GemFireSessionAttributes>readObject(in));
|
|
||||||
|
|
||||||
this.delta = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
<T> T readObject(DataInput in) throws ClassNotFoundException, IOException {
|
|
||||||
return DataSerializer.readObject(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public synchronized boolean hasDelta() {
|
|
||||||
return (this.delta || this.sessionAttributes.hasDelta());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public synchronized void toDelta(DataOutput out) throws IOException {
|
|
||||||
out.writeLong(getLastAccessedTime());
|
|
||||||
out.writeInt(getMaxInactiveIntervalInSeconds());
|
|
||||||
this.sessionAttributes.toDelta(out);
|
|
||||||
this.delta = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public synchronized void fromDelta(DataInput in) throws IOException {
|
|
||||||
setLastAccessedTime(in.readLong());
|
|
||||||
setMaxInactiveIntervalInSeconds(in.readInt());
|
|
||||||
this.sessionAttributes.fromDelta(in);
|
|
||||||
this.delta = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
@SuppressWarnings("all")
|
|
||||||
public int compareTo(ExpiringSession session) {
|
|
||||||
return (Long.valueOf(getCreationTime()).compareTo(session.getCreationTime()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
@Override
|
|
||||||
public boolean equals(final Object obj) {
|
|
||||||
if (obj == this) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(obj instanceof Session)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Session that = (Session) obj;
|
|
||||||
|
|
||||||
return this.getId().equals(that.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int hashValue = 17;
|
|
||||||
hashValue = 37 * hashValue + getId().hashCode();
|
|
||||||
return hashValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
@Override
|
|
||||||
public synchronized String toString() {
|
|
||||||
return String.format("{ @type = %1$s, id = %2$s, creationTime = %3$s, lastAccessedTime = %4$s"
|
|
||||||
+ ", maxInactiveIntervalInSeconds = %5$s, principalName = %6$s }",
|
|
||||||
getClass().getName(), getId(), toString(getCreationTime()), toString(getLastAccessedTime()),
|
|
||||||
getMaxInactiveIntervalInSeconds(), getPrincipalName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
private String toString(long timestamp) {
|
|
||||||
return TO_STRING_DATE_FORMAT.format(new Date(timestamp));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GemFireSessionInstantiator is a GemFire {@link Instantiator} use to instantiate instances
|
|
||||||
* of the {@link GemFireSession} object used in GemFire's data serialization framework when
|
|
||||||
* persisting Session state in GemFire.
|
|
||||||
*/
|
|
||||||
public static class GemFireSessionInstantiator extends Instantiator {
|
|
||||||
|
|
||||||
public static GemFireSessionInstantiator create() {
|
|
||||||
return new GemFireSessionInstantiator(GemFireSession.class, 800813552);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GemFireSessionInstantiator(Class<? extends DataSerializable> type, int id) {
|
|
||||||
super(type, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DataSerializable newInstance() {
|
|
||||||
return new GemFireSession();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GemFireSessionAttributes class is a container for Session attributes implementing
|
|
||||||
* both the {@link DataSerializable} and {@link Delta} GemFire interfaces for efficient
|
|
||||||
* storage and distribution (replication) in GemFire. Additionally, GemFireSessionAttributes
|
|
||||||
* extends {@link AbstractMap} providing {@link Map}-like behavior since attributes of a Session
|
|
||||||
* are effectively a name to value mapping.
|
|
||||||
*
|
|
||||||
* @see java.util.AbstractMap
|
|
||||||
* @see org.apache.geode.DataSerializable
|
|
||||||
* @see org.apache.geode.DataSerializer
|
|
||||||
* @see org.apache.geode.Delta
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public static class GemFireSessionAttributes extends AbstractMap<String, Object>
|
|
||||||
implements DataSerializable, Delta {
|
|
||||||
|
|
||||||
protected static final boolean DEFAULT_ALLOW_JAVA_SERIALIZATION = true;
|
|
||||||
|
|
||||||
private transient final Map<String, Object> sessionAttributes = new HashMap<String, Object>();
|
|
||||||
private transient final Map<String, Object> sessionAttributeDeltas = new HashMap<String, Object>();
|
|
||||||
|
|
||||||
private transient final Object lock;
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected GemFireSessionAttributes() {
|
|
||||||
this.lock = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected GemFireSessionAttributes(Object lock) {
|
|
||||||
this.lock = (lock != null ? lock : this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void setAttribute(String attributeName, Object attributeValue) {
|
|
||||||
synchronized (this.lock) {
|
|
||||||
if (attributeValue != null) {
|
|
||||||
if (!attributeValue.equals(this.sessionAttributes.put(attributeName, attributeValue))) {
|
|
||||||
this.sessionAttributeDeltas.put(attributeName, attributeValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
removeAttribute(attributeName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void removeAttribute(String attributeName) {
|
|
||||||
synchronized (this.lock) {
|
|
||||||
if (this.sessionAttributes.remove(attributeName) != null) {
|
|
||||||
this.sessionAttributeDeltas.put(attributeName, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <T> T getAttribute(String attributeName) {
|
|
||||||
synchronized (this.lock) {
|
|
||||||
return (T) this.sessionAttributes.get(attributeName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public Set<String> getAttributeNames() {
|
|
||||||
synchronized (this.lock) {
|
|
||||||
return Collections.unmodifiableSet(new HashSet<String>(this.sessionAttributes.keySet()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected boolean allowJavaSerialization() {
|
|
||||||
return DEFAULT_ALLOW_JAVA_SERIALIZATION;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc); NOTE: entrySet implementation is not Thread-safe. */
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("all")
|
|
||||||
public Set<Entry<String, Object>> entrySet() {
|
|
||||||
return new AbstractSet<Entry<String, Object>>() {
|
|
||||||
@Override
|
|
||||||
public Iterator<Entry<String, Object>> iterator() {
|
|
||||||
return Collections.unmodifiableMap(GemFireSessionAttributes.this.sessionAttributes)
|
|
||||||
.entrySet().iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return GemFireSessionAttributes.this.sessionAttributes.size();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void from(Session session) {
|
|
||||||
synchronized (this.lock) {
|
|
||||||
for (String attributeName : session.getAttributeNames()) {
|
|
||||||
setAttribute(attributeName, session.getAttribute(attributeName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void from(GemFireSessionAttributes sessionAttributes) {
|
|
||||||
synchronized (this.lock) {
|
|
||||||
for (String attributeName : sessionAttributes.getAttributeNames()) {
|
|
||||||
setAttribute(attributeName, sessionAttributes.getAttribute(attributeName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void toData(DataOutput out) throws IOException {
|
|
||||||
synchronized (this.lock) {
|
|
||||||
Set<String> attributeNames = getAttributeNames();
|
|
||||||
|
|
||||||
out.writeInt(attributeNames.size());
|
|
||||||
|
|
||||||
for (String attributeName : attributeNames) {
|
|
||||||
out.writeUTF(attributeName);
|
|
||||||
writeObject(getAttribute(attributeName), out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
void writeObject(Object obj, DataOutput out) throws IOException {
|
|
||||||
DataSerializer.writeObject(obj, out, allowJavaSerialization());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void fromData(DataInput in) throws IOException, ClassNotFoundException {
|
|
||||||
synchronized (this.lock) {
|
|
||||||
for (int count = in.readInt(); count > 0; count--) {
|
|
||||||
setAttribute(in.readUTF(), readObject(in));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sessionAttributeDeltas.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
<T> T readObject(DataInput in) throws ClassNotFoundException, IOException {
|
|
||||||
return DataSerializer.readObject(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public boolean hasDelta() {
|
|
||||||
synchronized (this.lock) {
|
|
||||||
return !this.sessionAttributeDeltas.isEmpty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void toDelta(DataOutput out) throws IOException {
|
|
||||||
synchronized (this.lock) {
|
|
||||||
out.writeInt(this.sessionAttributeDeltas.size());
|
|
||||||
|
|
||||||
for (Map.Entry<String, Object> entry : this.sessionAttributeDeltas.entrySet()) {
|
|
||||||
out.writeUTF(entry.getKey());
|
|
||||||
writeObject(entry.getValue(), out);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sessionAttributeDeltas.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void fromDelta(DataInput in) throws InvalidDeltaException, IOException {
|
|
||||||
synchronized (this.lock) {
|
|
||||||
try {
|
|
||||||
int count = in.readInt();
|
|
||||||
|
|
||||||
Map<String, Object> deltas = new HashMap<String, Object>(count);
|
|
||||||
|
|
||||||
while (count-- > 0) {
|
|
||||||
deltas.put(in.readUTF(), readObject(in));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<String, Object> entry : deltas.entrySet()) {
|
|
||||||
setAttribute(entry.getKey(), entry.getValue());
|
|
||||||
this.sessionAttributeDeltas.remove(entry.getKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ClassNotFoundException e) {
|
|
||||||
throw new InvalidDeltaException("class type in data not found", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return this.sessionAttributes.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GemFireSessionAttributesInstantiator is a GemFire {@link Instantiator} use to instantiate instances
|
|
||||||
* of the {@link GemFireSessionAttributes} object used in GemFire's data serialization framework when
|
|
||||||
* persisting Session attributes state in GemFire.
|
|
||||||
*/
|
|
||||||
public static class GemFireSessionAttributesInstantiator extends Instantiator {
|
|
||||||
|
|
||||||
public static GemFireSessionAttributesInstantiator create() {
|
|
||||||
return new GemFireSessionAttributesInstantiator(GemFireSessionAttributes.class, 800828008);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GemFireSessionAttributesInstantiator(Class<? extends DataSerializable> type, int id) {
|
|
||||||
super(type, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DataSerializable newInstance() {
|
|
||||||
return new GemFireSessionAttributes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.query.SelectResults;
|
|
||||||
|
|
||||||
import org.springframework.data.gemfire.GemfireOperations;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.SessionRepository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link GemFireOperationsSessionRepository} class is a Spring {@link SessionRepository} implementation
|
|
||||||
* that interfaces with and uses GemFire to back and store Spring Sessions.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.1.0
|
|
||||||
* @see org.springframework.data.gemfire.GemfireOperations
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository
|
|
||||||
*/
|
|
||||||
public class GemFireOperationsSessionRepository extends AbstractGemFireOperationsSessionRepository {
|
|
||||||
|
|
||||||
// GemFire OQL query used to lookup Sessions by arbitrary attributes.
|
|
||||||
protected static final String FIND_SESSIONS_BY_INDEX_NAME_VALUE_QUERY = "SELECT s FROM %1$s s WHERE s.attributes['%2$s'] = $1";
|
|
||||||
|
|
||||||
// GemFire OQL query used to look up Sessions by principal name.
|
|
||||||
protected static final String FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY = "SELECT s FROM %1$s s WHERE s.principalName = $1";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance of GemFireOperationsSessionRepository initialized with the
|
|
||||||
* required GemfireOperations object used to perform data access operations to manage
|
|
||||||
* Session state.
|
|
||||||
*
|
|
||||||
* @param template the GemfireOperations object used to access and manage Session
|
|
||||||
* state in GemFire.
|
|
||||||
* @see org.springframework.data.gemfire.GemfireOperations
|
|
||||||
*/
|
|
||||||
public GemFireOperationsSessionRepository(GemfireOperations template) {
|
|
||||||
super(template);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks up all available Sessions with the particular attribute indexed by name
|
|
||||||
* having the given value.
|
|
||||||
*
|
|
||||||
* @param indexName name of the indexed Session attribute. (e.g.
|
|
||||||
* {@link org.springframework.session.FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME}
|
|
||||||
* ).
|
|
||||||
* @param indexValue value of the indexed Session attribute to search on (e.g.
|
|
||||||
* username).
|
|
||||||
* @return a mapping of Session ID to Session instances.
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see java.util.Map
|
|
||||||
* @see #prepareQuery(String)
|
|
||||||
*/
|
|
||||||
public Map<String, ExpiringSession> findByIndexNameAndIndexValue(String indexName, String indexValue) {
|
|
||||||
SelectResults<ExpiringSession> results = getTemplate().find(prepareQuery(indexName), indexValue);
|
|
||||||
|
|
||||||
Map<String, ExpiringSession> sessions = new HashMap<String, ExpiringSession>(results.size());
|
|
||||||
|
|
||||||
for (ExpiringSession session : results.asList()) {
|
|
||||||
sessions.put(session.getId(), session);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sessions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares the appropriate GemFire OQL query based on the indexed Session attribute
|
|
||||||
* name.
|
|
||||||
*
|
|
||||||
* @param indexName a String indicating the name of the indexed Session attribute.
|
|
||||||
* @return an appropriate GemFire OQL statement for querying on a particular indexed
|
|
||||||
* Session attribute.
|
|
||||||
*/
|
|
||||||
protected String prepareQuery(String indexName) {
|
|
||||||
return (PRINCIPAL_NAME_INDEX_NAME.equals(indexName)
|
|
||||||
? String.format(FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY, getFullyQualifiedRegionName())
|
|
||||||
: String.format(FIND_SESSIONS_BY_INDEX_NAME_VALUE_QUERY, getFullyQualifiedRegionName(), indexName));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new {@link ExpiringSession} instance backed by GemFire.
|
|
||||||
*
|
|
||||||
* @return an instance of {@link ExpiringSession} backed by GemFire.
|
|
||||||
* @see AbstractGemFireOperationsSessionRepository.GemFireSession#create(int)
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see #getMaxInactiveIntervalInSeconds()
|
|
||||||
*/
|
|
||||||
public ExpiringSession createSession() {
|
|
||||||
return GemFireSession.create(getMaxInactiveIntervalInSeconds());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a copy of an existing, non-expired {@link ExpiringSession} by ID. If the
|
|
||||||
* Session is expired, then it is deleted.
|
|
||||||
*
|
|
||||||
* @param sessionId a String indicating the ID of the Session to get.
|
|
||||||
* @return an existing {@link ExpiringSession} by ID or null if not Session exists.
|
|
||||||
* @see AbstractGemFireOperationsSessionRepository.GemFireSession#from(ExpiringSession)
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see #delete(String)
|
|
||||||
*/
|
|
||||||
public ExpiringSession getSession(String sessionId) {
|
|
||||||
ExpiringSession storedSession = getTemplate().get(sessionId);
|
|
||||||
|
|
||||||
if (storedSession != null) {
|
|
||||||
storedSession = storedSession.isExpired()
|
|
||||||
? delete(storedSession)
|
|
||||||
: touch(GemFireSession.from(storedSession));
|
|
||||||
}
|
|
||||||
|
|
||||||
return storedSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the specified {@link ExpiringSession} to GemFire.
|
|
||||||
*
|
|
||||||
* @param session the {@link ExpiringSession} to save.
|
|
||||||
* @see org.springframework.data.gemfire.GemfireOperations#put(Object, Object)
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
*/
|
|
||||||
public void save(ExpiringSession session) {
|
|
||||||
getTemplate().put(session.getId(), GemFireSession.from(session));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes (removes) any existing {@link ExpiringSession} from GemFire. This operation
|
|
||||||
* also results in a SessionDeletedEvent.
|
|
||||||
*
|
|
||||||
* @param sessionId a String indicating the ID of the Session to remove from GemFire.
|
|
||||||
* @see org.springframework.data.gemfire.GemfireOperations#remove(Object)
|
|
||||||
* @see #handleDeleted(String, ExpiringSession)
|
|
||||||
*/
|
|
||||||
public void delete(String sessionId) {
|
|
||||||
handleDeleted(sessionId, getTemplate().<Object, ExpiringSession>remove(sessionId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire.config.annotation.web.http;
|
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.RegionShortcut;
|
|
||||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add this annotation to a Spring {@code @Configuration} class to expose the SessionRepositoryFilter
|
|
||||||
* as a bean named "springSessionRepositoryFilter" and backed by Pivotal GemFire or Apache Geode.
|
|
||||||
*
|
|
||||||
* In order to leverage the annotation, a single Pivotal GemFire/Apache Geode
|
|
||||||
* {@link org.apache.geode.cache.Cache}
|
|
||||||
* or {@link org.apache.geode.cache.client.ClientCache} instance must be provided.
|
|
||||||
*
|
|
||||||
* For example:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* <code>
|
|
||||||
* {@literal @Configuration}
|
|
||||||
* {@literal @EnableGemFireHttpSession}
|
|
||||||
* public class GemFirePeerCacheHttpSessionConfiguration {
|
|
||||||
*
|
|
||||||
* {@literal @Bean}
|
|
||||||
* public Properties gemfireProperties() {
|
|
||||||
* Properties gemfireProperties = new Properties();
|
|
||||||
* gemfireProperties.setProperty("name", "ExamplePeer");
|
|
||||||
* gemfireProperties.setProperty("mcast-port", "0");
|
|
||||||
* gemfireProperties.setProperty("log-level", "warning");
|
|
||||||
* return gemfireProperties;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* {@literal @Bean}
|
|
||||||
* public CacheFactoryBean gemfireCache() throws Exception {
|
|
||||||
* CacheFactoryBean cache = new CacheFactoryBean();
|
|
||||||
* cache.setProperties(gemfireProperties());
|
|
||||||
* return cache;
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </code> </pre>
|
|
||||||
*
|
|
||||||
* Alternatively, Spring Session can be configured to use Pivotal GemFire (Apache Geode) as a client
|
|
||||||
* using a dedicated GemFire Server cluster and a {@link org.apache.geode.cache.client.ClientCache}.
|
|
||||||
*
|
|
||||||
* For example:
|
|
||||||
*
|
|
||||||
* <code>
|
|
||||||
* {@literal @Configuration}
|
|
||||||
* {@literal @EnableGemFireHttpSession}
|
|
||||||
* public class GemFireClientCacheHttpSessionConfiguration {
|
|
||||||
*
|
|
||||||
* {@literal @Bean}
|
|
||||||
* public Properties gemfireProperties() {
|
|
||||||
* Properties gemfireProperties = new Properties();
|
|
||||||
* gemfireProperties.setProperty("name", "ExampleClient");
|
|
||||||
* gemfireProperties.setProperty("log-level", "warning");
|
|
||||||
* return gemfireProperties;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* {@literal @Bean}
|
|
||||||
* public ClientCacheFactoryBean gemfireCache() throws Exception {
|
|
||||||
* ClientCacheFactoryBean clientCache = new ClientCacheFactoryBean();
|
|
||||||
* clientCache.setClose(true)
|
|
||||||
* clientCache.setProperties(gemfireProperties());
|
|
||||||
* return clientCache;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* {@literal @Bean}
|
|
||||||
* public PoolFactoryBean gemfirePool() {
|
|
||||||
* PoolFactoryBean pool = new PoolFactoryBean();
|
|
||||||
* pool.addServer(new ConnectionEndpoint("serverHost", 40404);
|
|
||||||
* return pool;
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </code>
|
|
||||||
*
|
|
||||||
* More advanced configurations can extend {@link GemFireHttpSessionConfiguration} instead.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @see org.springframework.session.config.annotation.web.http.EnableSpringHttpSession
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
@Documented
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target(ElementType.TYPE)
|
|
||||||
@Configuration
|
|
||||||
@Import(GemFireHttpSessionConfiguration.class)
|
|
||||||
public @interface EnableGemFireHttpSession {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the GemFire ClientCache Region DataPolicy.
|
|
||||||
*
|
|
||||||
* @return a ClientRegionShortcut used to specify and configure the ClientCache Region
|
|
||||||
* DataPolicy.
|
|
||||||
* @see org.apache.geode.cache.client.ClientRegionShortcut
|
|
||||||
*/
|
|
||||||
ClientRegionShortcut clientRegionShortcut() default ClientRegionShortcut.PROXY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Identifies the Session attributes by name that should be indexed for query
|
|
||||||
* operations. For instance, find all Sessions in GemFire having attribute A defined
|
|
||||||
* with value X.
|
|
||||||
*
|
|
||||||
* @return an array of Strings identifying the names of Session attributes to index.
|
|
||||||
*/
|
|
||||||
String[] indexableSessionAttributes() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the maximum interval in seconds that a Session can remain inactive before
|
|
||||||
* it is considered expired. Defaults to 1800 seconds, or 30 minutes.
|
|
||||||
*
|
|
||||||
* @return an integer value defining the maximum inactive interval in seconds for
|
|
||||||
* declaring a Session expired.
|
|
||||||
*/
|
|
||||||
int maxInactiveIntervalInSeconds() default 1800;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies the name of the specific GemFire {@link org.apache.geode.cache.client.Pool} used
|
|
||||||
* by the Spring Session Data GemFire client Region ('ClusteredSpringSessions') when performing
|
|
||||||
* cache operations. This is attribute is only used in the client/server topology.
|
|
||||||
*
|
|
||||||
* @return the name of the GemFire {@link org.apache.geode.cache.client.Pool} to be used
|
|
||||||
* by the client Region used to manage (HTTP) Sessions.
|
|
||||||
* @see org.springframework.data.gemfire.config.xml.GemfireConstants#DEFAULT_GEMFIRE_POOL_NAME
|
|
||||||
*/
|
|
||||||
String poolName() default GemFireHttpSessionConfiguration.DEFAULT_GEMFIRE_POOL_NAME;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the name of the GemFire (Client)Cache Region used to store Sessions.
|
|
||||||
*
|
|
||||||
* @return a String specifying the name of the GemFire (Client)Cache Region used to
|
|
||||||
* store Sessions.
|
|
||||||
* @see org.apache.geode.cache.Region#getName()
|
|
||||||
*/
|
|
||||||
String regionName() default "ClusteredSpringSessions";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the GemFire, Peer Cache Region DataPolicy.
|
|
||||||
*
|
|
||||||
* @return a RegionShortcut used to specify and configure the Peer Cache Region
|
|
||||||
* DataPolicy.
|
|
||||||
* @see org.apache.geode.cache.RegionShortcut
|
|
||||||
*/
|
|
||||||
RegionShortcut serverRegionShortcut() default RegionShortcut.PARTITION;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,487 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire.config.annotation.web.http;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.ExpirationAction;
|
|
||||||
import org.apache.geode.cache.ExpirationAttributes;
|
|
||||||
import org.apache.geode.cache.GemFireCache;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.RegionAttributes;
|
|
||||||
import org.apache.geode.cache.RegionShortcut;
|
|
||||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
|
||||||
import org.apache.geode.cache.client.Pool;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.DependsOn;
|
|
||||||
import org.springframework.context.annotation.ImportAware;
|
|
||||||
import org.springframework.core.annotation.AnnotationAttributes;
|
|
||||||
import org.springframework.core.type.AnnotationMetadata;
|
|
||||||
import org.springframework.data.gemfire.GemfireOperations;
|
|
||||||
import org.springframework.data.gemfire.GemfireTemplate;
|
|
||||||
import org.springframework.data.gemfire.IndexFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.IndexType;
|
|
||||||
import org.springframework.data.gemfire.RegionAttributesFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.config.xml.GemfireConstants;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
|
||||||
import org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSession;
|
|
||||||
import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.support.SessionAttributesIndexFactoryBean;
|
|
||||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link GemFireHttpSessionConfiguration} class is a Spring {@link Configuration @Configuration} class
|
|
||||||
* used to configure and initialize Pivotal GemFire (or Apache Geode) as a clustered, replicated
|
|
||||||
* {@link javax.servlet.http.HttpSession} provider implementation in Spring {@link ExpiringSession}.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @see org.apache.geode.cache.GemFireCache
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
* @see org.apache.geode.cache.client.Pool
|
|
||||||
* @see org.springframework.beans.factory.BeanClassLoaderAware
|
|
||||||
* @see org.springframework.context.annotation.Bean
|
|
||||||
* @see org.springframework.context.annotation.Configuration
|
|
||||||
* @see org.springframework.context.annotation.ImportAware
|
|
||||||
* @see org.springframework.data.gemfire.GemfireOperations
|
|
||||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
|
||||||
* @see org.springframework.session.ExpiringSession
|
|
||||||
* @see org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration
|
|
||||||
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
|
||||||
implements BeanClassLoaderAware, ImportAware {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default maximum interval in seconds in which a Session can remain inactive
|
|
||||||
* before it is considered expired.
|
|
||||||
*/
|
|
||||||
public static final int DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS = (int) TimeUnit.MINUTES.toSeconds(30);
|
|
||||||
|
|
||||||
protected static final Class<Object> SPRING_SESSION_GEMFIRE_REGION_KEY_CONSTRAINT = Object.class;
|
|
||||||
protected static final Class<GemFireSession> SPRING_SESSION_GEMFIRE_REGION_VALUE_CONSTRAINT = GemFireSession.class;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default {@link ClientRegionShortcut} used to configure the GemFire ClientCache
|
|
||||||
* Region that will store Spring Sessions.
|
|
||||||
*/
|
|
||||||
public static final ClientRegionShortcut DEFAULT_CLIENT_REGION_SHORTCUT = ClientRegionShortcut.PROXY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default {@link RegionShortcut} used to configure the GemFire Cache Region that
|
|
||||||
* will store Spring Sessions.
|
|
||||||
*/
|
|
||||||
public static final RegionShortcut DEFAULT_SERVER_REGION_SHORTCUT = RegionShortcut.PARTITION;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Name of the GemFire {@link Pool} used by the client Region for managing Session state information.
|
|
||||||
*/
|
|
||||||
public static final String DEFAULT_GEMFIRE_POOL_NAME = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default name of the Gemfire (Client)Cache Region used to store Sessions.
|
|
||||||
*/
|
|
||||||
public static final String DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME = "ClusteredSpringSessions";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default names of all Session attributes that should be indexed by GemFire.
|
|
||||||
*/
|
|
||||||
public static final String[] DEFAULT_INDEXABLE_SESSION_ATTRIBUTES = {};
|
|
||||||
|
|
||||||
private int maxInactiveIntervalInSeconds = DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS;
|
|
||||||
|
|
||||||
private ClassLoader beanClassLoader;
|
|
||||||
|
|
||||||
private ClientRegionShortcut clientRegionShortcut = DEFAULT_CLIENT_REGION_SHORTCUT;
|
|
||||||
|
|
||||||
private RegionShortcut serverRegionShortcut = DEFAULT_SERVER_REGION_SHORTCUT;
|
|
||||||
|
|
||||||
private String poolName = DEFAULT_GEMFIRE_POOL_NAME;
|
|
||||||
|
|
||||||
private String springSessionGemFireRegionName = DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME;
|
|
||||||
|
|
||||||
private String[] indexableSessionAttributes = DEFAULT_INDEXABLE_SESSION_ATTRIBUTES;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a reference to the {@link ClassLoader} used to load bean definition class
|
|
||||||
* types in a Spring context.
|
|
||||||
*
|
|
||||||
* @param beanClassLoader the ClassLoader used by the Spring container to load bean
|
|
||||||
* class types.
|
|
||||||
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(ClassLoader)
|
|
||||||
* @see java.lang.ClassLoader
|
|
||||||
*/
|
|
||||||
public void setBeanClassLoader(ClassLoader beanClassLoader) {
|
|
||||||
this.beanClassLoader = beanClassLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a reference to the {@link ClassLoader} used to load bean definition class
|
|
||||||
* types in a Spring context.
|
|
||||||
*
|
|
||||||
* @return the ClassLoader used by the Spring container to load bean class types.
|
|
||||||
* @see java.lang.ClassLoader
|
|
||||||
*/
|
|
||||||
protected ClassLoader getBeanClassLoader() {
|
|
||||||
return this.beanClassLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link ClientRegionShortcut} used to configure the GemFire ClientCache
|
|
||||||
* Region that will store Spring Sessions.
|
|
||||||
*
|
|
||||||
* @param shortcut the ClientRegionShortcut used to configure the GemFire ClientCache
|
|
||||||
* Region.
|
|
||||||
* @see org.apache.geode.cache.client.ClientRegionShortcut
|
|
||||||
*/
|
|
||||||
public void setClientRegionShortcut(ClientRegionShortcut shortcut) {
|
|
||||||
this.clientRegionShortcut = shortcut;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the {@link ClientRegionShortcut} used to configure the GemFire ClientCache
|
|
||||||
* Region that will store Spring Sessions. Defaults to
|
|
||||||
* {@link ClientRegionShortcut#PROXY}.
|
|
||||||
*
|
|
||||||
* @return the ClientRegionShortcut used to configure the GemFire ClientCache Region.
|
|
||||||
* @see org.apache.geode.cache.client.ClientRegionShortcut
|
|
||||||
* @see EnableGemFireHttpSession#clientRegionShortcut()
|
|
||||||
*/
|
|
||||||
protected ClientRegionShortcut getClientRegionShortcut() {
|
|
||||||
return (this.clientRegionShortcut != null ? this.clientRegionShortcut : DEFAULT_CLIENT_REGION_SHORTCUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the names of all Session attributes that should be indexed by GemFire.
|
|
||||||
*
|
|
||||||
* @param indexableSessionAttributes an array of Strings indicating the names of all
|
|
||||||
* Session attributes for which an Index will be created by GemFire.
|
|
||||||
*/
|
|
||||||
public void setIndexableSessionAttributes(String[] indexableSessionAttributes) {
|
|
||||||
this.indexableSessionAttributes = indexableSessionAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the names of all Session attributes that should be indexed by GemFire.
|
|
||||||
*
|
|
||||||
* @return an array of Strings indicating the names of all Session attributes for
|
|
||||||
* which an Index will be created by GemFire. Defaults to an empty String array if
|
|
||||||
* unspecified.
|
|
||||||
* @see EnableGemFireHttpSession#indexableSessionAttributes()
|
|
||||||
*/
|
|
||||||
protected String[] getIndexableSessionAttributes() {
|
|
||||||
return (this.indexableSessionAttributes != null ? this.indexableSessionAttributes
|
|
||||||
: DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the maximum interval in seconds in which a Session can remain inactive before
|
|
||||||
* it is considered expired.
|
|
||||||
*
|
|
||||||
* @param maxInactiveIntervalInSeconds an integer value specifying the maximum
|
|
||||||
* interval in seconds that a Session can remain inactive before it is considered
|
|
||||||
* expired.
|
|
||||||
*/
|
|
||||||
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
|
||||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the maximum interval in seconds in which a Session can remain inactive before
|
|
||||||
* it is considered expired.
|
|
||||||
*
|
|
||||||
* @return an integer value specifying the maximum interval in seconds that a Session
|
|
||||||
* can remain inactive before it is considered expired.
|
|
||||||
* @see EnableGemFireHttpSession#maxInactiveIntervalInSeconds()
|
|
||||||
*/
|
|
||||||
protected int getMaxInactiveIntervalInSeconds() {
|
|
||||||
return this.maxInactiveIntervalInSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the name of the GemFire {@link Pool} used by the client Region for managing Sessions
|
|
||||||
* during cache operations involving the server.
|
|
||||||
*
|
|
||||||
* @param poolName the name of a GemFire {@link Pool}.
|
|
||||||
* @see Pool#getName()
|
|
||||||
*/
|
|
||||||
public void setPoolName(String poolName) {
|
|
||||||
this.poolName = poolName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the GemFire {@link Pool} used by the client Region for managing Sessions
|
|
||||||
* during cache operations involving the server.
|
|
||||||
*
|
|
||||||
* @return the name of a GemFire {@link Pool}.
|
|
||||||
* @see Pool#getName()
|
|
||||||
*/
|
|
||||||
protected String getPoolName() {
|
|
||||||
return (StringUtils.hasText(this.poolName) ? this.poolName : DEFAULT_GEMFIRE_POOL_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link RegionShortcut} used to configure the GemFire Cache Region that
|
|
||||||
* will store Spring Sessions.
|
|
||||||
*
|
|
||||||
* @param shortcut the RegionShortcut used to configure the GemFire Cache Region.
|
|
||||||
* @see org.apache.geode.cache.RegionShortcut
|
|
||||||
*/
|
|
||||||
public void setServerRegionShortcut(RegionShortcut shortcut) {
|
|
||||||
this.serverRegionShortcut = shortcut;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the {@link RegionShortcut} used to configure the GemFire Cache Region that
|
|
||||||
* will store Spring Sessions. Defaults to {@link RegionShortcut#PARTITION}.
|
|
||||||
*
|
|
||||||
* @return the RegionShortcut used to configure the GemFire Cache Region.
|
|
||||||
* @see org.apache.geode.cache.RegionShortcut
|
|
||||||
* @see EnableGemFireHttpSession#serverRegionShortcut()
|
|
||||||
*/
|
|
||||||
protected RegionShortcut getServerRegionShortcut() {
|
|
||||||
return (this.serverRegionShortcut != null ? this.serverRegionShortcut : DEFAULT_SERVER_REGION_SHORTCUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the name of the Gemfire (Client)Cache Region used to store Sessions.
|
|
||||||
*
|
|
||||||
* @param springSessionGemFireRegionName a String specifying the name of the GemFire
|
|
||||||
* (Client)Cache Region used to store the Session.
|
|
||||||
*/
|
|
||||||
public void setSpringSessionGemFireRegionName(String springSessionGemFireRegionName) {
|
|
||||||
this.springSessionGemFireRegionName = springSessionGemFireRegionName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the name of the Gemfire (Client)Cache Region used to store Sessions. Defaults
|
|
||||||
* to 'ClusteredSpringSessions'.
|
|
||||||
*
|
|
||||||
* @return a String specifying the name of the GemFire (Client)Cache Region used to
|
|
||||||
* store the Session.
|
|
||||||
* @see org.apache.geode.cache.Region#getName()
|
|
||||||
* @see EnableGemFireHttpSession#regionName()
|
|
||||||
*/
|
|
||||||
protected String getSpringSessionGemFireRegionName() {
|
|
||||||
return (StringUtils.hasText(this.springSessionGemFireRegionName) ? this.springSessionGemFireRegionName
|
|
||||||
: DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback with the {@link AnnotationMetadata} of the class containing @Import
|
|
||||||
* annotation that imported this @Configuration class.
|
|
||||||
*
|
|
||||||
* @param importMetadata the AnnotationMetadata of the class importing
|
|
||||||
* this @Configuration class.
|
|
||||||
*/
|
|
||||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
|
||||||
AnnotationAttributes enableGemFireHttpSessionAnnotationAttributes =
|
|
||||||
AnnotationAttributes.fromMap(importMetadata.getAnnotationAttributes(
|
|
||||||
EnableGemFireHttpSession.class.getName()));
|
|
||||||
|
|
||||||
setClientRegionShortcut(ClientRegionShortcut.class.cast(enableGemFireHttpSessionAnnotationAttributes
|
|
||||||
.getEnum("clientRegionShortcut")));
|
|
||||||
|
|
||||||
setIndexableSessionAttributes(enableGemFireHttpSessionAnnotationAttributes
|
|
||||||
.getStringArray("indexableSessionAttributes"));
|
|
||||||
|
|
||||||
setMaxInactiveIntervalInSeconds(enableGemFireHttpSessionAnnotationAttributes
|
|
||||||
.getNumber("maxInactiveIntervalInSeconds").intValue());
|
|
||||||
|
|
||||||
setPoolName(enableGemFireHttpSessionAnnotationAttributes.getString("poolName"));
|
|
||||||
|
|
||||||
setServerRegionShortcut(RegionShortcut.class.cast(enableGemFireHttpSessionAnnotationAttributes
|
|
||||||
.getEnum("serverRegionShortcut")));
|
|
||||||
|
|
||||||
setSpringSessionGemFireRegionName(enableGemFireHttpSessionAnnotationAttributes
|
|
||||||
.getString("regionName"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the Spring SessionRepository bean used to interact with GemFire as a Spring
|
|
||||||
* Session provider.
|
|
||||||
*
|
|
||||||
* @param gemfireOperations an instance of {@link GemfireOperations} used to manage
|
|
||||||
* Spring Sessions in GemFire.
|
|
||||||
* @return a GemFireOperationsSessionRepository for managing (clustering/replicating)
|
|
||||||
* Sessions using GemFire.
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public GemFireOperationsSessionRepository sessionRepository(
|
|
||||||
@Qualifier("sessionRegionTemplate") GemfireOperations gemfireOperations) {
|
|
||||||
|
|
||||||
GemFireOperationsSessionRepository sessionRepository =
|
|
||||||
new GemFireOperationsSessionRepository(gemfireOperations);
|
|
||||||
|
|
||||||
sessionRepository.setMaxInactiveIntervalInSeconds(getMaxInactiveIntervalInSeconds());
|
|
||||||
|
|
||||||
return sessionRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a Spring GemfireTemplate bean used to interact with GemFire's (Client)Cache
|
|
||||||
* {@link Region} storing Sessions.
|
|
||||||
*
|
|
||||||
* @param gemFireCache reference to the single GemFire cache instance used by the
|
|
||||||
* {@link GemfireTemplate} to perform GemFire cache data access operations.
|
|
||||||
* @return a {@link GemfireTemplate} used to interact with GemFire's (Client)Cache
|
|
||||||
* {@link Region} storing Sessions.
|
|
||||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@DependsOn(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME)
|
|
||||||
public GemfireTemplate sessionRegionTemplate(GemFireCache gemFireCache) {
|
|
||||||
return new GemfireTemplate(gemFireCache.getRegion(getSpringSessionGemFireRegionName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a Spring GemFire {@link org.apache.geode.cache.Cache} {@link Region}
|
|
||||||
* bean used to store and manage Sessions using either a client-server or peer-to-peer
|
|
||||||
* (p2p) topology.
|
|
||||||
*
|
|
||||||
* @param gemfireCache a reference to the GemFire
|
|
||||||
* {@link org.apache.geode.cache.Cache}.
|
|
||||||
* @param sessionRegionAttributes the GemFire {@link RegionAttributes} used to
|
|
||||||
* configure the {@link Region}.
|
|
||||||
* @return a {@link GemFireCacheTypeAwareRegionFactoryBean} used to configure and
|
|
||||||
* initialize a GemFire Cache {@link Region} for storing and managing Sessions.
|
|
||||||
* @see #getClientRegionShortcut()
|
|
||||||
* @see #getSpringSessionGemFireRegionName()
|
|
||||||
* @see #getServerRegionShortcut()
|
|
||||||
*/
|
|
||||||
@Bean(name = DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME)
|
|
||||||
public GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession> sessionRegion(GemFireCache gemfireCache,
|
|
||||||
@Qualifier("sessionRegionAttributes") RegionAttributes<Object, ExpiringSession> sessionRegionAttributes) {
|
|
||||||
|
|
||||||
GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession> sessionRegion =
|
|
||||||
new GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession>();
|
|
||||||
|
|
||||||
sessionRegion.setClientRegionShortcut(getClientRegionShortcut());
|
|
||||||
sessionRegion.setGemfireCache(gemfireCache);
|
|
||||||
sessionRegion.setPoolName(getPoolName());
|
|
||||||
sessionRegion.setRegionAttributes(sessionRegionAttributes);
|
|
||||||
sessionRegion.setRegionName(getSpringSessionGemFireRegionName());
|
|
||||||
sessionRegion.setServerRegionShortcut(getServerRegionShortcut());
|
|
||||||
|
|
||||||
return sessionRegion;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a Spring GemFire {@link RegionAttributes} bean used to configure and
|
|
||||||
* initialize the GemFire cache {@link Region} storing Sessions. Expiration is also
|
|
||||||
* configured for the {@link Region} on the basis that the GemFire cache
|
|
||||||
* {@link Region} is a not a proxy, on either the client or server.
|
|
||||||
*
|
|
||||||
* @param gemfireCache a reference to the GemFire cache.
|
|
||||||
* @return an instance of {@link RegionAttributes} used to configure and initialize
|
|
||||||
* the GemFire cache {@link Region} for storing and managing Sessions.
|
|
||||||
* @see org.springframework.data.gemfire.RegionAttributesFactoryBean
|
|
||||||
* @see org.apache.geode.cache.GemFireCache
|
|
||||||
* @see org.apache.geode.cache.PartitionAttributes
|
|
||||||
* @see #isExpirationAllowed(GemFireCache)
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@SuppressWarnings({ "unchecked", "deprecation" })
|
|
||||||
public RegionAttributesFactoryBean sessionRegionAttributes(GemFireCache gemfireCache) {
|
|
||||||
RegionAttributesFactoryBean regionAttributes = new RegionAttributesFactoryBean();
|
|
||||||
|
|
||||||
regionAttributes.setKeyConstraint(SPRING_SESSION_GEMFIRE_REGION_KEY_CONSTRAINT);
|
|
||||||
regionAttributes.setValueConstraint(SPRING_SESSION_GEMFIRE_REGION_VALUE_CONSTRAINT);
|
|
||||||
|
|
||||||
if (isExpirationAllowed(gemfireCache)) {
|
|
||||||
regionAttributes.setStatisticsEnabled(true);
|
|
||||||
regionAttributes.setEntryIdleTimeout(
|
|
||||||
new ExpirationAttributes(Math.max(getMaxInactiveIntervalInSeconds(), 0), ExpirationAction.INVALIDATE));
|
|
||||||
}
|
|
||||||
|
|
||||||
return regionAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether expiration configuration is allowed to be set on the GemFire
|
|
||||||
* cache {@link Region} used to store and manage Sessions.
|
|
||||||
*
|
|
||||||
* @param gemfireCache a reference to the GemFire cache.
|
|
||||||
* @return a boolean indicating if a {@link Region} can be configured for Region entry
|
|
||||||
* idle-timeout expiration.
|
|
||||||
* @see GemFireUtils#isClient(GemFireCache)
|
|
||||||
* @see GemFireUtils#isProxy(ClientRegionShortcut)
|
|
||||||
* @see GemFireUtils#isProxy(RegionShortcut)
|
|
||||||
*/
|
|
||||||
boolean isExpirationAllowed(GemFireCache gemfireCache) {
|
|
||||||
return !(GemFireUtils.isClient(gemfireCache) ? GemFireUtils.isProxy(getClientRegionShortcut())
|
|
||||||
: GemFireUtils.isProxy(getServerRegionShortcut()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a GemFire Index bean on the GemFire cache {@link Region} storing and managing Sessions,
|
|
||||||
* specifically on the 'principalName' property for quick lookup of Sessions by 'principalName'.
|
|
||||||
*
|
|
||||||
* @param gemfireCache a reference to the GemFire cache.
|
|
||||||
* @return a {@link IndexFactoryBean} to create an GemFire Index on the 'principalName' property
|
|
||||||
* for Sessions stored in the GemFire cache {@link Region}.
|
|
||||||
* @see org.springframework.data.gemfire.IndexFactoryBean
|
|
||||||
* @see org.apache.geode.cache.GemFireCache
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@DependsOn(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME)
|
|
||||||
public IndexFactoryBean principalNameIndex(GemFireCache gemfireCache) {
|
|
||||||
IndexFactoryBean index = new IndexFactoryBean();
|
|
||||||
|
|
||||||
index.setCache(gemfireCache);
|
|
||||||
index.setName("principalNameIndex");
|
|
||||||
index.setExpression("principalName");
|
|
||||||
index.setFrom(GemFireUtils.toRegionPath(getSpringSessionGemFireRegionName()));
|
|
||||||
index.setOverride(true);
|
|
||||||
index.setType(IndexType.HASH);
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a GemFire Index bean on the GemFire cache {@link Region} storing and managing Sessions,
|
|
||||||
* specifically on all Session attributes for quick lookup and queries on Session attribute names
|
|
||||||
* with a given value.
|
|
||||||
*
|
|
||||||
* @param gemfireCache a reference to the GemFire cache.
|
|
||||||
* @return a {@link IndexFactoryBean} to create an GemFire Index on attributes of Sessions
|
|
||||||
* stored in the GemFire cache {@link Region}.
|
|
||||||
* @see org.springframework.data.gemfire.IndexFactoryBean
|
|
||||||
* @see org.apache.geode.cache.GemFireCache
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@DependsOn(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME)
|
|
||||||
public SessionAttributesIndexFactoryBean sessionAttributesIndex(GemFireCache gemfireCache) {
|
|
||||||
SessionAttributesIndexFactoryBean sessionAttributesIndex = new SessionAttributesIndexFactoryBean();
|
|
||||||
|
|
||||||
sessionAttributesIndex.setGemFireCache(gemfireCache);
|
|
||||||
sessionAttributesIndex.setIndexableSessionAttributes(getIndexableSessionAttributes());
|
|
||||||
sessionAttributesIndex.setRegionName(getSpringSessionGemFireRegionName());
|
|
||||||
|
|
||||||
return sessionAttributesIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,384 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire.config.annotation.web.http.support;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.GemFireCache;
|
|
||||||
import org.apache.geode.cache.InterestResultPolicy;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.RegionAttributes;
|
|
||||||
import org.apache.geode.cache.RegionShortcut;
|
|
||||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
|
||||||
import org.apache.geode.cache.client.Pool;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
|
||||||
import org.springframework.beans.factory.BeanFactoryAware;
|
|
||||||
import org.springframework.beans.factory.FactoryBean;
|
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
|
||||||
import org.springframework.data.gemfire.GenericRegionFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
|
|
||||||
import org.springframework.data.gemfire.client.Interest;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
|
|
||||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link GemFireCacheTypeAwareRegionFactoryBean} class is a Spring {@link FactoryBean}
|
|
||||||
* used to construct, configure and initialize the GemFire cache {@link Region} used to
|
|
||||||
* store and manage Session state.
|
|
||||||
*
|
|
||||||
* @param <K> the type of keys
|
|
||||||
* @param <V> the type of values
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.1.0
|
|
||||||
* @see org.springframework.data.gemfire.GenericRegionFactoryBean
|
|
||||||
* @see org.springframework.beans.factory.BeanFactoryAware
|
|
||||||
* @see org.springframework.beans.factory.FactoryBean
|
|
||||||
* @see org.springframework.beans.factory.InitializingBean
|
|
||||||
*/
|
|
||||||
public class GemFireCacheTypeAwareRegionFactoryBean<K, V>
|
|
||||||
implements BeanFactoryAware, FactoryBean<Region<K, V>>, InitializingBean {
|
|
||||||
|
|
||||||
protected static final ClientRegionShortcut DEFAULT_CLIENT_REGION_SHORTCUT =
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT;
|
|
||||||
|
|
||||||
protected static final RegionShortcut DEFAULT_SERVER_REGION_SHORTCUT =
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT;
|
|
||||||
|
|
||||||
protected static final String DEFAULT_GEMFIRE_POOL_NAME =
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_GEMFIRE_POOL_NAME;
|
|
||||||
|
|
||||||
protected static final String DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME =
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME;
|
|
||||||
|
|
||||||
private BeanFactory beanFactory;
|
|
||||||
|
|
||||||
private ClientRegionShortcut clientRegionShortcut;
|
|
||||||
|
|
||||||
private GemFireCache gemfireCache;
|
|
||||||
|
|
||||||
private Region<K, V> region;
|
|
||||||
|
|
||||||
private RegionAttributes<K, V> regionAttributes;
|
|
||||||
|
|
||||||
private RegionShortcut serverRegionShortcut;
|
|
||||||
|
|
||||||
private String poolName;
|
|
||||||
private String regionName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Post-construction initialization callback to create, configure and initialize the
|
|
||||||
* GemFire cache {@link Region} used to store, replicate (distribute) and manage
|
|
||||||
* Session state. This method intelligently handles both client-server and
|
|
||||||
* peer-to-peer (p2p) GemFire supported distributed system topologies.
|
|
||||||
*
|
|
||||||
* @throws Exception if the initialization of the GemFire cache {@link Region} fails.
|
|
||||||
* @see org.springframework.session.data.gemfire.support.GemFireUtils#isClient(GemFireCache)
|
|
||||||
* @see #getGemfireCache()
|
|
||||||
* @see #newClientRegion(GemFireCache)
|
|
||||||
* @see #newServerRegion(GemFireCache)
|
|
||||||
*/
|
|
||||||
public void afterPropertiesSet() throws Exception {
|
|
||||||
GemFireCache gemfireCache = getGemfireCache();
|
|
||||||
|
|
||||||
this.region = (GemFireUtils.isClient(gemfireCache) ? newClientRegion(gemfireCache)
|
|
||||||
: newServerRegion(gemfireCache));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a GemFire cache {@link Region} using a peer-to-peer (p2p) GemFire
|
|
||||||
* topology to store and manage Session state in a GemFire server cluster accessible
|
|
||||||
* from a GemFire cache client.
|
|
||||||
*
|
|
||||||
* @param gemfireCache a reference to the GemFire
|
|
||||||
* {@link org.apache.geode.cache.Cache}.
|
|
||||||
* @return a peer-to-peer-based GemFire cache {@link Region} to store and manage
|
|
||||||
* Session state.
|
|
||||||
* @throws Exception if the instantiation, configuration and initialization of the
|
|
||||||
* GemFire cache {@link Region} fails.
|
|
||||||
* @see org.springframework.data.gemfire.GenericRegionFactoryBean
|
|
||||||
* @see org.apache.geode.cache.GemFireCache
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
* @see #getRegionAttributes()
|
|
||||||
* @see #getRegionName()
|
|
||||||
* @see #getServerRegionShortcut()
|
|
||||||
*/
|
|
||||||
protected Region<K, V> newServerRegion(GemFireCache gemfireCache) throws Exception {
|
|
||||||
GenericRegionFactoryBean<K, V> serverRegion = new GenericRegionFactoryBean<K, V>();
|
|
||||||
|
|
||||||
serverRegion.setAttributes(getRegionAttributes());
|
|
||||||
serverRegion.setCache(gemfireCache);
|
|
||||||
serverRegion.setRegionName(getRegionName());
|
|
||||||
serverRegion.setShortcut(getServerRegionShortcut());
|
|
||||||
serverRegion.afterPropertiesSet();
|
|
||||||
|
|
||||||
return serverRegion.getObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a GemFire cache {@link Region} using the client-server GemFire topology
|
|
||||||
* to store and manage Session state in a GemFire server cluster accessible from a
|
|
||||||
* GemFire cache client.
|
|
||||||
*
|
|
||||||
* @param gemfireCache a reference to the GemFire
|
|
||||||
* {@link org.apache.geode.cache.Cache}.
|
|
||||||
* @return a client-server-based GemFire cache {@link Region} to store and manage
|
|
||||||
* Session state.
|
|
||||||
* @throws Exception if the instantiation, configuration and initialization of the
|
|
||||||
* GemFire cache {@link Region} fails.
|
|
||||||
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
|
|
||||||
* @see org.apache.geode.cache.GemFireCache
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
* @see #getClientRegionShortcut()
|
|
||||||
* @see #getRegionAttributes()
|
|
||||||
* @see #getRegionName()
|
|
||||||
* @see #registerInterests(boolean)
|
|
||||||
*/
|
|
||||||
protected Region<K, V> newClientRegion(GemFireCache gemfireCache) throws Exception {
|
|
||||||
ClientRegionFactoryBean<K, V> clientRegion = new ClientRegionFactoryBean<K, V>();
|
|
||||||
|
|
||||||
ClientRegionShortcut shortcut = getClientRegionShortcut();
|
|
||||||
|
|
||||||
clientRegion.setAttributes(getRegionAttributes());
|
|
||||||
clientRegion.setBeanFactory(getBeanFactory());
|
|
||||||
clientRegion.setCache(gemfireCache);
|
|
||||||
clientRegion.setInterests(registerInterests(!GemFireUtils.isLocal(shortcut)));
|
|
||||||
clientRegion.setPoolName(getPoolName());
|
|
||||||
clientRegion.setRegionName(getRegionName());
|
|
||||||
clientRegion.setShortcut(shortcut);
|
|
||||||
clientRegion.afterPropertiesSet();
|
|
||||||
|
|
||||||
return clientRegion.getObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decides whether interests will be registered for all keys. Interests is only registered on
|
|
||||||
* a client and typically only when the client is a (CACHING) PROXY to the server (i.e. non-LOCAL only).
|
|
||||||
*
|
|
||||||
* @param register a boolean value indicating whether interests should be registered.
|
|
||||||
* @return an array of Interests KEY/VALUE registrations.
|
|
||||||
* @see org.springframework.data.gemfire.client.Interest
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected Interest<K>[] registerInterests(boolean register) {
|
|
||||||
return (!register ? new Interest[0] : new Interest[] {
|
|
||||||
new Interest<String>("ALL_KEYS", InterestResultPolicy.KEYS)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a reference to the constructed GemFire cache {@link Region} used to store
|
|
||||||
* and manage Session state.
|
|
||||||
*
|
|
||||||
* @return the {@link Region} used to store and manage Session state.
|
|
||||||
* @throws Exception if the {@link Region} reference cannot be obtained.
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
*/
|
|
||||||
public Region<K, V> getObject() throws Exception {
|
|
||||||
return this.region;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the specific type of GemFire cache {@link Region} this factory creates when
|
|
||||||
* initialized or Region.class when uninitialized.
|
|
||||||
*
|
|
||||||
* @return the GemFire cache {@link Region} class type constructed by this factory.
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
* @see java.lang.Class
|
|
||||||
*/
|
|
||||||
public Class<?> getObjectType() {
|
|
||||||
return (this.region != null ? this.region.getClass() : Region.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true indicating the GemFire cache {@link Region} created by this factory is
|
|
||||||
* the sole instance.
|
|
||||||
*
|
|
||||||
* @return true to indicate the GemFire cache {@link Region} storing and managing
|
|
||||||
* Sessions is a Singleton.
|
|
||||||
*/
|
|
||||||
public boolean isSingleton() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a reference to the Spring {@link BeanFactory} responsible for
|
|
||||||
* creating GemFire components.
|
|
||||||
*
|
|
||||||
* @param beanFactory reference to the Spring {@link BeanFactory}
|
|
||||||
* @throws IllegalArgumentException if the {@link BeanFactory} reference is null.
|
|
||||||
* @see org.springframework.beans.factory.BeanFactory
|
|
||||||
*/
|
|
||||||
public void setBeanFactory(BeanFactory beanFactory) {
|
|
||||||
Assert.notNull(beanFactory, "BeanFactory must not be null");
|
|
||||||
this.beanFactory = beanFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a reference to the Spring {@link BeanFactory} responsible for
|
|
||||||
* creating GemFire components.
|
|
||||||
*
|
|
||||||
* @return a reference to the Spring {@link BeanFactory}
|
|
||||||
* @throws IllegalStateException if the {@link BeanFactory} reference
|
|
||||||
* is null.
|
|
||||||
* @see org.springframework.beans.factory.BeanFactory
|
|
||||||
*/
|
|
||||||
protected BeanFactory getBeanFactory() {
|
|
||||||
Assert.state(this.beanFactory != null, "A reference to the BeanFactory was not properly configured");
|
|
||||||
return this.beanFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link Region} data policy used by the GemFire cache client to manage
|
|
||||||
* Session state.
|
|
||||||
*
|
|
||||||
* @param clientRegionShortcut a {@link ClientRegionShortcut} to specify the client
|
|
||||||
* {@link Region} data management policy.
|
|
||||||
* @see org.apache.geode.cache.client.ClientRegionShortcut
|
|
||||||
*/
|
|
||||||
public void setClientRegionShortcut(ClientRegionShortcut clientRegionShortcut) {
|
|
||||||
this.clientRegionShortcut = clientRegionShortcut;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link Region} data policy used by the GemFire cache client to manage
|
|
||||||
* Session state. Defaults to {@link ClientRegionShortcut#PROXY}.
|
|
||||||
*
|
|
||||||
* @return a {@link ClientRegionShortcut} specifying the client {@link Region} data
|
|
||||||
* management policy.
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration#DEFAULT_CLIENT_REGION_SHORTCUT
|
|
||||||
* @see org.apache.geode.cache.client.ClientRegionShortcut
|
|
||||||
*/
|
|
||||||
protected ClientRegionShortcut getClientRegionShortcut() {
|
|
||||||
return (this.clientRegionShortcut != null ? this.clientRegionShortcut : DEFAULT_CLIENT_REGION_SHORTCUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a reference to the GemFire cache used to construct the appropriate
|
|
||||||
* {@link Region}.
|
|
||||||
*
|
|
||||||
* @param gemfireCache a reference to the GemFire cache.
|
|
||||||
* @throws IllegalArgumentException if the {@link GemFireCache} reference is null.
|
|
||||||
*/
|
|
||||||
public void setGemfireCache(GemFireCache gemfireCache) {
|
|
||||||
Assert.notNull(gemfireCache, "GemFireCache must not be null");
|
|
||||||
this.gemfireCache = gemfireCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a reference to the GemFire cache used to construct the appropriate
|
|
||||||
* {@link Region}.
|
|
||||||
*
|
|
||||||
* @return a reference to the GemFire cache.
|
|
||||||
* @throws IllegalStateException if the {@link GemFireCache} reference is null.
|
|
||||||
*/
|
|
||||||
protected GemFireCache getGemfireCache() {
|
|
||||||
Assert.state(this.gemfireCache != null, "A reference to the GemFireCache was not properly configured");
|
|
||||||
return this.gemfireCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the name of the GemFire {@link Pool} used by the client Region for managing Sessions
|
|
||||||
* during cache operations involving the server.
|
|
||||||
*
|
|
||||||
* @param poolName the name of a GemFire {@link Pool}.
|
|
||||||
* @see Pool#getName()
|
|
||||||
*/
|
|
||||||
public void setPoolName(final String poolName) {
|
|
||||||
this.poolName = poolName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the GemFire {@link Pool} used by the client Region for managing Sessions
|
|
||||||
* during cache operations involving the server.
|
|
||||||
*
|
|
||||||
* @return the name of a GemFire {@link Pool}.
|
|
||||||
* @see Pool#getName()
|
|
||||||
*/
|
|
||||||
protected String getPoolName() {
|
|
||||||
return (StringUtils.hasText(this.poolName) ? this.poolName : DEFAULT_GEMFIRE_POOL_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the GemFire {@link RegionAttributes} used to configure the GemFire cache
|
|
||||||
* {@link Region} used to store and manage Session state.
|
|
||||||
*
|
|
||||||
* @param regionAttributes the GemFire {@link RegionAttributes} used to configure the
|
|
||||||
* GemFire cache {@link Region}.
|
|
||||||
* @see org.apache.geode.cache.RegionAttributes
|
|
||||||
*/
|
|
||||||
public void setRegionAttributes(RegionAttributes<K, V> regionAttributes) {
|
|
||||||
this.regionAttributes = regionAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the GemFire {@link RegionAttributes} used to configure the GemFire cache
|
|
||||||
* {@link Region} used to store and manage Session state.
|
|
||||||
*
|
|
||||||
* @return the GemFire {@link RegionAttributes} used to configure the GemFire cache
|
|
||||||
* {@link Region}.
|
|
||||||
* @see org.apache.geode.cache.RegionAttributes
|
|
||||||
*/
|
|
||||||
protected RegionAttributes<K, V> getRegionAttributes() {
|
|
||||||
return this.regionAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the name of the GemFire cache {@link Region} use to store and manage Session
|
|
||||||
* state.
|
|
||||||
*
|
|
||||||
* @param regionName a String specifying the name of the GemFire cache {@link Region}.
|
|
||||||
*/
|
|
||||||
public void setRegionName(final String regionName) {
|
|
||||||
this.regionName = regionName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the configured name of the GemFire cache {@link Region} use to store and
|
|
||||||
* manage Session state. Defaults to "ClusteredSpringSessions"
|
|
||||||
*
|
|
||||||
* @return a String specifying the name of the GemFire cache {@link Region}.
|
|
||||||
* @see org.apache.geode.cache.Region#getName()
|
|
||||||
*/
|
|
||||||
protected String getRegionName() {
|
|
||||||
return (StringUtils.hasText(this.regionName) ? this.regionName : DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link Region} data policy used by the GemFire peer cache to manage
|
|
||||||
* Session state.
|
|
||||||
*
|
|
||||||
* @param serverRegionShortcut a {@link RegionShortcut} to specify the peer
|
|
||||||
* {@link Region} data management policy.
|
|
||||||
* @see org.apache.geode.cache.RegionShortcut
|
|
||||||
*/
|
|
||||||
public void setServerRegionShortcut(RegionShortcut serverRegionShortcut) {
|
|
||||||
this.serverRegionShortcut = serverRegionShortcut;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link Region} data policy used by the GemFire peer cache to manage
|
|
||||||
* Session state. Defaults to {@link RegionShortcut#PARTITION}.
|
|
||||||
*
|
|
||||||
* @return a {@link RegionShortcut} specifying the peer {@link Region} data management
|
|
||||||
* policy.
|
|
||||||
* @see org.apache.geode.cache.RegionShortcut
|
|
||||||
*/
|
|
||||||
protected RegionShortcut getServerRegionShortcut() {
|
|
||||||
return (this.serverRegionShortcut != null ? this.serverRegionShortcut : DEFAULT_SERVER_REGION_SHORTCUT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire.config.annotation.web.http.support;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.GemFireCache;
|
|
||||||
import org.apache.geode.cache.query.Index;
|
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
|
||||||
import org.springframework.beans.factory.BeanFactoryAware;
|
|
||||||
import org.springframework.beans.factory.BeanNameAware;
|
|
||||||
import org.springframework.beans.factory.FactoryBean;
|
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
|
||||||
import org.springframework.data.gemfire.IndexFactoryBean;
|
|
||||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
|
||||||
import org.springframework.util.ObjectUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The SessionAttributesIndexFactoryBean class is a Spring {@link FactoryBean} that creates a GemFire {@link Index}
|
|
||||||
* on the session attributes of the {@link HttpSession} object.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.3.0
|
|
||||||
* @see org.springframework.beans.factory.BeanFactoryAware
|
|
||||||
* @see org.springframework.beans.factory.BeanNameAware
|
|
||||||
* @see org.springframework.beans.factory.FactoryBean
|
|
||||||
* @see org.springframework.beans.factory.InitializingBean
|
|
||||||
* @see org.apache.geode.cache.query.Index
|
|
||||||
*/
|
|
||||||
public class SessionAttributesIndexFactoryBean implements FactoryBean<Index>,
|
|
||||||
InitializingBean, BeanFactoryAware, BeanNameAware {
|
|
||||||
|
|
||||||
protected static final String[] DEFAULT_INDEXABLE_SESSION_ATTRIBUTES = {};
|
|
||||||
|
|
||||||
private BeanFactory beanFactory;
|
|
||||||
|
|
||||||
private GemFireCache gemfireCache;
|
|
||||||
|
|
||||||
private Index sessionAttributesIndex;
|
|
||||||
|
|
||||||
private String beanName;
|
|
||||||
private String regionName;
|
|
||||||
|
|
||||||
private String[] indexableSessionAttributes;
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void afterPropertiesSet() throws Exception {
|
|
||||||
if (isIndexableSessionAttributesConfigured()) {
|
|
||||||
this.sessionAttributesIndex = newIndex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether any indexable Session attributes were configured for this {@link FactoryBean}.
|
|
||||||
*
|
|
||||||
* @return a boolean value indicating whether any indexable Session attributes were configured
|
|
||||||
* for this {@link FactoryBean}
|
|
||||||
* @see #setIndexableSessionAttributes(String[])
|
|
||||||
*/
|
|
||||||
protected boolean isIndexableSessionAttributesConfigured() {
|
|
||||||
return !ObjectUtils.isEmpty(this.indexableSessionAttributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a GemFire {@link Index} over the attributes of the {@link HttpSession}.
|
|
||||||
*
|
|
||||||
* @return a GemFire {@link Index} over the {@link HttpSession} attributes.
|
|
||||||
* @throws Exception if an error occurs while initializing the GemFire {@link Index}.
|
|
||||||
* @see org.springframework.data.gemfire.IndexFactoryBean
|
|
||||||
*/
|
|
||||||
protected Index newIndex() throws Exception {
|
|
||||||
IndexFactoryBean indexFactory = new IndexFactoryBean();
|
|
||||||
|
|
||||||
indexFactory.setBeanFactory(this.beanFactory);
|
|
||||||
indexFactory.setBeanName(this.beanName);
|
|
||||||
indexFactory.setCache(this.gemfireCache);
|
|
||||||
indexFactory.setName("sessionAttributesIndex");
|
|
||||||
indexFactory.setExpression(String.format("s.attributes[%1$s]",
|
|
||||||
getIndexableSessionAttributesAsGemFireIndexExpression()));
|
|
||||||
indexFactory.setFrom(String.format("%1$s s", GemFireUtils.toRegionPath(this.regionName)));
|
|
||||||
indexFactory.setOverride(true);
|
|
||||||
indexFactory.afterPropertiesSet();
|
|
||||||
|
|
||||||
return indexFactory.getObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the names of all Session attributes that will be indexed by GemFire as single, comma-delimited
|
|
||||||
* String value constituting the Index expression of the Index definition.
|
|
||||||
*
|
|
||||||
* @return a String composed of all the named Session attributes for which GemFire
|
|
||||||
* will create an Index as an Index definition expression. If the indexable Session
|
|
||||||
* attributes were not configured, then the wildcard ("*") is returned.
|
|
||||||
* @see org.apache.geode.cache.query.Index#getIndexedExpression()
|
|
||||||
*/
|
|
||||||
protected String getIndexableSessionAttributesAsGemFireIndexExpression() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
|
|
||||||
for (String sessionAttribute : getIndexableSessionAttributes()) {
|
|
||||||
builder.append(builder.length() > 0 ? ", " : "");
|
|
||||||
builder.append(String.format("'%s'", sessionAttribute));
|
|
||||||
}
|
|
||||||
|
|
||||||
String indexExpression = builder.toString();
|
|
||||||
|
|
||||||
return (indexExpression.isEmpty() ? "*" : indexExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public Index getObject() throws Exception {
|
|
||||||
return this.sessionAttributesIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public Class<?> getObjectType() {
|
|
||||||
return (this.sessionAttributesIndex != null ? this.sessionAttributesIndex.getClass() : Index.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public boolean isSingleton() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
|
||||||
this.beanFactory = beanFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void setBeanName(String beanName) {
|
|
||||||
this.beanName = beanName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void setGemFireCache(GemFireCache gemfireCache) {
|
|
||||||
this.gemfireCache = gemfireCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void setIndexableSessionAttributes(String[] indexableSessionAttributes) {
|
|
||||||
this.indexableSessionAttributes = indexableSessionAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected String[] getIndexableSessionAttributes() {
|
|
||||||
return (this.indexableSessionAttributes != null ? this.indexableSessionAttributes
|
|
||||||
: DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
public void setRegionName(String regionName) {
|
|
||||||
this.regionName = regionName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected String getRegionName() {
|
|
||||||
return this.regionName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire.support;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Cache;
|
|
||||||
import org.apache.geode.cache.DataPolicy;
|
|
||||||
import org.apache.geode.cache.GemFireCache;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.RegionShortcut;
|
|
||||||
import org.apache.geode.cache.client.ClientCache;
|
|
||||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
|
||||||
import org.apache.geode.internal.cache.GemFireCacheImpl;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GemFireUtils is an abstract, extensible utility class for working with GemFire types
|
|
||||||
* and functionality and is used by Spring Session's GemFire adapter support classes.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
public abstract class GemFireUtils {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Null-safe method to close the given {@link Closeable} object.
|
|
||||||
*
|
|
||||||
* @param obj the {@link Closeable} object to close.
|
|
||||||
* @return true if the {@link Closeable} object is not null and was successfully
|
|
||||||
* closed, otherwise return false.
|
|
||||||
* @see java.io.Closeable
|
|
||||||
*/
|
|
||||||
public static boolean close(Closeable obj) {
|
|
||||||
if (obj != null) {
|
|
||||||
try {
|
|
||||||
obj.close();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (IOException ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the GemFire cache is a client.
|
|
||||||
*
|
|
||||||
* @param gemFireCache a reference to the GemFire cache.
|
|
||||||
* @return a boolean value indicating whether the GemFire cache is a client.
|
|
||||||
* @see org.apache.geode.cache.client.ClientCache
|
|
||||||
* @see org.apache.geode.cache.GemFireCache
|
|
||||||
*/
|
|
||||||
public static boolean isClient(GemFireCache gemFireCache) {
|
|
||||||
boolean client = (gemFireCache instanceof ClientCache);
|
|
||||||
client &= (!(gemFireCache instanceof GemFireCacheImpl) || ((GemFireCacheImpl) gemFireCache).isClient());
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the GemFire cache is a peer.
|
|
||||||
*
|
|
||||||
* @param gemFireCache a reference to the GemFire cache.
|
|
||||||
* @return a boolean value indicating whether the GemFire cache is a peer.
|
|
||||||
* @see org.apache.geode.cache.Cache
|
|
||||||
* @see org.apache.geode.cache.GemFireCache
|
|
||||||
*/
|
|
||||||
public static boolean isPeer(GemFireCache gemFireCache) {
|
|
||||||
return (gemFireCache instanceof Cache && !isClient(gemFireCache));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the given {@link ClientRegionShortcut} is local only.
|
|
||||||
*
|
|
||||||
* @param shortcut the ClientRegionShortcut to evaluate.
|
|
||||||
* @return a boolean value indicating if the {@link ClientRegionShortcut} is local or
|
|
||||||
* not.
|
|
||||||
* @see org.apache.geode.cache.client.ClientRegionShortcut
|
|
||||||
*/
|
|
||||||
public static boolean isLocal(ClientRegionShortcut shortcut) {
|
|
||||||
switch (shortcut) {
|
|
||||||
case LOCAL:
|
|
||||||
case LOCAL_HEAP_LRU:
|
|
||||||
case LOCAL_OVERFLOW:
|
|
||||||
case LOCAL_PERSISTENT:
|
|
||||||
case LOCAL_PERSISTENT_OVERFLOW:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the client {@link ClientRegionShortcut} is a proxy-based
|
|
||||||
* shortcut. NOTE: "proxy"-based Regions keep no local state.
|
|
||||||
*
|
|
||||||
* @param shortcut the client {@link ClientRegionShortcut} to evaluate.
|
|
||||||
* @return a boolean value indicating whether the client {@link ClientRegionShortcut}
|
|
||||||
* refers to a proxy-based shortcut.
|
|
||||||
* @see org.apache.geode.cache.client.ClientRegionShortcut
|
|
||||||
*/
|
|
||||||
public static boolean isProxy(ClientRegionShortcut shortcut) {
|
|
||||||
switch (shortcut) {
|
|
||||||
case PROXY:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the given {@link Region} is a PROXY, which would be indicated by the {@link Region}
|
|
||||||
* having a {@link DataPolicy} of {@link DataPolicy#EMPTY}.
|
|
||||||
*
|
|
||||||
* @param region {@link Region} to evaluate.
|
|
||||||
* @return a boolean value indicating whether the {@link Region} is a PROXY.
|
|
||||||
* @see org.apache.geode.cache.DataPolicy
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
*/
|
|
||||||
public static boolean isProxy(Region<?, ?> region) {
|
|
||||||
return DataPolicy.EMPTY.equals(region.getAttributes().getDataPolicy());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the peer {@link RegionShortcut} is a proxy-based shortcut. NOTE:
|
|
||||||
* "proxy"-based Regions keep no local state.
|
|
||||||
*
|
|
||||||
* @param shortcut the peer {@link RegionShortcut} to evaluate.
|
|
||||||
* @return a boolean value indicating whether the peer {@link RegionShortcut} refers
|
|
||||||
* to a proxy-based shortcut.
|
|
||||||
* @see org.apache.geode.cache.RegionShortcut
|
|
||||||
*/
|
|
||||||
public static boolean isProxy(RegionShortcut shortcut) {
|
|
||||||
switch (shortcut) {
|
|
||||||
case PARTITION_PROXY:
|
|
||||||
case PARTITION_PROXY_REDUNDANT:
|
|
||||||
case REPLICATE_PROXY:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a {@link Region} name to a {@link Region} path.
|
|
||||||
*
|
|
||||||
* @param regionName a String specifying the name of the {@link Region}.
|
|
||||||
* @return a String path for the given {@link Region} by name.
|
|
||||||
* @see org.apache.geode.cache.Region#getFullPath()
|
|
||||||
* @see org.apache.geode.cache.Region#getName()
|
|
||||||
*/
|
|
||||||
public static String toRegionPath(String regionName) {
|
|
||||||
return String.format("%1$s%2$s", Region.SEPARATOR, regionName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,436 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.AttributesMutator;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.query.SelectResults;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
|
||||||
import org.mockito.stubbing.Answer;
|
|
||||||
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
|
||||||
import org.springframework.data.gemfire.GemfireAccessor;
|
|
||||||
import org.springframework.data.gemfire.GemfireOperations;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
|
||||||
import org.springframework.session.events.AbstractSessionEvent;
|
|
||||||
import org.springframework.session.events.SessionDeletedEvent;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
|
||||||
import static org.mockito.ArgumentMatchers.isA;
|
|
||||||
import static org.mockito.ArgumentMatchers.same;
|
|
||||||
import static org.mockito.BDDMockito.given;
|
|
||||||
import static org.mockito.BDDMockito.willAnswer;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GemFireOperationsSessionRepositoryTest class is a test suite of test cases testing
|
|
||||||
* the contract and functionality of the GemFireOperationsSessionRepository class.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.1.0
|
|
||||||
* @see org.junit.Test
|
|
||||||
* @see org.junit.runner.RunWith
|
|
||||||
* @see org.mockito.Mock
|
|
||||||
* @see org.mockito.Mockito
|
|
||||||
* @see org.mockito.junit.MockitoJUnitRunner
|
|
||||||
* @see org.springframework.data.gemfire.GemfireOperations
|
|
||||||
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
*/
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
|
||||||
public class GemFireOperationsSessionRepositoryTest {
|
|
||||||
|
|
||||||
protected static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private ApplicationEventPublisher mockApplicationEventPublisher;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private AttributesMutator<Object, ExpiringSession> mockAttributesMutator;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private GemfireOperationsAccessor mockTemplate;
|
|
||||||
|
|
||||||
private GemFireOperationsSessionRepository sessionRepository;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private Region<Object, ExpiringSession> mockRegion;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() throws Exception {
|
|
||||||
given(this.mockRegion.getAttributesMutator()).willReturn(this.mockAttributesMutator);
|
|
||||||
given(this.mockRegion.getFullPath()).willReturn("/Example");
|
|
||||||
given(this.mockTemplate.<Object, ExpiringSession>getRegion()).willReturn(this.mockRegion);
|
|
||||||
|
|
||||||
this.sessionRepository = new GemFireOperationsSessionRepository(this.mockTemplate);
|
|
||||||
this.sessionRepository.setApplicationEventPublisher(this.mockApplicationEventPublisher);
|
|
||||||
this.sessionRepository.setMaxInactiveIntervalInSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
|
||||||
this.sessionRepository.afterPropertiesSet();
|
|
||||||
|
|
||||||
assertThat(this.sessionRepository.getApplicationEventPublisher()).isSameAs(this.mockApplicationEventPublisher);
|
|
||||||
assertThat(this.sessionRepository.getFullyQualifiedRegionName()).isEqualTo("/Example");
|
|
||||||
assertThat(this.sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
verify(this.mockAttributesMutator, times(1)).addCacheListener(same(this.sessionRepository));
|
|
||||||
verify(this.mockRegion, times(1)).getFullPath();
|
|
||||||
verify(this.mockTemplate, times(1)).getRegion();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void findByIndexNameValueFindsMatchingSession() {
|
|
||||||
ExpiringSession mockSession = mock(ExpiringSession.class, "MockSession");
|
|
||||||
|
|
||||||
given(mockSession.getId()).willReturn("1");
|
|
||||||
|
|
||||||
SelectResults<Object> mockSelectResults = mock(SelectResults.class);
|
|
||||||
|
|
||||||
given(mockSelectResults.asList()).willReturn(Collections.<Object>singletonList(mockSession));
|
|
||||||
|
|
||||||
String indexName = "vip";
|
|
||||||
String indexValue = "rwinch";
|
|
||||||
|
|
||||||
String expectedQql = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_INDEX_NAME_VALUE_QUERY,
|
|
||||||
this.sessionRepository.getFullyQualifiedRegionName(), indexName);
|
|
||||||
|
|
||||||
given(this.mockTemplate.find(eq(expectedQql), eq(indexValue))).willReturn(mockSelectResults);
|
|
||||||
|
|
||||||
Map<String, ExpiringSession> sessions =
|
|
||||||
this.sessionRepository.findByIndexNameAndIndexValue(indexName, indexValue);
|
|
||||||
|
|
||||||
assertThat(sessions).isNotNull();
|
|
||||||
assertThat(sessions.size()).isEqualTo(1);
|
|
||||||
assertThat(sessions.get("1")).isEqualTo(mockSession);
|
|
||||||
|
|
||||||
verify(this.mockTemplate, times(1)).find(eq(expectedQql), eq(indexValue));
|
|
||||||
verify(mockSelectResults, times(1)).asList();
|
|
||||||
verify(mockSession, times(1)).getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void findByPrincipalNameFindsMatchingSessions() throws Exception {
|
|
||||||
ExpiringSession mockSessionOne = mock(ExpiringSession.class, "MockSessionOne");
|
|
||||||
ExpiringSession mockSessionTwo = mock(ExpiringSession.class, "MockSessionTwo");
|
|
||||||
ExpiringSession mockSessionThree = mock(ExpiringSession.class, "MockSessionThree");
|
|
||||||
|
|
||||||
given(mockSessionOne.getId()).willReturn("1");
|
|
||||||
given(mockSessionTwo.getId()).willReturn("2");
|
|
||||||
given(mockSessionThree.getId()).willReturn("3");
|
|
||||||
|
|
||||||
SelectResults<Object> mockSelectResults = mock(SelectResults.class);
|
|
||||||
|
|
||||||
given(mockSelectResults.asList())
|
|
||||||
.willReturn(Arrays.<Object>asList(mockSessionOne, mockSessionTwo, mockSessionThree));
|
|
||||||
|
|
||||||
String principalName = "jblum";
|
|
||||||
|
|
||||||
String expectedOql = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
|
|
||||||
this.sessionRepository.getFullyQualifiedRegionName());
|
|
||||||
|
|
||||||
given(this.mockTemplate.find(eq(expectedOql), eq(principalName))).willReturn(mockSelectResults);
|
|
||||||
|
|
||||||
Map<String, ExpiringSession> sessions = this.sessionRepository.findByIndexNameAndIndexValue(
|
|
||||||
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principalName);
|
|
||||||
|
|
||||||
assertThat(sessions).isNotNull();
|
|
||||||
assertThat(sessions.size()).isEqualTo(3);
|
|
||||||
assertThat(sessions.get("1")).isEqualTo(mockSessionOne);
|
|
||||||
assertThat(sessions.get("2")).isEqualTo(mockSessionTwo);
|
|
||||||
assertThat(sessions.get("3")).isEqualTo(mockSessionThree);
|
|
||||||
|
|
||||||
verify(this.mockTemplate, times(1)).find(eq(expectedOql), eq(principalName));
|
|
||||||
verify(mockSelectResults, times(1)).asList();
|
|
||||||
verify(mockSessionOne, times(1)).getId();
|
|
||||||
verify(mockSessionTwo, times(1)).getId();
|
|
||||||
verify(mockSessionThree, times(1)).getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void findByPrincipalNameReturnsNoMatchingSessions() {
|
|
||||||
SelectResults<Object> mockSelectResults = mock(SelectResults.class);
|
|
||||||
|
|
||||||
given(mockSelectResults.asList()).willReturn(Collections.emptyList());
|
|
||||||
|
|
||||||
String principalName = "jblum";
|
|
||||||
|
|
||||||
String expectedOql = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
|
|
||||||
this.sessionRepository.getFullyQualifiedRegionName());
|
|
||||||
|
|
||||||
given(this.mockTemplate.find(eq(expectedOql), eq(principalName))).willReturn(mockSelectResults);
|
|
||||||
|
|
||||||
Map<String, ExpiringSession> sessions = this.sessionRepository.findByIndexNameAndIndexValue(
|
|
||||||
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principalName);
|
|
||||||
|
|
||||||
assertThat(sessions).isNotNull();
|
|
||||||
assertThat(sessions.isEmpty()).isTrue();
|
|
||||||
|
|
||||||
verify(this.mockTemplate, times(1)).find(eq(expectedOql), eq(principalName));
|
|
||||||
verify(mockSelectResults, times(1)).asList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void prepareQueryReturnsPrincipalNameOql() {
|
|
||||||
String actualQql =
|
|
||||||
this.sessionRepository.prepareQuery(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
|
|
||||||
|
|
||||||
String expectedOql = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
|
|
||||||
this.sessionRepository.getFullyQualifiedRegionName());
|
|
||||||
|
|
||||||
assertThat(actualQql).isEqualTo(expectedOql);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void prepareQueryReturnsIndexNameValueOql() {
|
|
||||||
String attributeName = "testAttributeName";
|
|
||||||
String actualOql = this.sessionRepository.prepareQuery(attributeName);
|
|
||||||
String expectedOql = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_INDEX_NAME_VALUE_QUERY,
|
|
||||||
this.sessionRepository.getFullyQualifiedRegionName(), attributeName);
|
|
||||||
|
|
||||||
assertThat(actualOql).isEqualTo(expectedOql);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void createProperlyInitializedSession() {
|
|
||||||
long beforeOrAtCreationTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
ExpiringSession session = this.sessionRepository.createSession();
|
|
||||||
|
|
||||||
assertThat(session).isInstanceOf(AbstractGemFireOperationsSessionRepository.GemFireSession.class);
|
|
||||||
assertThat(session.getId()).isNotNull();
|
|
||||||
assertThat(session.getAttributeNames().isEmpty()).isTrue();
|
|
||||||
assertThat(session.getCreationTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime);
|
|
||||||
assertThat(session.getLastAccessedTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime);
|
|
||||||
assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getSessionDeletesMatchingExpiredSessionById() {
|
|
||||||
final String expectedSessionId = "1";
|
|
||||||
|
|
||||||
final ExpiringSession mockSession = mock(ExpiringSession.class);
|
|
||||||
|
|
||||||
given(mockSession.isExpired()).willReturn(true);
|
|
||||||
given(mockSession.getId()).willReturn(expectedSessionId);
|
|
||||||
given(this.mockTemplate.get(eq(expectedSessionId))).willReturn(mockSession);
|
|
||||||
given(this.mockTemplate.remove(eq(expectedSessionId))).willReturn(mockSession);
|
|
||||||
|
|
||||||
willAnswer(new Answer<Void>() {
|
|
||||||
public Void answer(final InvocationOnMock invocation) throws Throwable {
|
|
||||||
ApplicationEvent applicationEvent = invocation.getArgument(0);
|
|
||||||
|
|
||||||
assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class);
|
|
||||||
|
|
||||||
AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent;
|
|
||||||
|
|
||||||
assertThat(sessionEvent.getSource())
|
|
||||||
.isSameAs(GemFireOperationsSessionRepositoryTest.this.sessionRepository);
|
|
||||||
assertThat(sessionEvent.<ExpiringSession>getSession()).isSameAs(mockSession);
|
|
||||||
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}).given(this.mockApplicationEventPublisher).publishEvent(any(ApplicationEvent.class));
|
|
||||||
|
|
||||||
assertThat(this.sessionRepository.getSession(expectedSessionId)).isNull();
|
|
||||||
|
|
||||||
verify(this.mockTemplate, times(1)).get(eq(expectedSessionId));
|
|
||||||
verify(this.mockTemplate, times(1)).remove(eq(expectedSessionId));
|
|
||||||
verify(mockSession, times(1)).isExpired();
|
|
||||||
verify(mockSession, times(2)).getId();
|
|
||||||
verify(this.mockApplicationEventPublisher, times(1))
|
|
||||||
.publishEvent(isA(SessionDeletedEvent.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getSessionFindsMatchingNonExpiredSessionById() {
|
|
||||||
String expectedId = "1";
|
|
||||||
|
|
||||||
long expectedCreationTime = System.currentTimeMillis();
|
|
||||||
long currentLastAccessedTime = (expectedCreationTime + TimeUnit.MINUTES.toMillis(5));
|
|
||||||
|
|
||||||
ExpiringSession mockSession = mock(ExpiringSession.class);
|
|
||||||
|
|
||||||
given(mockSession.isExpired()).willReturn(false);
|
|
||||||
given(mockSession.getId()).willReturn(expectedId);
|
|
||||||
given(mockSession.getCreationTime()).willReturn(expectedCreationTime);
|
|
||||||
given(mockSession.getLastAccessedTime()).willReturn(currentLastAccessedTime);
|
|
||||||
given(mockSession.getAttributeNames()).willReturn(Collections.singleton("attrOne"));
|
|
||||||
given(mockSession.getAttribute(eq("attrOne"))).willReturn("test");
|
|
||||||
given(this.mockTemplate.get(eq(expectedId))).willReturn(mockSession);
|
|
||||||
|
|
||||||
ExpiringSession actualSession = this.sessionRepository.getSession(expectedId);
|
|
||||||
|
|
||||||
assertThat(actualSession).isNotSameAs(mockSession);
|
|
||||||
assertThat(actualSession.getId()).isEqualTo(expectedId);
|
|
||||||
assertThat(actualSession.getCreationTime()).isEqualTo(expectedCreationTime);
|
|
||||||
assertThat(actualSession.getLastAccessedTime()).isNotEqualTo(currentLastAccessedTime);
|
|
||||||
assertThat(actualSession.getLastAccessedTime()).isGreaterThanOrEqualTo(expectedCreationTime);
|
|
||||||
assertThat(actualSession.getAttributeNames()).isEqualTo(Collections.singleton("attrOne"));
|
|
||||||
assertThat(String.valueOf(actualSession.<String>getAttribute("attrOne"))).isEqualTo("test");
|
|
||||||
|
|
||||||
verify(this.mockTemplate, times(1)).get(eq(expectedId));
|
|
||||||
verify(mockSession, times(1)).isExpired();
|
|
||||||
verify(mockSession, times(1)).getId();
|
|
||||||
verify(mockSession, times(1)).getCreationTime();
|
|
||||||
verify(mockSession, times(1)).getLastAccessedTime();
|
|
||||||
verify(mockSession, times(1)).getAttributeNames();
|
|
||||||
verify(mockSession, times(1)).getAttribute(eq("attrOne"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getSessionReturnsNull() {
|
|
||||||
given(this.mockTemplate.get(anyString())).willReturn(null);
|
|
||||||
assertThat(this.sessionRepository.getSession("1")).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void saveStoresSession() {
|
|
||||||
final String expectedSessionId = "1";
|
|
||||||
|
|
||||||
final long expectedCreationTime = System.currentTimeMillis();
|
|
||||||
final long expectedLastAccessTime = (expectedCreationTime + TimeUnit.MINUTES.toMillis(5));
|
|
||||||
|
|
||||||
ExpiringSession mockSession = mock(ExpiringSession.class);
|
|
||||||
|
|
||||||
given(mockSession.getId()).willReturn(expectedSessionId);
|
|
||||||
given(mockSession.getCreationTime()).willReturn(expectedCreationTime);
|
|
||||||
given(mockSession.getLastAccessedTime()).willReturn(expectedLastAccessTime);
|
|
||||||
given(mockSession.getMaxInactiveIntervalInSeconds())
|
|
||||||
.willReturn(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
|
||||||
given(mockSession.getAttributeNames()).willReturn(Collections.<String>emptySet());
|
|
||||||
|
|
||||||
given(this.mockTemplate.put(eq(expectedSessionId),
|
|
||||||
isA(AbstractGemFireOperationsSessionRepository.GemFireSession.class)))
|
|
||||||
.willAnswer(new Answer<ExpiringSession>() {
|
|
||||||
public ExpiringSession answer(InvocationOnMock invocation) throws Throwable {
|
|
||||||
ExpiringSession session = invocation.getArgument(1);
|
|
||||||
|
|
||||||
assertThat(session).isNotNull();
|
|
||||||
assertThat(session.getId()).isEqualTo(expectedSessionId);
|
|
||||||
assertThat(session.getCreationTime()).isEqualTo(expectedCreationTime);
|
|
||||||
assertThat(session.getLastAccessedTime()).isEqualTo(expectedLastAccessTime);
|
|
||||||
assertThat(session.getMaxInactiveIntervalInSeconds())
|
|
||||||
.isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
|
||||||
assertThat(session.getAttributeNames().isEmpty()).isTrue();
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sessionRepository.save(mockSession);
|
|
||||||
|
|
||||||
verify(mockSession, times(2)).getId();
|
|
||||||
verify(mockSession, times(1)).getCreationTime();
|
|
||||||
verify(mockSession, times(1)).getLastAccessedTime();
|
|
||||||
verify(mockSession, times(1)).getMaxInactiveIntervalInSeconds();
|
|
||||||
verify(mockSession, times(1)).getAttributeNames();
|
|
||||||
verify(this.mockTemplate, times(1)).put(eq(expectedSessionId),
|
|
||||||
isA(AbstractGemFireOperationsSessionRepository.GemFireSession.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deleteRemovesExistingSessionAndHandlesDelete() {
|
|
||||||
final String expectedSessionId = "1";
|
|
||||||
|
|
||||||
final ExpiringSession mockSession = mock(ExpiringSession.class);
|
|
||||||
|
|
||||||
given(mockSession.getId()).willReturn(expectedSessionId);
|
|
||||||
given(this.mockTemplate.remove(eq(expectedSessionId))).willReturn(mockSession);
|
|
||||||
|
|
||||||
willAnswer(new Answer<Void>() {
|
|
||||||
public Void answer(final InvocationOnMock invocation) throws Throwable {
|
|
||||||
ApplicationEvent applicationEvent = invocation.getArgument(0);
|
|
||||||
|
|
||||||
assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class);
|
|
||||||
|
|
||||||
AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent;
|
|
||||||
|
|
||||||
assertThat(sessionEvent.getSource())
|
|
||||||
.isSameAs(GemFireOperationsSessionRepositoryTest.this.sessionRepository);
|
|
||||||
assertThat(sessionEvent.<ExpiringSession>getSession()).isSameAs(mockSession);
|
|
||||||
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}).given(this.mockApplicationEventPublisher).publishEvent(isA(SessionDeletedEvent.class));
|
|
||||||
|
|
||||||
this.sessionRepository.delete(expectedSessionId);
|
|
||||||
|
|
||||||
verify(mockSession, times(1)).getId();
|
|
||||||
verify(this.mockTemplate, times(1)).remove(eq(expectedSessionId));
|
|
||||||
verify(this.mockApplicationEventPublisher, times(1))
|
|
||||||
.publishEvent(isA(SessionDeletedEvent.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deleteRemovesNonExistingSessionAndHandlesDelete() {
|
|
||||||
final String expectedSessionId = "1";
|
|
||||||
|
|
||||||
given(this.mockTemplate.remove(anyString())).willReturn(null);
|
|
||||||
|
|
||||||
willAnswer(new Answer<Void>() {
|
|
||||||
public Void answer(final InvocationOnMock invocation) throws Throwable {
|
|
||||||
ApplicationEvent applicationEvent = invocation.getArgument(0);
|
|
||||||
|
|
||||||
assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class);
|
|
||||||
|
|
||||||
AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent;
|
|
||||||
|
|
||||||
assertThat(sessionEvent.getSource()).
|
|
||||||
isSameAs(GemFireOperationsSessionRepositoryTest.this.sessionRepository);
|
|
||||||
assertThat(sessionEvent.<ExpiringSession>getSession()).isNull();
|
|
||||||
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}).given(this.mockApplicationEventPublisher).publishEvent(isA(SessionDeletedEvent.class));
|
|
||||||
|
|
||||||
this.sessionRepository.delete(expectedSessionId);
|
|
||||||
|
|
||||||
verify(this.mockTemplate, times(1)).remove(eq(expectedSessionId));
|
|
||||||
verify(this.mockApplicationEventPublisher, times(1))
|
|
||||||
.publishEvent(isA(SessionDeletedEvent.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract class GemfireOperationsAccessor extends GemfireAccessor
|
|
||||||
implements GemfireOperations {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,407 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire.config.annotation.web.http;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Cache;
|
|
||||||
import org.apache.geode.cache.ExpirationAction;
|
|
||||||
import org.apache.geode.cache.ExpirationAttributes;
|
|
||||||
import org.apache.geode.cache.GemFireCache;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.RegionAttributes;
|
|
||||||
import org.apache.geode.cache.RegionShortcut;
|
|
||||||
import org.apache.geode.cache.client.ClientCache;
|
|
||||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import org.springframework.core.type.AnnotationMetadata;
|
|
||||||
import org.springframework.data.gemfire.GemfireOperations;
|
|
||||||
import org.springframework.data.gemfire.GemfireTemplate;
|
|
||||||
import org.springframework.data.gemfire.RegionAttributesFactoryBean;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository;
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
|
||||||
import static org.mockito.BDDMockito.given;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GemFireHttpSessionConfigurationTest class is a test suite of test cases testing the
|
|
||||||
* contract and functionality of the {@link GemFireHttpSessionConfiguration} class.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.1.0
|
|
||||||
* @see org.junit.Test
|
|
||||||
* @see org.mockito.Mockito
|
|
||||||
* @see org.springframework.data.gemfire.GemfireOperations
|
|
||||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
|
||||||
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
|
||||||
* @see org.apache.geode.cache.Cache
|
|
||||||
* @see org.apache.geode.cache.GemFireCache
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
* @see org.apache.geode.cache.client.ClientCache
|
|
||||||
*/
|
|
||||||
public class GemFireHttpSessionConfigurationTest {
|
|
||||||
|
|
||||||
private GemFireHttpSessionConfiguration gemfireConfiguration;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected <T> T getField(Object obj, String fieldName) {
|
|
||||||
try {
|
|
||||||
Field field = obj.getClass().getDeclaredField(fieldName);
|
|
||||||
field.setAccessible(true);
|
|
||||||
return (T) field.get(obj);
|
|
||||||
}
|
|
||||||
catch (NoSuchFieldException e) {
|
|
||||||
throw new IllegalArgumentException(String.format(
|
|
||||||
"field with name [%1$s] was not found in class [%2$s]", fieldName, obj), e);
|
|
||||||
}
|
|
||||||
catch (IllegalAccessException e) {
|
|
||||||
throw new Error(String.format("unable to access field [%1$s] on object of type [%2$s]",
|
|
||||||
fieldName, obj.getClass().getName()), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected <T> T[] toArray(T... array) {
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
this.gemfireConfiguration = new GemFireHttpSessionConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setAndGetBeanClassLoader() {
|
|
||||||
assertThat(this.gemfireConfiguration.getBeanClassLoader()).isNull();
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setBeanClassLoader(Thread.currentThread().getContextClassLoader());
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getBeanClassLoader()).isEqualTo(
|
|
||||||
Thread.currentThread().getContextClassLoader());
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setBeanClassLoader(null);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getBeanClassLoader()).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setAndGetClientRegionShortcut() {
|
|
||||||
assertThat(this.gemfireConfiguration.getClientRegionShortcut()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getClientRegionShortcut()).isEqualTo(
|
|
||||||
ClientRegionShortcut.CACHING_PROXY);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setClientRegionShortcut(null);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getClientRegionShortcut()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setAndGetMaxInactiveIntervalInSeconds() {
|
|
||||||
assertThat(this.gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setMaxInactiveIntervalInSeconds(300);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(300);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setMaxInactiveIntervalInSeconds(Integer.MAX_VALUE);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(Integer.MAX_VALUE);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setMaxInactiveIntervalInSeconds(-1);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(-1);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setMaxInactiveIntervalInSeconds(Integer.MIN_VALUE);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(Integer.MIN_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setAndGetPoolName() {
|
|
||||||
assertThat(this.gemfireConfiguration.getPoolName()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_GEMFIRE_POOL_NAME);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setPoolName("TestPoolName");
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getPoolName()).isEqualTo("TestPoolName");
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setPoolName(" ");
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getPoolName()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_GEMFIRE_POOL_NAME);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setPoolName("");
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getPoolName()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_GEMFIRE_POOL_NAME);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setPoolName(null);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getPoolName()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_GEMFIRE_POOL_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setAndGetServerRegionShortcut() {
|
|
||||||
assertThat(this.gemfireConfiguration.getServerRegionShortcut()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE_PERSISTENT);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getServerRegionShortcut()).isEqualTo(
|
|
||||||
RegionShortcut.REPLICATE_PERSISTENT);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setServerRegionShortcut(null);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getServerRegionShortcut()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setAndGetSpringSessionGemFireRegionName() {
|
|
||||||
assertThat(this.gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setSpringSessionGemFireRegionName("test");
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("test");
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setSpringSessionGemFireRegionName(" ");
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setSpringSessionGemFireRegionName("");
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setSpringSessionGemFireRegionName(null);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setsImportMetadata() {
|
|
||||||
AnnotationMetadata mockAnnotationMetadata = mock(AnnotationMetadata.class);
|
|
||||||
|
|
||||||
Map<String, Object> annotationAttributes = new HashMap<String, Object>(4);
|
|
||||||
|
|
||||||
annotationAttributes.put("clientRegionShortcut", ClientRegionShortcut.CACHING_PROXY);
|
|
||||||
annotationAttributes.put("indexableSessionAttributes", toArray("one", "two", "three"));
|
|
||||||
annotationAttributes.put("maxInactiveIntervalInSeconds", 600);
|
|
||||||
annotationAttributes.put("poolName", "TestPool");
|
|
||||||
annotationAttributes.put("serverRegionShortcut", RegionShortcut.REPLICATE);
|
|
||||||
annotationAttributes.put("regionName", "TEST");
|
|
||||||
|
|
||||||
given(mockAnnotationMetadata.getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName())))
|
|
||||||
.willReturn(annotationAttributes);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setImportMetadata(mockAnnotationMetadata);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY);
|
|
||||||
assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()).isEqualTo(toArray("one", "two", "three"));
|
|
||||||
assertThat(this.gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(600);
|
|
||||||
assertThat(this.gemfireConfiguration.getPoolName()).isEqualTo("TestPool");
|
|
||||||
assertThat(this.gemfireConfiguration.getServerRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE);
|
|
||||||
assertThat(this.gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("TEST");
|
|
||||||
|
|
||||||
verify(mockAnnotationMetadata, times(1)).getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void createsAndInitializesSessionRepositoryBean() {
|
|
||||||
GemfireOperations mockGemfireOperations = mock(GemfireOperations.class);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setMaxInactiveIntervalInSeconds(120);
|
|
||||||
|
|
||||||
GemFireOperationsSessionRepository sessionRepository = this.gemfireConfiguration.sessionRepository(
|
|
||||||
mockGemfireOperations);
|
|
||||||
|
|
||||||
assertThat(sessionRepository).isNotNull();
|
|
||||||
assertThat(sessionRepository.getTemplate()).isSameAs(mockGemfireOperations);
|
|
||||||
assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(120);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void createsAndInitializesSessionRegionTemplateBean() {
|
|
||||||
GemFireCache mockGemFireCache = mock(GemFireCache.class);
|
|
||||||
Region<Object, Object> mockRegion = mock(Region.class);
|
|
||||||
|
|
||||||
given(mockGemFireCache.getRegion(eq("Example"))).willReturn(mockRegion);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setSpringSessionGemFireRegionName("Example");
|
|
||||||
|
|
||||||
GemfireTemplate template = this.gemfireConfiguration.sessionRegionTemplate(mockGemFireCache);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("Example");
|
|
||||||
assertThat(template).isNotNull();
|
|
||||||
assertThat(template.getRegion()).isSameAs(mockRegion);
|
|
||||||
|
|
||||||
verify(mockGemFireCache, times(1)).getRegion(eq("Example"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void createsAndInitializesSessionRegionBean() {
|
|
||||||
GemFireCache mockGemFireCache = mock(GemFireCache.class);
|
|
||||||
RegionAttributes<Object, ExpiringSession> mockRegionAttributes = mock(RegionAttributes.class);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY);
|
|
||||||
this.gemfireConfiguration.setPoolName("TestPool");
|
|
||||||
this.gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE_PERSISTENT);
|
|
||||||
this.gemfireConfiguration.setSpringSessionGemFireRegionName("TestRegion");
|
|
||||||
|
|
||||||
GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession> sessionRegionFactoryBean =
|
|
||||||
this.gemfireConfiguration.sessionRegion(mockGemFireCache, mockRegionAttributes);
|
|
||||||
|
|
||||||
assertThat(sessionRegionFactoryBean).isNotNull();
|
|
||||||
assertThat(this.<ClientRegionShortcut>getField(sessionRegionFactoryBean, "clientRegionShortcut"))
|
|
||||||
.isEqualTo(ClientRegionShortcut.CACHING_PROXY);
|
|
||||||
assertThat(this.<GemFireCache>getField(sessionRegionFactoryBean, "gemfireCache"))
|
|
||||||
.isEqualTo(mockGemFireCache);
|
|
||||||
assertThat(this.<String>getField(sessionRegionFactoryBean, "poolName")).isEqualTo("TestPool");
|
|
||||||
assertThat(this.<RegionAttributes<Object, ExpiringSession>>getField(sessionRegionFactoryBean,
|
|
||||||
"regionAttributes")).isEqualTo(mockRegionAttributes);
|
|
||||||
assertThat(this.<String>getField(sessionRegionFactoryBean, "regionName")).isEqualTo("TestRegion");
|
|
||||||
assertThat(this.<RegionShortcut>getField(sessionRegionFactoryBean, "serverRegionShortcut"))
|
|
||||||
.isEqualTo(RegionShortcut.REPLICATE_PERSISTENT);
|
|
||||||
|
|
||||||
verifyZeroInteractions(mockGemFireCache);
|
|
||||||
verifyZeroInteractions(mockRegionAttributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void createsAndInitializesSessionRegionAttributesWithExpiration() throws Exception {
|
|
||||||
Cache mockCache = mock(Cache.class);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setMaxInactiveIntervalInSeconds(300);
|
|
||||||
this.gemfireConfiguration.setServerRegionShortcut(RegionShortcut.LOCAL);
|
|
||||||
|
|
||||||
RegionAttributesFactoryBean regionAttributesFactory =
|
|
||||||
this.gemfireConfiguration.sessionRegionAttributes(mockCache);
|
|
||||||
|
|
||||||
assertThat(regionAttributesFactory).isNotNull();
|
|
||||||
|
|
||||||
regionAttributesFactory.afterPropertiesSet();
|
|
||||||
|
|
||||||
RegionAttributes<Object, ExpiringSession> sessionRegionAttributes = regionAttributesFactory.getObject();
|
|
||||||
|
|
||||||
assertThat(sessionRegionAttributes).isNotNull();
|
|
||||||
assertThat(sessionRegionAttributes.getKeyConstraint()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.SPRING_SESSION_GEMFIRE_REGION_KEY_CONSTRAINT);
|
|
||||||
assertThat(sessionRegionAttributes.getValueConstraint()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.SPRING_SESSION_GEMFIRE_REGION_VALUE_CONSTRAINT);
|
|
||||||
|
|
||||||
ExpirationAttributes entryIdleTimeoutExpiration = sessionRegionAttributes.getEntryIdleTimeout();
|
|
||||||
|
|
||||||
assertThat(entryIdleTimeoutExpiration).isNotNull();
|
|
||||||
assertThat(entryIdleTimeoutExpiration.getAction()).isEqualTo(ExpirationAction.INVALIDATE);
|
|
||||||
assertThat(entryIdleTimeoutExpiration.getTimeout()).isEqualTo(300);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void createsAndInitializesSessionRegionAttributesWithoutExpiration() throws Exception {
|
|
||||||
ClientCache mockClientCache = mock(ClientCache.class);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setMaxInactiveIntervalInSeconds(300);
|
|
||||||
|
|
||||||
RegionAttributesFactoryBean regionAttributesFactory =
|
|
||||||
this.gemfireConfiguration.sessionRegionAttributes(mockClientCache);
|
|
||||||
|
|
||||||
assertThat(regionAttributesFactory).isNotNull();
|
|
||||||
|
|
||||||
regionAttributesFactory.afterPropertiesSet();
|
|
||||||
|
|
||||||
RegionAttributes<Object, ExpiringSession> sessionRegionAttributes = regionAttributesFactory.getObject();
|
|
||||||
|
|
||||||
assertThat(sessionRegionAttributes).isNotNull();
|
|
||||||
assertThat(sessionRegionAttributes.getKeyConstraint()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.SPRING_SESSION_GEMFIRE_REGION_KEY_CONSTRAINT);
|
|
||||||
assertThat(sessionRegionAttributes.getValueConstraint()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.SPRING_SESSION_GEMFIRE_REGION_VALUE_CONSTRAINT);
|
|
||||||
|
|
||||||
ExpirationAttributes entryIdleTimeoutExpiration = sessionRegionAttributes.getEntryIdleTimeout();
|
|
||||||
|
|
||||||
assertThat(entryIdleTimeoutExpiration).isNotNull();
|
|
||||||
assertThat(entryIdleTimeoutExpiration.getAction()).isEqualTo(ExpirationAction.INVALIDATE);
|
|
||||||
assertThat(entryIdleTimeoutExpiration.getTimeout()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void expirationIsAllowed() {
|
|
||||||
Cache mockCache = mock(Cache.class);
|
|
||||||
|
|
||||||
ClientCache mockClientCache = mock(ClientCache.class);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.PROXY);
|
|
||||||
this.gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.isExpirationAllowed(mockCache)).isTrue();
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT_OVERFLOW);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.isExpirationAllowed(mockCache)).isTrue();
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY);
|
|
||||||
this.gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION_PROXY);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.isExpirationAllowed(mockClientCache)).isTrue();
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW);
|
|
||||||
this.gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE_PROXY);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.isExpirationAllowed(mockClientCache)).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void expirationIsNotAllowed() {
|
|
||||||
Cache mockCache = mock(Cache.class);
|
|
||||||
ClientCache mockClientCache = mock(ClientCache.class);
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.PROXY);
|
|
||||||
this.gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.isExpirationAllowed(mockClientCache)).isFalse();
|
|
||||||
|
|
||||||
this.gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.LOCAL);
|
|
||||||
this.gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION_PROXY);
|
|
||||||
|
|
||||||
assertThat(this.gemfireConfiguration.isExpirationAllowed(mockCache)).isFalse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,295 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire.config.annotation.web.http.support;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Cache;
|
|
||||||
import org.apache.geode.cache.GemFireCache;
|
|
||||||
import org.apache.geode.cache.InterestResultPolicy;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.RegionAttributes;
|
|
||||||
import org.apache.geode.cache.RegionShortcut;
|
|
||||||
import org.apache.geode.cache.client.ClientCache;
|
|
||||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.rules.ExpectedException;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
|
||||||
import org.springframework.data.gemfire.client.Interest;
|
|
||||||
import org.springframework.session.ExpiringSession;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GemFireCacheTypeAwareRegionFactoryBeanTest class is a test suite of test cases
|
|
||||||
* testing the contract and functionality of the GemFireCacheTypeAwareRegionFactoryBean
|
|
||||||
* class.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.1.0
|
|
||||||
* @see org.junit.Rule
|
|
||||||
* @see org.junit.Test
|
|
||||||
* @see org.junit.rules.ExpectedException
|
|
||||||
* @see org.mockito.Mockito
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean
|
|
||||||
* @see org.apache.geode.cache.Cache
|
|
||||||
* @see org.apache.geode.cache.GemFireCache
|
|
||||||
* @see org.apache.geode.cache.InterestResultPolicy
|
|
||||||
* @see org.apache.geode.cache.Region
|
|
||||||
* @see org.apache.geode.cache.RegionAttributes
|
|
||||||
* @see org.apache.geode.cache.RegionShortcut
|
|
||||||
* @see org.apache.geode.cache.client.ClientCache
|
|
||||||
* @see org.apache.geode.cache.client.ClientRegionShortcut
|
|
||||||
*/
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
|
||||||
public class GemFireCacheTypeAwareRegionFactoryBeanTest {
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public ExpectedException exception = ExpectedException.none();
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
Cache mockCache;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
ClientCache mockClientCache;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
Region<Object, ExpiringSession> mockClientRegion;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
Region<Object, ExpiringSession> mockServerRegion;
|
|
||||||
|
|
||||||
private GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession> regionFactoryBean;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
this.regionFactoryBean = new GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void afterPropertiesSetCreatesCorrectRegionForGemFireCacheType(final GemFireCache expectedCache,
|
|
||||||
Region<Object, ExpiringSession> expectedRegion) throws Exception {
|
|
||||||
|
|
||||||
this.regionFactoryBean = new GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession>() {
|
|
||||||
@Override
|
|
||||||
protected Region<Object, ExpiringSession> newClientRegion(GemFireCache gemfireCache) throws Exception {
|
|
||||||
assertThat(gemfireCache).isSameAs(expectedCache);
|
|
||||||
return GemFireCacheTypeAwareRegionFactoryBeanTest.this.mockClientRegion;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Region<Object, ExpiringSession> newServerRegion(GemFireCache gemfireCache) throws Exception {
|
|
||||||
assertThat(gemfireCache).isSameAs(expectedCache);
|
|
||||||
return GemFireCacheTypeAwareRegionFactoryBeanTest.this.mockServerRegion;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.regionFactoryBean.setGemfireCache(expectedCache);
|
|
||||||
this.regionFactoryBean.afterPropertiesSet();
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getGemfireCache()).isSameAs(expectedCache);
|
|
||||||
assertThat(this.regionFactoryBean.getObject()).isEqualTo(expectedRegion);
|
|
||||||
assertThat(this.regionFactoryBean.getObjectType()).isEqualTo(expectedRegion.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void afterPropertiesSetCreatesClientRegionForClientCache() throws Exception {
|
|
||||||
afterPropertiesSetCreatesCorrectRegionForGemFireCacheType(this.mockClientCache, this.mockClientRegion);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void afterPropertiesSetCreatesServerRegionForPeerCache() throws Exception {
|
|
||||||
afterPropertiesSetCreatesCorrectRegionForGemFireCacheType(this.mockCache, this.mockServerRegion);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void allKeysInterestsRegistration() {
|
|
||||||
Interest<Object>[] interests = this.regionFactoryBean.registerInterests(true);
|
|
||||||
|
|
||||||
assertThat(interests).isNotNull();
|
|
||||||
assertThat(interests.length).isEqualTo(1);
|
|
||||||
assertThat(interests[0].isDurable()).isFalse();
|
|
||||||
assertThat(interests[0].getKey().toString()).isEqualTo("ALL_KEYS");
|
|
||||||
assertThat(interests[0].getPolicy()).isEqualTo(InterestResultPolicy.KEYS);
|
|
||||||
assertThat(interests[0].isReceiveValues()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void emptyInterestsRegistration() {
|
|
||||||
Interest<Object>[] interests = this.regionFactoryBean.registerInterests(false);
|
|
||||||
|
|
||||||
assertThat(interests).isNotNull();
|
|
||||||
assertThat(interests.length).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getObjectTypeBeforeInitializationIsRegionClass() {
|
|
||||||
assertThat(this.regionFactoryBean.getObjectType()).isEqualTo(Region.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void isSingletonIsTrue() {
|
|
||||||
assertThat(this.regionFactoryBean.isSingleton()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setAndGetBeanFactory() {
|
|
||||||
BeanFactory mockBeanFactory = mock(BeanFactory.class);
|
|
||||||
|
|
||||||
this.regionFactoryBean.setBeanFactory(mockBeanFactory);
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getBeanFactory()).isEqualTo(mockBeanFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setBeanFactoryToNullThrowsIllegalArgumentException() {
|
|
||||||
this.exception.expect(IllegalArgumentException.class);
|
|
||||||
this.exception.expectMessage("BeanFactory must not be null");
|
|
||||||
this.regionFactoryBean.setBeanFactory(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getBeanFactoryWhenNullThrowsIllegalStateException() {
|
|
||||||
this.exception.expect(IllegalStateException.class);
|
|
||||||
this.exception.expectMessage("A reference to the BeanFactory was not properly configured");
|
|
||||||
this.regionFactoryBean.getBeanFactory();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setAndGetClientRegionShortcut() {
|
|
||||||
assertThat(this.regionFactoryBean.getClientRegionShortcut()).isEqualTo(
|
|
||||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_CLIENT_REGION_SHORTCUT);
|
|
||||||
|
|
||||||
this.regionFactoryBean.setClientRegionShortcut(ClientRegionShortcut.LOCAL_PERSISTENT);
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getClientRegionShortcut()).isEqualTo(
|
|
||||||
ClientRegionShortcut.LOCAL_PERSISTENT);
|
|
||||||
|
|
||||||
this.regionFactoryBean.setClientRegionShortcut(null);
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getClientRegionShortcut()).isEqualTo(
|
|
||||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_CLIENT_REGION_SHORTCUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setAndGetGemfireCache() {
|
|
||||||
Cache mockCache = mock(Cache.class);
|
|
||||||
|
|
||||||
this.regionFactoryBean.setGemfireCache(mockCache);
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getGemfireCache()).isEqualTo(mockCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setGemfireCacheToNullThrowsIllegalArgumentException() {
|
|
||||||
this.exception.expect(IllegalArgumentException.class);
|
|
||||||
this.exception.expectMessage("GemFireCache must not be null");
|
|
||||||
this.regionFactoryBean.setGemfireCache(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getGemfireCacheWhenNullThrowsIllegalStateException() {
|
|
||||||
this.exception.expect(IllegalStateException.class);
|
|
||||||
this.exception.expectMessage("A reference to the GemFireCache was not properly configured");
|
|
||||||
this.regionFactoryBean.getGemfireCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setAndGetPoolName() {
|
|
||||||
assertThat(this.regionFactoryBean.getPoolName()).isEqualTo(
|
|
||||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_GEMFIRE_POOL_NAME);
|
|
||||||
|
|
||||||
this.regionFactoryBean.setPoolName("TestPoolName");
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getPoolName()).isEqualTo("TestPoolName");
|
|
||||||
|
|
||||||
this.regionFactoryBean.setPoolName(" ");
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getPoolName()).isEqualTo(
|
|
||||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_GEMFIRE_POOL_NAME);
|
|
||||||
|
|
||||||
this.regionFactoryBean.setPoolName("");
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getPoolName()).isEqualTo(
|
|
||||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_GEMFIRE_POOL_NAME);
|
|
||||||
|
|
||||||
this.regionFactoryBean.setPoolName(null);
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getPoolName()).isEqualTo(
|
|
||||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_GEMFIRE_POOL_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void setAndGetRegionAttributes() {
|
|
||||||
RegionAttributes<Object, ExpiringSession> mockRegionAttributes = mock(RegionAttributes.class);
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getRegionAttributes()).isNull();
|
|
||||||
|
|
||||||
this.regionFactoryBean.setRegionAttributes(mockRegionAttributes);
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getRegionAttributes()).isSameAs(mockRegionAttributes);
|
|
||||||
|
|
||||||
this.regionFactoryBean.setRegionAttributes(null);
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getRegionAttributes()).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setAndGetRegionName() {
|
|
||||||
assertThat(this.regionFactoryBean.getRegionName()).isEqualTo(
|
|
||||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
|
|
||||||
this.regionFactoryBean.setRegionName("Example");
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getRegionName()).isEqualTo("Example");
|
|
||||||
|
|
||||||
this.regionFactoryBean.setRegionName(" ");
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getRegionName()).isEqualTo(
|
|
||||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
|
|
||||||
this.regionFactoryBean.setRegionName("");
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getRegionName()).isEqualTo(
|
|
||||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
|
|
||||||
this.regionFactoryBean.setRegionName(null);
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getRegionName()).isEqualTo(
|
|
||||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setAndGetServerRegionShortcut() {
|
|
||||||
assertThat(this.regionFactoryBean.getServerRegionShortcut()).isEqualTo(
|
|
||||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SERVER_REGION_SHORTCUT);
|
|
||||||
|
|
||||||
this.regionFactoryBean.setServerRegionShortcut(RegionShortcut.LOCAL_PERSISTENT);
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getServerRegionShortcut()).isEqualTo(RegionShortcut.LOCAL_PERSISTENT);
|
|
||||||
|
|
||||||
this.regionFactoryBean.setServerRegionShortcut(null);
|
|
||||||
|
|
||||||
assertThat(this.regionFactoryBean.getServerRegionShortcut()).isEqualTo(
|
|
||||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SERVER_REGION_SHORTCUT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire.config.annotation.web.http.support;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.query.Index;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test suite of test cases testing the contract and functionality
|
|
||||||
* of the {@link SessionAttributesIndexFactoryBean} class.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.3.0
|
|
||||||
* @see org.junit.Test
|
|
||||||
* @see org.mockito.Mockito
|
|
||||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.support.SessionAttributesIndexFactoryBean
|
|
||||||
*/
|
|
||||||
public class SessionAttributesIndexFactoryBeanTests {
|
|
||||||
|
|
||||||
static <T> T[] toArray(T... array) {
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SessionAttributesIndexFactoryBean indexFactoryBean;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
this.indexFactoryBean = new SessionAttributesIndexFactoryBean();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void indexIsNotInitializedWhenNoIndexableSessionAttributesAreConfigured() throws Exception {
|
|
||||||
final Index mockIndex = mock(Index.class);
|
|
||||||
|
|
||||||
SessionAttributesIndexFactoryBean indexFactoryBean = new SessionAttributesIndexFactoryBean() {
|
|
||||||
@Override
|
|
||||||
protected Index newIndex() throws Exception {
|
|
||||||
return mockIndex;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
indexFactoryBean.afterPropertiesSet();
|
|
||||||
|
|
||||||
assertThat(indexFactoryBean.getObject()).isNull();
|
|
||||||
assertThat(indexFactoryBean.getObjectType()).isEqualTo(Index.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void initializesIndexWhenIndexableSessionAttributesAreConfigured() throws Exception {
|
|
||||||
final Index mockIndex = mock(Index.class);
|
|
||||||
|
|
||||||
SessionAttributesIndexFactoryBean indexFactoryBean = new SessionAttributesIndexFactoryBean() {
|
|
||||||
@Override
|
|
||||||
protected Index newIndex() throws Exception {
|
|
||||||
return mockIndex;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
indexFactoryBean.setIndexableSessionAttributes(toArray("one", "two"));
|
|
||||||
indexFactoryBean.afterPropertiesSet();
|
|
||||||
|
|
||||||
assertThat(indexFactoryBean.getObject()).isEqualTo(mockIndex);
|
|
||||||
assertThat(indexFactoryBean.getObjectType()).isEqualTo(mockIndex.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void isSingletonIsTrue() {
|
|
||||||
assertThat(this.indexFactoryBean.isSingleton()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setAndGetIndexableSessionAttributes() {
|
|
||||||
assertThat(this.indexFactoryBean.getIndexableSessionAttributes()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
|
|
||||||
|
|
||||||
assertThat(this.indexFactoryBean.getIndexableSessionAttributesAsGemFireIndexExpression()).isEqualTo("*");
|
|
||||||
|
|
||||||
this.indexFactoryBean.setIndexableSessionAttributes(toArray("one", "two", "three"));
|
|
||||||
|
|
||||||
assertThat(this.indexFactoryBean.getIndexableSessionAttributes()).isEqualTo(
|
|
||||||
toArray("one", "two", "three"));
|
|
||||||
|
|
||||||
assertThat(this.indexFactoryBean.getIndexableSessionAttributesAsGemFireIndexExpression()).isEqualTo(
|
|
||||||
"'one', 'two', 'three'");
|
|
||||||
|
|
||||||
this.indexFactoryBean.setIndexableSessionAttributes(toArray("one"));
|
|
||||||
|
|
||||||
assertThat(this.indexFactoryBean.getIndexableSessionAttributes()).isEqualTo(toArray("one"));
|
|
||||||
assertThat(this.indexFactoryBean.getIndexableSessionAttributesAsGemFireIndexExpression()).isEqualTo("'one'");
|
|
||||||
|
|
||||||
this.indexFactoryBean.setIndexableSessionAttributes(null);
|
|
||||||
|
|
||||||
assertThat(this.indexFactoryBean.getIndexableSessionAttributes()).isEqualTo(
|
|
||||||
GemFireHttpSessionConfiguration.DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
|
|
||||||
|
|
||||||
assertThat(this.indexFactoryBean.getIndexableSessionAttributesAsGemFireIndexExpression()).isEqualTo("*");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setAndGetRegionName() {
|
|
||||||
assertThat(this.indexFactoryBean.getRegionName()).isNull();
|
|
||||||
|
|
||||||
this.indexFactoryBean.setRegionName("Example");
|
|
||||||
|
|
||||||
assertThat(this.indexFactoryBean.getRegionName()).isEqualTo("Example");
|
|
||||||
|
|
||||||
this.indexFactoryBean.setRegionName(null);
|
|
||||||
|
|
||||||
assertThat(this.indexFactoryBean.getRegionName()).isNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.data.gemfire.support;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.apache.geode.cache.Cache;
|
|
||||||
import org.apache.geode.cache.DataPolicy;
|
|
||||||
import org.apache.geode.cache.GemFireCache;
|
|
||||||
import org.apache.geode.cache.Region;
|
|
||||||
import org.apache.geode.cache.RegionAttributes;
|
|
||||||
import org.apache.geode.cache.RegionShortcut;
|
|
||||||
import org.apache.geode.cache.client.ClientCache;
|
|
||||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.mockito.BDDMockito.given;
|
|
||||||
import static org.mockito.BDDMockito.willThrow;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GemFireUtilsTest class is a test suite of test cases testing the contract and
|
|
||||||
* functionality of the GemFireUtils utility class.
|
|
||||||
*
|
|
||||||
* @author John Blum
|
|
||||||
* @since 1.1.0
|
|
||||||
* @see org.junit.Test
|
|
||||||
* @see org.mockito.Mockito
|
|
||||||
* @see org.springframework.session.data.gemfire.support.GemFireUtils
|
|
||||||
*/
|
|
||||||
public class GemFireUtilsTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void closeNonNullCloseableSuccessfullyReturnsTrue() throws IOException {
|
|
||||||
Closeable mockCloseable = mock(Closeable.class);
|
|
||||||
assertThat(GemFireUtils.close(mockCloseable)).isTrue();
|
|
||||||
verify(mockCloseable, times(1)).close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void closeNonNullCloseableObjectThrowingIOExceptionReturnsFalse() throws IOException {
|
|
||||||
Closeable mockCloseable = mock(Closeable.class);
|
|
||||||
willThrow(new IOException("test")).given(mockCloseable).close();
|
|
||||||
assertThat(GemFireUtils.close(mockCloseable)).isFalse();
|
|
||||||
verify(mockCloseable, times(1)).close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void closeNullCloseableObjectReturnsFalse() {
|
|
||||||
assertThat(GemFireUtils.close(null)).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void clientCacheIsClient() {
|
|
||||||
assertThat(GemFireUtils.isClient(mock(ClientCache.class))).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void genericCacheIsNotClient() {
|
|
||||||
assertThat(GemFireUtils.isClient(mock(GemFireCache.class))).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void peerCacheIsNotClient() {
|
|
||||||
assertThat(GemFireUtils.isClient(mock(Cache.class))).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void peerCacheIsPeer() {
|
|
||||||
assertThat(GemFireUtils.isPeer(mock(Cache.class))).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void genericCacheIsNotPeer() {
|
|
||||||
assertThat(GemFireUtils.isPeer(mock(GemFireCache.class))).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void clientCacheIsNotPeer() {
|
|
||||||
assertThat(GemFireUtils.isPeer(mock(ClientCache.class))).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void clientRegionShortcutIsLocal() {
|
|
||||||
assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL)).isTrue();
|
|
||||||
assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL_HEAP_LRU)).isTrue();
|
|
||||||
assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL_OVERFLOW)).isTrue();
|
|
||||||
assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL_PERSISTENT)).isTrue();
|
|
||||||
assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW)).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void clientRegionShortcutIsNotLocal() {
|
|
||||||
assertThat(GemFireUtils.isLocal(ClientRegionShortcut.CACHING_PROXY)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isLocal(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isLocal(ClientRegionShortcut.CACHING_PROXY_OVERFLOW)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isLocal(ClientRegionShortcut.PROXY)).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void clientRegionShortcutIsProxy() {
|
|
||||||
assertThat(GemFireUtils.isProxy(ClientRegionShortcut.PROXY)).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void clientRegionShortcutIsNotProxy() {
|
|
||||||
assertThat(GemFireUtils.isProxy(ClientRegionShortcut.CACHING_PROXY)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(ClientRegionShortcut.CACHING_PROXY_OVERFLOW)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL_HEAP_LRU)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL_OVERFLOW)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL_PERSISTENT)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW)).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void regionShortcutIsProxy() {
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PROXY)).isTrue();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PROXY_REDUNDANT)).isTrue();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_PROXY)).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void regionIsProxy() {
|
|
||||||
Region mockRegion = mock(Region.class);
|
|
||||||
RegionAttributes mockRegionAttributes = mock(RegionAttributes.class);
|
|
||||||
|
|
||||||
given(mockRegion.getAttributes()).willReturn(mockRegionAttributes);
|
|
||||||
given(mockRegionAttributes.getDataPolicy()).willReturn(DataPolicy.EMPTY);
|
|
||||||
|
|
||||||
assertThat(GemFireUtils.isProxy(mockRegion)).isTrue();
|
|
||||||
|
|
||||||
verify(mockRegion, times(1)).getAttributes();
|
|
||||||
verify(mockRegionAttributes, times(1)).getDataPolicy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void regionIsNotProxy() {
|
|
||||||
Region mockRegion = mock(Region.class);
|
|
||||||
RegionAttributes mockRegionAttributes = mock(RegionAttributes.class);
|
|
||||||
|
|
||||||
given(mockRegion.getAttributes()).willReturn(mockRegionAttributes);
|
|
||||||
given(mockRegionAttributes.getDataPolicy()).willReturn(DataPolicy.NORMAL);
|
|
||||||
|
|
||||||
assertThat(GemFireUtils.isProxy(mockRegion)).isFalse();
|
|
||||||
|
|
||||||
verify(mockRegion, times(1)).getAttributes();
|
|
||||||
verify(mockRegionAttributes, times(1)).getDataPolicy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void regionShortcutIsNotProxy() {
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL_HEAP_LRU)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL_OVERFLOW)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL_PERSISTENT)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL_PERSISTENT_OVERFLOW)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_HEAP_LRU)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_OVERFLOW)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_PERSISTENT)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_PERSISTENT_OVERFLOW)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_HEAP_LRU)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_OVERFLOW)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PERSISTENT)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PERSISTENT_OVERFLOW)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_HEAP_LRU)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_OVERFLOW)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT)).isFalse();
|
|
||||||
assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT_OVERFLOW)).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void toRegionPath() {
|
|
||||||
assertThat(GemFireUtils.toRegionPath("A")).isEqualTo("/A");
|
|
||||||
assertThat(GemFireUtils.toRegionPath("Example")).isEqualTo("/Example");
|
|
||||||
assertThat(GemFireUtils.toRegionPath("/Example")).isEqualTo("//Example");
|
|
||||||
assertThat(GemFireUtils.toRegionPath("/")).isEqualTo("//");
|
|
||||||
assertThat(GemFireUtils.toRegionPath("")).isEqualTo("/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
||||||
xmlns:gfe="http://www.springframework.org/schema/gemfire"
|
|
||||||
xmlns:p="http://www.springframework.org/schema/p"
|
|
||||||
xmlns:util="http://www.springframework.org/schema/util"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="
|
|
||||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
|
||||||
http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
|
|
||||||
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
|
|
||||||
">
|
|
||||||
|
|
||||||
<util:properties id="gemfireProperties">
|
|
||||||
<prop key="name">GemFireHttpSessionXmlConfigurationTests</prop>
|
|
||||||
<prop key="mcast-port">0</prop>
|
|
||||||
<prop key="log-level">warning</prop>
|
|
||||||
</util:properties>
|
|
||||||
|
|
||||||
<gfe:cache properties-ref="gemfireProperties"/>
|
|
||||||
|
|
||||||
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
|
|
||||||
p:indexableSessionAttributes="one, two, three" p:maxInactiveIntervalInSeconds="3600"
|
|
||||||
p:serverRegionShortcut="#{T(org.apache.geode.cache.RegionShortcut).LOCAL}"
|
|
||||||
p:springSessionGemFireRegionName="XmlExample"/>
|
|
||||||
|
|
||||||
</beans>
|
|
||||||
Reference in New Issue
Block a user