Add GemFire Support
Fixes GH PR #148 and PR #308 implementing a GemFire Adapter to support clustered HttpSessions in Spring Session. * Resolve SGF-373 - Implement a Spring Session Adapter for GemFire backing a HttpSession similar to the Redis support. * Add Spring Session annotation to enable GemFire support with @EnableGemFireHttpSession. * Add extesion of SpringHttpSessionConfiguration to configure GemFire using GemFireHttpSessionConfiguration. * Add implementation of SessionRepository to access clustered, replicated HttpSession state in GemFire with GemFireOperationsSessionRepository. * Utilize GemFire Data Serialization framework to both replicate HttpSession state information as well as handle deltas. * Utilize GemFire OQL query to lookup arbitrary Session attributes by name, and in particular the user authenticated principal name. * Implment unit and integration tests, and in particular, tests for both peer-to-peer (p2p) and client/server topologies. * Set initial Spring Data GemFire version to 1.7.2.RELEASE, which depends on Pivotal GemFire 8.1.0. * Add documentation, Javadoc and samples along with additional Integration Tests. Fixes gh-148
This commit is contained in:
@@ -16,6 +16,10 @@ liveReload {
|
||||
docRoot asciidoctor.sourceDir.canonicalPath
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url 'http://dist.gemstone.com/maven/release' }
|
||||
}
|
||||
|
||||
asciidoctorj {
|
||||
|
||||
}
|
||||
@@ -25,6 +29,7 @@ tasks.findByPath("artifactoryPublish")?.enabled = false
|
||||
dependencies {
|
||||
testCompile project(':spring-session'),
|
||||
"org.springframework.data:spring-data-redis:$springDataRedisVersion",
|
||||
"org.springframework.data:spring-data-gemfire:$springDataGemFireVersion",
|
||||
"org.springframework:spring-websocket:${springVersion}",
|
||||
"org.springframework:spring-messaging:${springVersion}",
|
||||
"org.springframework.security:spring-security-web:${springSecurityVersion}",
|
||||
@@ -61,4 +66,4 @@ asciidoctor {
|
||||
'idseparator':'-',
|
||||
'docinfo1':'true',
|
||||
'revnumber' : project.version
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
= 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}httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/spring/session-client.xml[tags=beans]
|
||||
----
|
||||
|
||||
<1> First, a `Properties` bean is created to reference GemFire configuration common to both the client and server,
|
||||
stored in the `META-INF/spring/application.properties` file.
|
||||
<2> The `application.properties` are used along with the `PropertySourcesPlaceholderConfigurer` bean to replace
|
||||
placeholders in the Spring XML configuration meta-data with property values.
|
||||
<3> 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.
|
||||
<4> `GemFireHttpSessionConfiguration` is registered to enable Spring Session functionality.
|
||||
<5> Then, a Spring `BeanPostProcessor` is registered to determine whether a GemFire Server at the designated host/port
|
||||
is running, blocking client startup until the server is available.
|
||||
<6> 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 sample application-specific System property, defaulting
|
||||
to `warning` if unspecified.
|
||||
<7> Finally, we create the GemFire client cache and 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.
|
||||
|
||||
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
|
||||
|
||||
Now, we have only covered one side of the equation. We also need a GemFire Server for our client to talk to and pass
|
||||
session state information up to the server to manage.
|
||||
|
||||
In this sample, we will use the following GemFire Server Java Configuration:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-clientserver-xml/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 from `META-INF/spring/application.properties`.
|
||||
<3> We enable the same Spring Session functionality that 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.
|
||||
<4> 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.
|
||||
<5> Then, we create an instance of the GemFire peer cache using our GemFire System properties.
|
||||
<6> And finally, we also setup a GemFire `CacheServer` instance running on *localhost*, listening on port **11235**,
|
||||
to accept our client connection.
|
||||
|
||||
The GemFire Server configuration gets bootstrapped with the following:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-clientserver-xml/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}httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/web.xml[tags=context-param]
|
||||
include::{samples-dir}httpsession-gemfire-clientserver-xml/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}httpsession-gemfire-clientserver-xml/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}httpsession-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.
|
||||
@@ -0,0 +1,274 @@
|
||||
= 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}httpsession-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.
|
||||
In this instance, Spring Session is backed by 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> 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].
|
||||
<4> After configuring a `Pool`, we create an instance of the GemFire client cache using the GemFire `Properties`
|
||||
and `Pool` to communicate with the server and perform cache data access operations.
|
||||
<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. This `BeanPostProcessor` implements 2 approaches to ensure our server has adequate
|
||||
time to startup.
|
||||
|
||||
The first approach uses a timed wait, checking at periodic intervals to determine whether a client `Socket` connection
|
||||
can be made to the server's `CacheServer` endpoint.
|
||||
|
||||
The second approach 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. Either one of these approaches are sufficient
|
||||
by themselves, but both are demonstrated here to illustrate how this might work and to give you ideas, or other options
|
||||
in practice.
|
||||
|
||||
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 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/client/ClientRegionShortcut.html[ClientRegionShortcut]
|
||||
(default is `PROXY`).
|
||||
|
||||
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
|
||||
|
||||
Now, we have only covered one side of the equation. We also need a GemFire Server for our client to talk to and pass
|
||||
session state up to the server to manage.
|
||||
|
||||
In this sample, we will use the following GemFire Server Java Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/ServerConfig.java[tags=class]
|
||||
----
|
||||
|
||||
<1> On the server, we also configure Spring Session using the `@EnableGemFireHttpSession` annotation. For one, this
|
||||
ensures that 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 the GemFire peer cache using our GemFire System properties.
|
||||
<4> We also setup a GemFire `CacheServer` instance running on **localhost**, listening on port **12480**,
|
||||
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}httpsession-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}httpsession-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.
|
||||
210
docs/src/docs/asciidoc/guides/httpsession-gemfire-p2p-xml.adoc
Normal file
210
docs/src/docs/asciidoc/guides/httpsession-gemfire-p2p-xml.adoc
Normal file
@@ -0,0 +1,210 @@
|
||||
= 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}httpsession-gemfire-p2p-xml/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}httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/web.xml[tags=context-param]
|
||||
include::{samples-dir}httpsession-gemfire-p2p-xml/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}httpsession-gemfire-p2p-xml/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}httpsession-gemfire-p2p-xml/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.
|
||||
209
docs/src/docs/asciidoc/guides/httpsession-gemfire-p2p.adoc
Normal file
209
docs/src/docs/asciidoc/guides/httpsession-gemfire-p2p.adoc
Normal file
@@ -0,0 +1,209 @@
|
||||
= 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}httpsession-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}httpsession-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}httpsession-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,6 +52,22 @@ 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 using XML based configuration.
|
||||
| link:guides/httpsession-xml.html[HttpSession XML Guide]
|
||||
|
||||
| {gh-samples-url}httpsession-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/httpsession-gemfire-clientserver.html[HttpSession GemFire Client/Server Guide]
|
||||
|
||||
| {gh-samples-url}httpsession-gemfire-clientserver-xml[HttpSession with GemFire (Client/Server) using XML]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a Client/Server topology configured with XML.
|
||||
| link:guides/httpsession-gemfire-clientserver-xml.html[HttpSession GemFire Client/Server XML Guide]
|
||||
|
||||
| {gh-samples-url}httpsession-gemfire-p2p[HttpSession with GemFire (P2P)]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a P2P topology.
|
||||
| link:guides/httpsession-gemfire-p2p.html[HttpSession GemFire P2P Guide]
|
||||
|
||||
| {gh-samples-url}httpsession-gemfire-p2p-xml[HttpSession with GemFire (P2P) using XML]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a P2P topology configured with XML.
|
||||
| link:guides/httpsession-gemfire-p2p-xml.html[HttpSession GemFire P2P XML Guide]
|
||||
|
||||
| {gh-samples-url}custom-cookie[Custom Cookie]
|
||||
| Demonstrates how to use Spring Session and customize the cookie.
|
||||
| link:guides/custom-cookie.html[Custom Cookie Guide]
|
||||
@@ -136,6 +152,110 @@ You can read the basic steps for integration below, but you are encouraged to fo
|
||||
|
||||
include::guides/httpsession-xml.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/httpsession-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/httpsession-gemfire-clientserver-xml.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/httpsession-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/httpsession-gemfire-p2p-xml.adoc[tags=config,leveloffset=+3]
|
||||
|
||||
[[httpsession-how]]
|
||||
=== How HttpSession Integration Works
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ jspApiVersion=2.0
|
||||
servletApiVersion=3.0.1
|
||||
spockVersion=0.7-groovy-2.0
|
||||
commonsPoolVersion=2.2
|
||||
springDataGemFireVersion=1.7.2.RELEASE
|
||||
springDataRedisVersion=1.4.2.RELEASE
|
||||
hazelcastVersion=3.5.1
|
||||
assertjVersion=2.3.0
|
||||
65
samples/httpsession-gemfire-clientserver-xml/build.gradle
Normal file
65
samples/httpsession-gemfire-clientserver-xml/build.gradle
Normal file
@@ -0,0 +1,65 @@
|
||||
apply from: JAVA_GRADLE
|
||||
apply from: TOMCAT_7_GRADLE
|
||||
apply plugin: "application"
|
||||
|
||||
tasks.findByPath("artifactoryPublish")?.enabled = false
|
||||
|
||||
sonarRunner {
|
||||
skipProject = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-data-gemfire'),
|
||||
"org.springframework:spring-web:$springVersion",
|
||||
jstlDependencies
|
||||
|
||||
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
|
||||
|
||||
testCompile "junit:junit:$junitVersion"
|
||||
|
||||
integrationTestCompile gebDependencies
|
||||
|
||||
integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
|
||||
}
|
||||
|
||||
mainClassName = 'sample.Application'
|
||||
|
||||
def port
|
||||
def process
|
||||
|
||||
task availablePort() << {
|
||||
def serverSocket = new ServerSocket(0)
|
||||
port = serverSocket.localPort
|
||||
serverSocket.close()
|
||||
}
|
||||
|
||||
task runGemFireServer(dependsOn: availablePort) << {
|
||||
println 'STARTING GEMFIRE SERVER...'
|
||||
|
||||
String classpath = sourceSets.main.runtimeClasspath.collect { it }.join(File.pathSeparator)
|
||||
|
||||
String[] commandLine = ['java', '-server', '-ea',
|
||||
"-Dspring.session.data.gemfire.port=$port",
|
||||
"-Dsample.httpsession.gemfire.log-level="
|
||||
+ System.getProperty('sample.httpsession.gemfire.log-level', 'warning'),
|
||||
'-classpath', classpath, 'sample.Application' ]
|
||||
|
||||
//println commandLine
|
||||
|
||||
process = commandLine.execute()
|
||||
process.in.close()
|
||||
process.out.close()
|
||||
process.err.close()
|
||||
}
|
||||
|
||||
integrationTest.doLast {
|
||||
println 'STOPPING GEMFIRE SERVER...'
|
||||
process?.destroyForcibly()
|
||||
}
|
||||
|
||||
integrationTomcatRun {
|
||||
dependsOn runGemFireServer
|
||||
doFirst {
|
||||
System.setProperty("spring.session.data.gemfire.port", "$port");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 geb.spock.*
|
||||
import sample.pages.HomePage;
|
||||
import spock.lang.Stepwise
|
||||
import pages.*
|
||||
|
||||
/**
|
||||
* Tests the CAS sample application using service tickets.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Stepwise
|
||||
class AttributeTests extends GebReportingSpec {
|
||||
def 'first visit no attributes'() {
|
||||
when:
|
||||
to HomePage
|
||||
then:
|
||||
attributes.empty
|
||||
}
|
||||
|
||||
def 'create attribute'() {
|
||||
when:
|
||||
createAttribute('a','b')
|
||||
then:
|
||||
attributes.size() == 1
|
||||
attributes[0].name == 'a'
|
||||
attributes[0].value == 'b'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 geb.*
|
||||
|
||||
/**
|
||||
* The home page
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class HomePage extends Page {
|
||||
static url = ''
|
||||
static at = { assert driver.title == 'Session Attributes'; true}
|
||||
static content = {
|
||||
form { $('form') }
|
||||
submit { $('input[type=submit]') }
|
||||
createAttribute(required:false) { name, value ->
|
||||
form.attributeName = name
|
||||
form.attributeValue = value
|
||||
submit.click(HomePage)
|
||||
}
|
||||
attributes { moduleList AttributeRow, $("table tr").tail() }
|
||||
}
|
||||
}
|
||||
class AttributeRow extends Module {
|
||||
static content = {
|
||||
cell { $("td", it) }
|
||||
name { cell(0).text() }
|
||||
value { cell(1).text() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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;
|
||||
|
||||
// tag::class[]
|
||||
@Configuration // <1>
|
||||
@ImportResource("META-INF/spring/session-server.xml") // <2>
|
||||
public class Application {
|
||||
|
||||
public static void main(final String[] args) {
|
||||
new AnnotationConfigApplicationContext(Application.class).registerShutdownHook();
|
||||
}
|
||||
}
|
||||
// tag::end[]
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.net.Socket;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.data.gemfire.client.PoolFactoryBean;
|
||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.gemstone.gemfire.cache.client.Pool;
|
||||
import com.gemstone.gemfire.management.membership.ClientMembership;
|
||||
import com.gemstone.gemfire.management.membership.ClientMembershipEvent;
|
||||
import com.gemstone.gemfire.management.membership.ClientMembershipListenerAdapter;
|
||||
|
||||
public class GemFireCacheServerReadyBeanPostProcessor implements BeanPostProcessor {
|
||||
|
||||
static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(20);
|
||||
static final long DEFAULT_WAIT_INTERVAL = 500l;
|
||||
|
||||
static final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
static final String DEFAULT_SERVER_HOST = "localhost";
|
||||
|
||||
@Value("${spring.session.data.gemfire.port:${application.gemfire.client-server.port}}")
|
||||
int port;
|
||||
|
||||
// tag::class[]
|
||||
static {
|
||||
ClientMembership.registerClientMembershipListener(new ClientMembershipListenerAdapter() {
|
||||
public void memberJoined(final ClientMembershipEvent event) {
|
||||
if (!event.isClient()) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
@Resource(name = "applicationProperties")
|
||||
private Properties applicationProperties;
|
||||
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof PoolFactoryBean || bean instanceof Pool) {
|
||||
String host = getServerHost(DEFAULT_SERVER_HOST);
|
||||
Assert.isTrue(waitForCacheServerToStart(host, port), String.format(
|
||||
"GemFire Server failed to start [host: '%1$s', port: %2$d]%n", host, port));
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof PoolFactoryBean || bean instanceof Pool) {
|
||||
try {
|
||||
latch.await(DEFAULT_WAIT_DURATION, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
// tag::end[]
|
||||
|
||||
interface Condition {
|
||||
boolean evaluate();
|
||||
}
|
||||
|
||||
String getServerHost(String defaultServerHost) {
|
||||
return applicationProperties.getProperty("application.gemfire.client-server.host", defaultServerHost);
|
||||
}
|
||||
|
||||
boolean waitForCacheServerToStart(String host, int port) {
|
||||
return waitForCacheServerToStart(host, port, DEFAULT_WAIT_DURATION);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
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 {
|
||||
// NOTE: this code is not intended to be an atomic, compound action (a possible race condition);
|
||||
// opening another connection (at the expense of using system resources) after connectivity
|
||||
// has already been established is not detrimental in this use case
|
||||
if (!connected.get()) {
|
||||
socket = new Socket(host, port);
|
||||
connected.set(true);
|
||||
}
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
finally {
|
||||
GemFireUtils.close(socket);
|
||||
}
|
||||
|
||||
return connected.get();
|
||||
}
|
||||
}, duration);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
boolean waitOnCondition(Condition condition) {
|
||||
return waitOnCondition(condition, DEFAULT_WAIT_DURATION);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
boolean waitOnCondition(Condition condition, long duration) {
|
||||
final 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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[]
|
||||
@@ -0,0 +1,3 @@
|
||||
application.gemfire.client-server.host=localhost
|
||||
application.gemfire.client-server.port=11235
|
||||
application.gemfire.client-server.max-connections=50
|
||||
@@ -0,0 +1,46 @@
|
||||
<?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="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
|
||||
p:maxInactiveIntervalInSeconds="30"/>
|
||||
|
||||
<!--4-->
|
||||
<util:properties id="gemfireProperties">
|
||||
<prop key="name">GemFireClientServerHttpSessionXmlSample</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>
|
||||
|
||||
<!--5-->
|
||||
<gfe:cache properties-ref="gemfireProperties"
|
||||
use-bean-factory-locator="false"/>
|
||||
|
||||
<!--6-->
|
||||
<gfe:cache-server auto-startup="true"
|
||||
bind-address="${application.gemfire.client-server.host}"
|
||||
port="${spring.session.data.gemfire.port:${application.gemfire.client-server.port}}"
|
||||
max-connections="${application.gemfire.client-server.max-connections}"/>
|
||||
<!-- end::beans[] -->
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,55 @@
|
||||
<?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-->
|
||||
<util:properties id="applicationProperties"
|
||||
location="classpath:META-INF/spring/application.properties"/>
|
||||
|
||||
<!--2-->
|
||||
<context:property-placeholder properties-ref="applicationProperties"/>
|
||||
|
||||
<!--3-->
|
||||
<context:annotation-config/>
|
||||
|
||||
<!--4-->
|
||||
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
|
||||
p:maxInactiveIntervalInSeconds="30"/>
|
||||
|
||||
<!--5-->
|
||||
<bean class="sample.GemFireCacheServerReadyBeanPostProcessor"/>
|
||||
|
||||
<!--6-->
|
||||
<util:properties id="gemfireProperties">
|
||||
<prop key="log-level">${sample.httpsession.gemfire.log-level:warning}</prop>
|
||||
</util:properties>
|
||||
|
||||
<!--7-->
|
||||
<gfe:pool free-connection-timeout="5000"
|
||||
keep-alive="false"
|
||||
ping-interval="5000"
|
||||
read-timeout="5000"
|
||||
retry-attempts="2"
|
||||
subscription-enabled="true"
|
||||
thread-local-connections="false"
|
||||
max-connections="${application.gemfire.client-server.max-connections}">
|
||||
<gfe:server host="${application.gemfire.client-server.host}"
|
||||
port="${spring.session.data.gemfire.port:${application.gemfire.client-server.port}}"/>
|
||||
</gfe:pool>
|
||||
|
||||
<gfe:client-cache properties-ref="gemfireProperties"
|
||||
use-bean-factory-locator="false"/>
|
||||
<!-- end::beans[] -->
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app version="2.5" 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_2_5.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>
|
||||
</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>
|
||||
5
samples/httpsession-gemfire-clientserver-xml/src/main/webapp/assets/bootstrap.min.css
vendored
Normal file
5
samples/httpsession-gemfire-clientserver-xml/src/main/webapp/assets/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,48 @@
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Session Attributes</title>
|
||||
<link rel="stylesheet" href="assets/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>
|
||||
<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>
|
||||
66
samples/httpsession-gemfire-clientserver/build.gradle
Normal file
66
samples/httpsession-gemfire-clientserver/build.gradle
Normal file
@@ -0,0 +1,66 @@
|
||||
apply from: JAVA_GRADLE
|
||||
apply from: TOMCAT_7_GRADLE
|
||||
apply plugin: "application"
|
||||
|
||||
tasks.findByPath("artifactoryPublish")?.enabled = false
|
||||
|
||||
sonarRunner {
|
||||
skipProject = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-data-gemfire'),
|
||||
"org.springframework:spring-web:$springVersion",
|
||||
jstlDependencies
|
||||
|
||||
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
|
||||
|
||||
testCompile "junit:junit:$junitVersion"
|
||||
|
||||
integrationTestCompile gebDependencies
|
||||
|
||||
integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
|
||||
}
|
||||
|
||||
|
||||
def port
|
||||
def process
|
||||
|
||||
mainClassName = "sample.ServerConfig"
|
||||
|
||||
task availablePort() << {
|
||||
def serverSocket = new ServerSocket(0)
|
||||
port = serverSocket.localPort
|
||||
serverSocket.close()
|
||||
}
|
||||
|
||||
task runGemFireServer(dependsOn: availablePort) << {
|
||||
println 'STARTING GEMFIRE SERVER...'
|
||||
|
||||
String classpath = sourceSets.main.runtimeClasspath.collect { it }.join(File.pathSeparator)
|
||||
|
||||
String[] commandLine = ['java', '-server', '-ea',
|
||||
"-Dspring.session.data.gemfire.port=$port",
|
||||
"-Dsample.httpsession.gemfire.log-level="
|
||||
+ System.getProperty('sample.httpsession.gemfire.log-level', 'warning'),
|
||||
'-classpath', classpath, 'sample.ServerConfig']
|
||||
|
||||
//println commandLine
|
||||
|
||||
process = commandLine.execute()
|
||||
process.in.close()
|
||||
process.out.close()
|
||||
process.err.close()
|
||||
}
|
||||
|
||||
integrationTest.doLast {
|
||||
println 'STOPPING GEMFIRE SERVER...'
|
||||
process?.destroyForcibly()
|
||||
}
|
||||
|
||||
integrationTomcatRun {
|
||||
dependsOn runGemFireServer
|
||||
doFirst {
|
||||
System.setProperty("spring.session.data.gemfire.port", "$port");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 geb.spock.*
|
||||
import sample.pages.HomePage;
|
||||
import spock.lang.Stepwise
|
||||
import pages.*
|
||||
|
||||
/**
|
||||
* Tests the CAS sample application using service tickets.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Stepwise
|
||||
class AttributeTests extends GebReportingSpec {
|
||||
def 'first visit no attributes'() {
|
||||
when:
|
||||
to HomePage
|
||||
then:
|
||||
attributes.empty
|
||||
}
|
||||
|
||||
def 'create attribute'() {
|
||||
when:
|
||||
createAttribute('a','b')
|
||||
then:
|
||||
attributes.size() == 1
|
||||
attributes[0].name == 'a'
|
||||
attributes[0].value == 'b'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 geb.*
|
||||
|
||||
/**
|
||||
* The home page
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class HomePage extends Page {
|
||||
static url = ''
|
||||
static at = { assert driver.title == 'Session Attributes'; true}
|
||||
static content = {
|
||||
form { $('form') }
|
||||
submit { $('input[type=submit]') }
|
||||
createAttribute(required:false) { name, value ->
|
||||
form.attributeName = name
|
||||
form.attributeValue = value
|
||||
submit.click(HomePage)
|
||||
}
|
||||
attributes { moduleList AttributeRow, $("table tr").tail() }
|
||||
}
|
||||
}
|
||||
class AttributeRow extends Module {
|
||||
static content = {
|
||||
cell { $("td", it) }
|
||||
name { cell(0).text() }
|
||||
value { cell(1).text() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.net.Socket;
|
||||
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 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.client.PoolFactoryBean;
|
||||
import org.springframework.data.gemfire.config.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.support.GemFireUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.gemstone.gemfire.cache.client.Pool;
|
||||
import com.gemstone.gemfire.management.membership.ClientMembership;
|
||||
import com.gemstone.gemfire.management.membership.ClientMembershipEvent;
|
||||
import com.gemstone.gemfire.management.membership.ClientMembershipListenerAdapter;
|
||||
|
||||
// tag::class[]
|
||||
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 30) // <1>
|
||||
public class ClientConfig {
|
||||
|
||||
static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(20);
|
||||
static final long DEFAULT_WAIT_INTERVAL = 500l;
|
||||
|
||||
static final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
static {
|
||||
System.setProperty("gemfire.log-level",
|
||||
System.getProperty("sample.httpsession.gemfire.log-level", "warning"));
|
||||
|
||||
ClientMembership.registerClientMembershipListener(
|
||||
new ClientMembershipListenerAdapter() {
|
||||
public void memberJoined(ClientMembershipEvent event) {
|
||||
if (!event.isClient()) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Bean
|
||||
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
||||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Properties gemfireProperties() { // <2>
|
||||
return new Properties();
|
||||
}
|
||||
|
||||
@Bean(name = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME)
|
||||
PoolFactoryBean gemfirePool( // <3>
|
||||
@Value("${spring.session.data.gemfire.port:"+ServerConfig.SERVER_PORT+"}") int port) {
|
||||
|
||||
PoolFactoryBean poolFactory = new PoolFactoryBean();
|
||||
|
||||
poolFactory.setName(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME);
|
||||
poolFactory.setFreeConnectionTimeout(5000); // 5 seconds
|
||||
poolFactory.setKeepAlive(false);
|
||||
poolFactory.setMaxConnections(ServerConfig.MAX_CONNECTIONS);
|
||||
poolFactory.setPingInterval(TimeUnit.SECONDS.toMillis(5));
|
||||
poolFactory.setReadTimeout(2000); // 2 seconds
|
||||
poolFactory.setRetryAttempts(2);
|
||||
poolFactory.setSubscriptionEnabled(true);
|
||||
poolFactory.setThreadLocalConnections(false);
|
||||
|
||||
poolFactory.setServerEndpoints(Collections.singletonList(new ConnectionEndpoint(
|
||||
ServerConfig.SERVER_HOSTNAME, port)));
|
||||
|
||||
return poolFactory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ClientCacheFactoryBean gemfireCache(Pool gemfirePool) { // <4>
|
||||
ClientCacheFactoryBean clientCacheFactory = new ClientCacheFactoryBean();
|
||||
|
||||
clientCacheFactory.setClose(true);
|
||||
clientCacheFactory.setProperties(gemfireProperties());
|
||||
clientCacheFactory.setPool(gemfirePool);
|
||||
clientCacheFactory.setUseBeanFactoryLocator(false);
|
||||
|
||||
return clientCacheFactory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
BeanPostProcessor gemfireCacheServerReadyBeanPostProcessor( // <5>
|
||||
@Value("${spring.session.data.gemfire.port:"+ServerConfig.SERVER_PORT+"}") final int port) {
|
||||
|
||||
return new BeanPostProcessor() {
|
||||
|
||||
public Object postProcessBeforeInitialization(
|
||||
Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof PoolFactoryBean || bean instanceof Pool) {
|
||||
Assert.isTrue(waitForCacheServerToStart(ServerConfig.SERVER_HOSTNAME, port),
|
||||
String.format("GemFire Server failed to start [hostname: %1$s, port: %2$d]",
|
||||
ServerConfig.SERVER_HOSTNAME, port));
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
public Object postProcessAfterInitialization(
|
||||
Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof PoolFactoryBean || bean instanceof Pool) {
|
||||
try {
|
||||
latch.await(DEFAULT_WAIT_DURATION,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
};
|
||||
}
|
||||
// end::class[]
|
||||
|
||||
interface Condition {
|
||||
boolean evaluate();
|
||||
}
|
||||
|
||||
boolean waitForCacheServerToStart(String host, int port) {
|
||||
return waitForCacheServerToStart(host, port, DEFAULT_WAIT_DURATION);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
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 {
|
||||
// NOTE: this code is not intended to be an atomic, compound action (a possible race condition);
|
||||
// opening another connection (at the expense of using system resources) after connectivity
|
||||
// has already been established is not detrimental in this use case
|
||||
if (!connected.get()) {
|
||||
socket = new Socket(host, port);
|
||||
connected.set(true);
|
||||
}
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
finally {
|
||||
GemFireUtils.close(socket);
|
||||
}
|
||||
|
||||
return connected.get();
|
||||
}
|
||||
}, duration);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
boolean waitOnCondition(Condition condition) {
|
||||
return waitOnCondition(condition, DEFAULT_WAIT_DURATION);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
boolean waitOnCondition(Condition condition, long duration) {
|
||||
final 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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[]
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 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;
|
||||
|
||||
import com.gemstone.gemfire.cache.Cache;
|
||||
|
||||
// tag::class[]
|
||||
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 30)// <1>
|
||||
public class ServerConfig {
|
||||
|
||||
static final int MAX_CONNECTIONS = 50;
|
||||
static final int SERVER_PORT = 12480;
|
||||
|
||||
static final String SERVER_HOSTNAME = "localhost";
|
||||
|
||||
@Bean
|
||||
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
||||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Properties gemfireProperties() { // <2>
|
||||
Properties gemfireProperties = new Properties();
|
||||
|
||||
gemfireProperties.setProperty("name", "GemFireClientServerHttpSessionSample");
|
||||
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.setProperties(gemfireProperties());
|
||||
gemfireCache.setUseBeanFactoryLocator(false);
|
||||
|
||||
return gemfireCache;
|
||||
}
|
||||
|
||||
@Bean
|
||||
CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache, // <4>
|
||||
@Value("${spring.session.data.gemfire.port:"+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;
|
||||
}
|
||||
|
||||
public static void main(final String[] args) throws IOException { // <5>
|
||||
new AnnotationConfigApplicationContext(ServerConfig.class)
|
||||
.registerShutdownHook();
|
||||
}
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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[]
|
||||
5
samples/httpsession-gemfire-clientserver/src/main/webapp/assets/bootstrap.min.css
vendored
Normal file
5
samples/httpsession-gemfire-clientserver/src/main/webapp/assets/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,48 @@
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Session Attributes</title>
|
||||
<link rel="stylesheet" href="assets/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>
|
||||
<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>
|
||||
22
samples/httpsession-gemfire-p2p-xml/build.gradle
Normal file
22
samples/httpsession-gemfire-p2p-xml/build.gradle
Normal file
@@ -0,0 +1,22 @@
|
||||
apply from: JAVA_GRADLE
|
||||
apply from: TOMCAT_7_GRADLE
|
||||
|
||||
tasks.findByPath("artifactoryPublish")?.enabled = false
|
||||
|
||||
sonarRunner {
|
||||
skipProject = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-data-gemfire'),
|
||||
"org.springframework:spring-web:$springVersion",
|
||||
jstlDependencies
|
||||
|
||||
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
|
||||
|
||||
testCompile "junit:junit:$junitVersion"
|
||||
|
||||
integrationTestCompile gebDependencies
|
||||
|
||||
integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 geb.spock.*
|
||||
import sample.pages.HomePage;
|
||||
import spock.lang.Stepwise
|
||||
import pages.*
|
||||
|
||||
/**
|
||||
* Tests the CAS sample application using service tickets.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Stepwise
|
||||
class AttributeTests extends GebReportingSpec {
|
||||
def 'first visit no attributes'() {
|
||||
when:
|
||||
to HomePage
|
||||
then:
|
||||
attributes.empty
|
||||
}
|
||||
|
||||
def 'create attribute'() {
|
||||
when:
|
||||
createAttribute('a','b')
|
||||
then:
|
||||
attributes.size() == 1
|
||||
attributes[0].name == 'a'
|
||||
attributes[0].value == 'b'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 geb.*
|
||||
|
||||
/**
|
||||
* The home page
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class HomePage extends Page {
|
||||
static url = ''
|
||||
static at = { assert driver.title == 'Session Attributes'; true}
|
||||
static content = {
|
||||
form { $('form') }
|
||||
submit { $('input[type=submit]') }
|
||||
createAttribute(required:false) { name, value ->
|
||||
form.attributeName = name
|
||||
form.attributeValue = value
|
||||
submit.click(HomePage)
|
||||
}
|
||||
attributes { moduleList AttributeRow, $("table tr").tail() }
|
||||
}
|
||||
}
|
||||
class AttributeRow extends Module {
|
||||
static content = {
|
||||
cell { $("td", it) }
|
||||
name { cell(0).text() }
|
||||
value { cell(1).text() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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[]
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app version="2.5" 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_2_5.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>
|
||||
</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>
|
||||
5
samples/httpsession-gemfire-p2p-xml/src/main/webapp/assets/bootstrap.min.css
vendored
Normal file
5
samples/httpsession-gemfire-p2p-xml/src/main/webapp/assets/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,48 @@
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Session Attributes</title>
|
||||
<link rel="stylesheet" href="assets/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>
|
||||
<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>
|
||||
22
samples/httpsession-gemfire-p2p/build.gradle
Normal file
22
samples/httpsession-gemfire-p2p/build.gradle
Normal file
@@ -0,0 +1,22 @@
|
||||
apply from: JAVA_GRADLE
|
||||
apply from: TOMCAT_7_GRADLE
|
||||
|
||||
tasks.findByPath("artifactoryPublish")?.enabled = false
|
||||
|
||||
sonarRunner {
|
||||
skipProject = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-data-gemfire'),
|
||||
"org.springframework:spring-web:$springVersion",
|
||||
jstlDependencies
|
||||
|
||||
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
|
||||
|
||||
testCompile "junit:junit:$junitVersion"
|
||||
|
||||
integrationTestCompile gebDependencies
|
||||
|
||||
integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 geb.spock.*
|
||||
import sample.pages.HomePage;
|
||||
import spock.lang.Stepwise
|
||||
import pages.*
|
||||
|
||||
/**
|
||||
* Tests the CAS sample application using service tickets.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Stepwise
|
||||
class AttributeTests extends GebReportingSpec {
|
||||
def 'first visit no attributes'() {
|
||||
when:
|
||||
to HomePage
|
||||
then:
|
||||
attributes.empty
|
||||
}
|
||||
|
||||
def 'create attribute'() {
|
||||
when:
|
||||
createAttribute('a','b')
|
||||
then:
|
||||
attributes.size() == 1
|
||||
attributes[0].name == 'a'
|
||||
attributes[0].value == 'b'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 geb.*
|
||||
|
||||
/**
|
||||
* The home page
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class HomePage extends Page {
|
||||
static url = ''
|
||||
static at = { assert driver.title == 'Session Attributes'; true}
|
||||
static content = {
|
||||
form { $('form') }
|
||||
submit { $('input[type=submit]') }
|
||||
createAttribute(required:false) { name, value ->
|
||||
form.attributeName = name
|
||||
form.attributeValue = value
|
||||
submit.click(HomePage)
|
||||
}
|
||||
attributes { moduleList AttributeRow, $("table tr").tail() }
|
||||
}
|
||||
}
|
||||
class AttributeRow extends Module {
|
||||
static content = {
|
||||
cell { $("td", it) }
|
||||
name { cell(0).text() }
|
||||
value { cell(1).text() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.setProperties(gemfireProperties());
|
||||
gemfireCache.setUseBeanFactoryLocator(false);
|
||||
|
||||
return gemfireCache;
|
||||
}
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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[]
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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[]
|
||||
5
samples/httpsession-gemfire-p2p/src/main/webapp/assets/bootstrap.min.css
vendored
Normal file
5
samples/httpsession-gemfire-p2p/src/main/webapp/assets/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
48
samples/httpsession-gemfire-p2p/src/main/webapp/index.jsp
Normal file
48
samples/httpsession-gemfire-p2p/src/main/webapp/index.jsp
Normal file
@@ -0,0 +1,48 @@
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Session Attributes</title>
|
||||
<link rel="stylesheet" href="assets/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>
|
||||
<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>
|
||||
@@ -8,6 +8,10 @@ include 'samples:findbyusername'
|
||||
include 'samples:hazelcast'
|
||||
include 'samples:hazelcast-spring'
|
||||
include 'samples:httpsession'
|
||||
include 'samples:httpsession-gemfire-clientserver'
|
||||
include 'samples:httpsession-gemfire-clientserver-xml'
|
||||
include 'samples:httpsession-gemfire-p2p'
|
||||
include 'samples:httpsession-gemfire-p2p-xml'
|
||||
include 'samples:httpsession-xml'
|
||||
include 'samples:rest'
|
||||
include 'samples:security'
|
||||
@@ -15,4 +19,5 @@ include 'samples:users'
|
||||
include 'samples:websocket'
|
||||
|
||||
include 'spring-session'
|
||||
include 'spring-session-data-gemfire'
|
||||
include 'spring-session-data-redis'
|
||||
|
||||
22
spring-session-data-gemfire/build.gradle
Normal file
22
spring-session-data-gemfire/build.gradle
Normal file
@@ -0,0 +1,22 @@
|
||||
apply from: JAVA_GRADLE
|
||||
apply from: MAVEN_GRADLE
|
||||
apply plugin: 'spring-io'
|
||||
|
||||
description = "Aggregator for Spring Session and Spring Data GemFire"
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session')
|
||||
compile("org.springframework.data:spring-data-gemfire:$springDataGemFireVersion") {
|
||||
exclude group: "org.slf4j", module: 'slf4j-api'
|
||||
exclude group: "org.slf4j", module: 'jcl-over-slf4j'
|
||||
}
|
||||
runtime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
|
||||
}
|
||||
|
||||
dependencyManagement {
|
||||
springIoTestRuntime {
|
||||
imports {
|
||||
mavenBom "io.spring.platform:platform-bom:${springIoVersion}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ configurations {
|
||||
dependencies {
|
||||
optional "org.springframework.data:spring-data-redis:$springDataRedisVersion",
|
||||
"com.hazelcast:hazelcast:$hazelcastVersion",
|
||||
"org.springframework.data:spring-data-gemfire:$springDataGemFireVersion",
|
||||
"org.springframework:spring-context:$springVersion",
|
||||
"org.springframework:spring-web:$springVersion",
|
||||
"org.springframework:spring-messaging:$springVersion",
|
||||
@@ -25,8 +26,11 @@ dependencies {
|
||||
integrationTestCompile "redis.clients:jedis:2.4.1",
|
||||
"org.apache.commons:commons-pool2:2.2"
|
||||
|
||||
integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
|
||||
|
||||
testCompile "junit:junit:$junitVersion",
|
||||
'org.mockito:mockito-core:1.9.5',
|
||||
'org.mockito:mockito-core:1.10.19',
|
||||
"edu.umd.cs.mtc:multithreadedtc:1.01",
|
||||
"org.springframework:spring-test:$springVersion",
|
||||
"org.assertj:assertj-core:$assertjVersion",
|
||||
"org.springframework.security:spring-security-core:$springSecurityVersion"
|
||||
|
||||
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
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.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 com.gemstone.gemfire.cache.Cache;
|
||||
import com.gemstone.gemfire.cache.CacheClosedException;
|
||||
import com.gemstone.gemfire.cache.DataPolicy;
|
||||
import com.gemstone.gemfire.cache.ExpirationAction;
|
||||
import com.gemstone.gemfire.cache.ExpirationAttributes;
|
||||
import com.gemstone.gemfire.cache.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.client.ClientCache;
|
||||
import com.gemstone.gemfire.cache.client.ClientCacheFactory;
|
||||
import com.gemstone.gemfire.cache.server.CacheServer;
|
||||
|
||||
/**
|
||||
* AbstractGemFireIntegrationTests is an abstract base class encapsulating common operations for writing
|
||||
* Spring Session GemFire integration tests.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
* @see org.springframework.session.events.AbstractSessionEvent
|
||||
* @see com.gemstone.gemfire.cache.Cache
|
||||
* @see com.gemstone.gemfire.cache.DataPolicy
|
||||
* @see com.gemstone.gemfire.cache.ExpirationAttributes
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @see com.gemstone.gemfire.cache.client.ClientCache
|
||||
* @see com.gemstone.gemfire.cache.server.CacheServer
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public 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", "warning");
|
||||
|
||||
@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) {
|
||||
final 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) {
|
||||
final 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 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 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) 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 expire(T session) {
|
||||
session.setLastAccessedTime(0l);
|
||||
return session;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends ExpiringSession> T get(String sessionId) {
|
||||
return (T) gemfireSessionRepository.getSession(sessionId);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected <T extends ExpiringSession> T save(T session) {
|
||||
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) {
|
||||
sessionEvent = event;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public <T extends AbstractSessionEvent> T waitForSessionEvent(long duration) {
|
||||
waitOnCondition(new Condition() {
|
||||
public boolean evaluate() {
|
||||
return (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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
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.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.config.GemfireConstants;
|
||||
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.config.annotation.web.http.GemFireHttpSessionConfiguration;
|
||||
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.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 com.gemstone.gemfire.cache.Cache;
|
||||
import com.gemstone.gemfire.cache.DataPolicy;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.RegionAttributes;
|
||||
import com.gemstone.gemfire.cache.client.ClientCache;
|
||||
import com.gemstone.gemfire.cache.client.Pool;
|
||||
|
||||
/**
|
||||
* The ClientServerGemFireOperationsSessionRepositoryIntegrationTests class is a test suite of test cases testing
|
||||
* the functionality of GemFire-backed Spring Sessions using a GemFire client-server topology.
|
||||
*
|
||||
* @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.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
||||
* @see org.springframework.test.context.web.WebAppConfiguration
|
||||
* @see com.gemstone.gemfire.cache.Cache
|
||||
* @see com.gemstone.gemfire.cache.client.ClientCache
|
||||
* @see com.gemstone.gemfire.cache.client.Pool
|
||||
* @see com.gemstone.gemfire.cache.server.CacheServer
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes =
|
||||
ClientServerGemFireOperationsSessionRepositoryIntegrationTests.SpringSessionGemFireClientConfiguration.class)
|
||||
@WebAppConfiguration
|
||||
@SuppressWarnings("unused")
|
||||
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;
|
||||
|
||||
@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 GemFire Server running on [%1$s] listening on port [%2$d]%n",
|
||||
InetAddress.getLocalHost().getHostName(), 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(SpringSessionGemFireServerConfiguration.class, processWorkingDirectory,
|
||||
String.format("-Dspring.session.data.gemfire.port=%1$d", port));
|
||||
|
||||
assertThat(waitForCacheServerToStart(SpringSessionGemFireServerConfiguration.SERVER_HOSTNAME, port)).isTrue();
|
||||
//assertThat(waitForProcessToStart(gemfireServer, processWorkingDirectory)).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(
|
||||
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
||||
|
||||
assertThat(springSessionGemFireRegion).isNotNull();
|
||||
|
||||
RegionAttributes<Object, ExpiringSession> springSessionGemFireRegionAttributes =
|
||||
springSessionGemFireRegion.getAttributes();
|
||||
|
||||
assertThat(springSessionGemFireRegionAttributes).isNotNull();
|
||||
assertThat(springSessionGemFireRegionAttributes.getDataPolicy()).isEqualTo(DataPolicy.EMPTY);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
sessionEventListener.getSessionEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionFiresSessionCreatedEvent() {
|
||||
final long beforeOrAtCreationTime = System.currentTimeMillis();
|
||||
|
||||
ExpiringSession expectedSession = save(createSession());
|
||||
|
||||
AbstractSessionEvent sessionEvent = sessionEventListener.waitForSessionEvent(500);
|
||||
|
||||
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);
|
||||
|
||||
gemfireSessionRepository.delete(expectedSession.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExistingNonExpiredSessionBeforeAndAfterExpiration() {
|
||||
ExpiringSession expectedSession = save(touch(createSession()));
|
||||
|
||||
AbstractSessionEvent sessionEvent = sessionEventListener.waitForSessionEvent(500);
|
||||
|
||||
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
|
||||
assertThat(sessionEvent.<ExpiringSession>getSession()).isEqualTo(expectedSession);
|
||||
assertThat(sessionEventListener.getSessionEvent()).isNull();
|
||||
|
||||
ExpiringSession savedSession = gemfireSessionRepository.getSession(expectedSession.getId());
|
||||
|
||||
assertThat(savedSession).isEqualTo(expectedSession);
|
||||
|
||||
// NOTE for some reason or another, performing a GemFire (Client)Cache Region.get(key)
|
||||
// causes a Region CREATE event... o.O
|
||||
// calling sessionEventListener.getSessionEvent() here to clear the event
|
||||
sessionEventListener.getSessionEvent();
|
||||
|
||||
sessionEvent = sessionEventListener.waitForSessionEvent(TimeUnit.SECONDS.toMillis(
|
||||
MAX_INACTIVE_INTERVAL_IN_SECONDS + 1));
|
||||
|
||||
assertThat(sessionEvent).isInstanceOf(SessionExpiredEvent.class);
|
||||
assertThat(sessionEvent.<ExpiringSession>getSessionId()).isEqualTo(expectedSession.getId());
|
||||
|
||||
ExpiringSession expiredSession = gemfireSessionRepository.getSession(expectedSession.getId());
|
||||
|
||||
assertThat(expiredSession).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteExistingNonExpiredSessionFiresSessionDeletedEventAndReturnsNullOnGet() {
|
||||
ExpiringSession expectedSession = save(touch(createSession()));
|
||||
|
||||
AbstractSessionEvent sessionEvent = sessionEventListener.waitForSessionEvent(500);
|
||||
|
||||
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
|
||||
assertThat(sessionEvent.<ExpiringSession>getSession()).isEqualTo(expectedSession);
|
||||
|
||||
gemfireSessionRepository.delete(expectedSession.getId());
|
||||
|
||||
sessionEvent = sessionEventListener.waitForSessionEvent(500);
|
||||
|
||||
assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class);
|
||||
assertThat(sessionEvent.<ExpiringSession>getSessionId()).isEqualTo(expectedSession.getId());
|
||||
|
||||
ExpiringSession deletedSession = gemfireSessionRepository.getSession(expectedSession.getId());
|
||||
|
||||
assertThat(deletedSession).isNull();
|
||||
}
|
||||
|
||||
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
||||
static class SpringSessionGemFireClientConfiguration {
|
||||
|
||||
//TODO remove when SGF-458 is released
|
||||
static {
|
||||
System.setProperty("gemfire.log-level", GEMFIRE_LOG_LEVEL);
|
||||
}
|
||||
|
||||
@Bean
|
||||
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
||||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Properties gemfireProperties() {
|
||||
Properties gemfireProperties = new Properties();
|
||||
//TODO uncomment when SGF-458 is released.
|
||||
//gemfireProperties.setProperty("name", ClientServerGemFireOperationsSessionRepositoryIntegrationTests.class.getName());
|
||||
//gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
|
||||
return gemfireProperties;
|
||||
}
|
||||
|
||||
@Bean(name = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME)
|
||||
PoolFactoryBean gemfirePool(@Value("${spring.session.data.gemfire.port:"+DEFAULT_GEMFIRE_SERVER_PORT+"}") int port) {
|
||||
PoolFactoryBean poolFactory = new PoolFactoryBean();
|
||||
|
||||
// TODO uncomment when SGF-458 is released
|
||||
//poolFactory.setProperties(gemfireProperties());
|
||||
poolFactory.setName(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME);
|
||||
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(2);
|
||||
poolFactory.setSubscriptionEnabled(true);
|
||||
poolFactory.setThreadLocalConnections(false);
|
||||
|
||||
poolFactory.setServerEndpoints(Collections.singletonList(new ConnectionEndpoint(
|
||||
SpringSessionGemFireServerConfiguration.SERVER_HOSTNAME, port)));
|
||||
|
||||
return poolFactory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ClientCacheFactoryBean gemfireCache(Pool gemfirePool) {
|
||||
ClientCacheFactoryBean clientCacheFactory = new ClientCacheFactoryBean();
|
||||
|
||||
clientCacheFactory.setClose(true);
|
||||
clientCacheFactory.setPool(gemfirePool);
|
||||
clientCacheFactory.setProperties(gemfireProperties());
|
||||
clientCacheFactory.setUseBeanFactoryLocator(false);
|
||||
|
||||
return clientCacheFactory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SessionEventListener sessionEventListener() {
|
||||
return new SessionEventListener();
|
||||
}
|
||||
|
||||
// used for debugging purposes
|
||||
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(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", SpringSessionGemFireServerConfiguration.class.getName());
|
||||
gemfireProperties.setProperty("mcast-port", "0");
|
||||
gemfireProperties.setProperty("log-file", "server.log");
|
||||
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
|
||||
|
||||
return gemfireProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
CacheFactoryBean gemfireCache() {
|
||||
CacheFactoryBean cacheFactory = new CacheFactoryBean();
|
||||
|
||||
cacheFactory.setProperties(gemfireProperties());
|
||||
cacheFactory.setUseBeanFactoryLocator(false);
|
||||
|
||||
return cacheFactory;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
public static void main(final String[] args) throws IOException {
|
||||
new AnnotationConfigApplicationContext(SpringSessionGemFireServerConfiguration.class).registerShutdownHook();
|
||||
writeProcessControlFile(WORKING_DIRECTORY);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
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.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.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.DataPolicy;
|
||||
import com.gemstone.gemfire.cache.ExpirationAction;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.query.Query;
|
||||
import com.gemstone.gemfire.cache.query.QueryService;
|
||||
import com.gemstone.gemfire.cache.query.SelectResults;
|
||||
import com.gemstone.gemfire.pdx.PdxReader;
|
||||
import com.gemstone.gemfire.pdx.PdxSerializable;
|
||||
import com.gemstone.gemfire.pdx.PdxWriter;
|
||||
|
||||
/**
|
||||
* The GemFireOperationsSessionRepositoryIntegrationTests class is a test suite of test cases testing
|
||||
* the findByPrincipalName query method on the GemFireOpeationsSessionRepository class.
|
||||
*
|
||||
* @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.GemFireOperationsSessionRepository
|
||||
* @see org.springframework.test.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
||||
* @see org.springframework.test.context.web.WebAppConfiguration
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
@SuppressWarnings("unused")
|
||||
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_SESSION_GEMFIRE_REGION_NAME = "TestPartitionedSessions";
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
assertThat(gemfireCache).isNotNull();
|
||||
assertThat(gemfireSessionRepository).isNotNull();
|
||||
assertThat(gemfireSessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(
|
||||
MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
|
||||
Region<Object, ExpiringSession> sessionRegion = 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> doFindByPrincipalName(String principalName) {
|
||||
return gemfireSessionRepository.findByPrincipalName(principalName);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Map<String, ExpiringSession> doFindByPrincipalName(String regionName, String principalName) {
|
||||
try {
|
||||
Region<String, ExpiringSession> region = gemfireCache.getRegion(regionName);
|
||||
|
||||
assertThat(region).isNotNull();
|
||||
|
||||
QueryService queryService = region.getRegionService().getQueryService();
|
||||
|
||||
String queryString = String.format("SELECT s FROM %1$s s WHERE s.principalName = $1", 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;
|
||||
}
|
||||
|
||||
@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(get(sessionOne.getId())).isEqualTo(sessionOne);
|
||||
assertThat(get(sessionTwo.getId())).isEqualTo(sessionTwo);
|
||||
assertThat(get(sessionThree.getId())).isEqualTo(sessionThree);
|
||||
assertThat(get(sessionFour.getId())).isEqualTo(sessionFour);
|
||||
assertThat(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 findsNoSessionsByNonExistingPrincipal() {
|
||||
Map<String, ExpiringSession> nonExistingPrincipalSessions = doFindByPrincipalName("nonExistingPrincipalName");
|
||||
|
||||
assertThat(nonExistingPrincipalSessions).isNotNull();
|
||||
assertThat(nonExistingPrincipalSessions.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveAndReadSessionWithAttributes() {
|
||||
ExpiringSession expectedSession = gemfireSessionRepository.createSession();
|
||||
|
||||
assertThat(expectedSession).isInstanceOf(GemFireSession.class);
|
||||
|
||||
((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);
|
||||
|
||||
gemfireSessionRepository.save(touch(expectedSession));
|
||||
|
||||
ExpiringSession savedSession = gemfireSessionRepository.getSession(expectedSession.getId());
|
||||
|
||||
assertThat(savedSession).isEqualTo(expectedSession);
|
||||
assertThat(savedSession).isInstanceOf(GemFireSession.class);
|
||||
assertThat(((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(Boolean.valueOf(String.valueOf(savedSession.getAttribute(expectedAttributeNames.get(0))))).isTrue();
|
||||
assertThat(Double.valueOf(String.valueOf(savedSession.getAttribute(expectedAttributeNames.get(1)))))
|
||||
.isEqualTo(Math.PI);
|
||||
assertThat(String.valueOf(savedSession.getAttribute(expectedAttributeNames.get(2)))).isEqualTo("test");
|
||||
assertThat(savedSession.getAttribute(expectedAttributeNames.get(3))).isEqualTo(jonDoe);
|
||||
}
|
||||
|
||||
@EnableGemFireHttpSession(regionName = SPRING_SESSION_GEMFIRE_REGION_NAME,
|
||||
maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
||||
static class SpringSessionGemFireConfiguration {
|
||||
|
||||
@Bean
|
||||
Properties gemfireProperties() {
|
||||
Properties gemfireProperties = new Properties();
|
||||
|
||||
gemfireProperties.setProperty("name", GemFireOperationsSessionRepositoryIntegrationTests.class.getName());
|
||||
gemfireProperties.setProperty("mcast-port", "0");
|
||||
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
|
||||
|
||||
return gemfireProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
CacheFactoryBean gemfireCache() {
|
||||
CacheFactoryBean gemfireCache = new CacheFactoryBean();
|
||||
|
||||
gemfireCache.setLazyInitialize(false);
|
||||
gemfireCache.setProperties(gemfireProperties());
|
||||
gemfireCache.setUseBeanFactoryLocator(false);
|
||||
|
||||
return gemfireCache;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Person implements 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 firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return 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");
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import com.gemstone.gemfire.cache.DataPolicy;
|
||||
import com.gemstone.gemfire.cache.ExpirationAction;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.RegionShortcut;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @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.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
||||
* @see org.springframework.test.context.web.WebAppConfiguration
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
@SuppressWarnings("unused")
|
||||
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(gemfireCache)).isTrue();
|
||||
assertThat(gemfireSessionRepository).isNotNull();
|
||||
assertThat(gemfireSessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(
|
||||
MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
assertThat(sessionEventListener).isNotNull();
|
||||
|
||||
Region<Object, ExpiringSession> sessionRegion = 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() {
|
||||
sessionEventListener.getSessionEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sessionCreatedEvent() {
|
||||
final long beforeOrAtCreationTime = System.currentTimeMillis();
|
||||
|
||||
ExpiringSession expectedSession = save(createSession());
|
||||
|
||||
AbstractSessionEvent sessionEvent = 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 = gemfireSessionRepository.getSession(expectedSession.getId());
|
||||
|
||||
assertThat(savedSession).isEqualTo(expectedSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExistingExpiredSession() {
|
||||
ExpiringSession expectedSession = save(expire(createSession()));
|
||||
|
||||
AbstractSessionEvent sessionEvent = sessionEventListener.getSessionEvent();
|
||||
|
||||
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
|
||||
|
||||
ExpiringSession createdSession = sessionEvent.getSession();
|
||||
|
||||
assertThat(createdSession).isEqualTo(expectedSession);
|
||||
assertThat(createdSession.isExpired()).isTrue();
|
||||
assertThat(gemfireSessionRepository.getSession(createdSession.getId())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNonExistingSession() {
|
||||
assertThat(gemfireSessionRepository.getSession(UUID.randomUUID().toString())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteExistingNonExpiredSession() {
|
||||
ExpiringSession expectedSession = save(touch(createSession()));
|
||||
ExpiringSession savedSession = gemfireSessionRepository.getSession(expectedSession.getId());
|
||||
|
||||
assertThat(savedSession).isEqualTo(expectedSession);
|
||||
assertThat(savedSession.isExpired()).isFalse();
|
||||
|
||||
gemfireSessionRepository.delete(savedSession.getId());
|
||||
|
||||
AbstractSessionEvent sessionEvent = sessionEventListener.getSessionEvent();
|
||||
|
||||
assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class);
|
||||
assertThat(sessionEvent.getSessionId()).isEqualTo(savedSession.getId());
|
||||
|
||||
ExpiringSession deletedSession = sessionEvent.getSession();
|
||||
|
||||
assertThat(deletedSession).isEqualTo(savedSession);
|
||||
assertThat(gemfireSessionRepository.getSession(deletedSession.getId())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteExistingExpiredSession() {
|
||||
ExpiringSession expectedSession = save(createSession());
|
||||
|
||||
AbstractSessionEvent sessionEvent = sessionEventListener.getSessionEvent();
|
||||
|
||||
assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class);
|
||||
|
||||
ExpiringSession createdSession = sessionEvent.getSession();
|
||||
|
||||
assertThat(createdSession).isEqualTo(expectedSession);
|
||||
|
||||
sessionEvent = sessionEventListener.waitForSessionEvent(TimeUnit.SECONDS.toMillis(
|
||||
gemfireSessionRepository.getMaxInactiveIntervalInSeconds() + 1));
|
||||
|
||||
assertThat(sessionEvent).isInstanceOf(SessionExpiredEvent.class);
|
||||
|
||||
ExpiringSession expiredSession = sessionEvent.getSession();
|
||||
|
||||
assertThat(expiredSession).isEqualTo(createdSession);
|
||||
assertThat(expiredSession.isExpired()).isTrue();
|
||||
|
||||
gemfireSessionRepository.delete(expectedSession.getId());
|
||||
|
||||
sessionEvent = sessionEventListener.getSessionEvent();
|
||||
|
||||
assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class);
|
||||
assertThat(sessionEvent.getSession()).isNull();
|
||||
assertThat(sessionEvent.getSessionId()).isEqualTo(expiredSession.getId());
|
||||
assertThat(gemfireSessionRepository.getSession(sessionEvent.getSessionId())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteNonExistingSession() {
|
||||
String expectedSessionId = UUID.randomUUID().toString();
|
||||
|
||||
assertThat(gemfireSessionRepository.getSession(expectedSessionId)).isNull();
|
||||
|
||||
gemfireSessionRepository.delete(expectedSessionId);
|
||||
|
||||
AbstractSessionEvent sessionEvent = sessionEventListener.getSessionEvent();
|
||||
|
||||
assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class);
|
||||
assertThat(sessionEvent.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.setLazyInitialize(false);
|
||||
gemfireCache.setProperties(gemfireProperties());
|
||||
gemfireCache.setUseBeanFactoryLocator(false);
|
||||
|
||||
return gemfireCache;
|
||||
}
|
||||
|
||||
@Bean
|
||||
SessionEventListener sessionEventListener() {
|
||||
return new SessionEventListener();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
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.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import com.gemstone.gemfire.cache.Cache;
|
||||
import com.gemstone.gemfire.cache.DataPolicy;
|
||||
import com.gemstone.gemfire.cache.ExpirationAction;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
|
||||
/**
|
||||
* The GemFireHttpSessionConfigurationXmlTests class is a test suite of test cases testing the configuration of
|
||||
* Spring Session backed by GemFire using XML configuration meta-data.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
|
||||
* @see org.springframework.test.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.web.WebAppConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
@SuppressWarnings("unused")
|
||||
public class GemFireHttpSessionConfigurationXmlTests extends AbstractGemFireIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private Cache gemfireCache;
|
||||
|
||||
@Test
|
||||
public void gemfireCacheConfigurationIsValid() {
|
||||
assertThat(gemfireCache).isNotNull();
|
||||
|
||||
Region<Object, ExpiringSession> example = gemfireCache.getRegion("Example");
|
||||
|
||||
assertRegion(example, "Example", DataPolicy.NORMAL);
|
||||
assertEntryIdleTimeout(example, ExpirationAction.INVALIDATE, 3600);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,766 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
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.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.session.ExpiringSession;
|
||||
import org.springframework.session.FindByPrincipalNameSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
|
||||
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.StringUtils;
|
||||
|
||||
import com.gemstone.gemfire.DataSerializable;
|
||||
import com.gemstone.gemfire.DataSerializer;
|
||||
import com.gemstone.gemfire.Delta;
|
||||
import com.gemstone.gemfire.Instantiator;
|
||||
import com.gemstone.gemfire.InvalidDeltaException;
|
||||
import com.gemstone.gemfire.cache.EntryEvent;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.util.CacheListenerAdapter;
|
||||
|
||||
/**
|
||||
* AbstractGemFireOperationsSessionRepository is an abstract base class encapsulating functionality common
|
||||
* to all implementations that support SessionRepository operations backed by GemFire.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.beans.factory.InitializingBean
|
||||
* @see org.springframework.context.ApplicationEventPublisher
|
||||
* @see org.springframework.context.ApplicationEventPublisherAware
|
||||
* @see org.springframework.data.gemfire.GemfireAccessor
|
||||
* @see org.springframework.data.gemfire.GemfireOperations
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
* @see org.springframework.session.FindByPrincipalNameSessionRepository
|
||||
* @see org.springframework.session.Session
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @see com.gemstone.gemfire.cache.util.CacheListenerAdapter
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public abstract class AbstractGemFireOperationsSessionRepository extends CacheListenerAdapter<Object, ExpiringSession>
|
||||
implements InitializingBean, FindByPrincipalNameSessionRepository<ExpiringSession>,
|
||||
ApplicationEventPublisherAware {
|
||||
|
||||
private int maxInactiveIntervalInSeconds = GemFireHttpSessionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS;
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher = new ApplicationEventPublisher() {
|
||||
public void publishEvent(ApplicationEvent event) {
|
||||
}
|
||||
};
|
||||
|
||||
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 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 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 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 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 com.gemstone.gemfire.cache.CacheListener}.
|
||||
*
|
||||
* @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();
|
||||
|
||||
fullyQualifiedRegionName = region.getFullPath();
|
||||
region.getAttributesMutator().addCacheListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method triggered when an entry is created in the GemFire cache {@link Region}.
|
||||
*
|
||||
* @param event an EntryEvent containing the details of the cache operation.
|
||||
* @see com.gemstone.gemfire.cache.EntryEvent
|
||||
* @see #handleCreated(String, ExpiringSession)
|
||||
*/
|
||||
@Override
|
||||
public void afterCreate(EntryEvent<Object, ExpiringSession> event) {
|
||||
handleCreated(event.getKey().toString(), 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 com.gemstone.gemfire.cache.EntryEvent
|
||||
* @see #handleDestroyed(String, ExpiringSession)
|
||||
*/
|
||||
@Override
|
||||
public void afterDestroy(EntryEvent<Object, ExpiringSession> event) {
|
||||
handleDestroyed(event.getKey().toString(), 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 com.gemstone.gemfire.cache.EntryEvent
|
||||
* @see #handleExpired(String, ExpiringSession)
|
||||
*/
|
||||
@Override
|
||||
public void afterInvalidate(EntryEvent<Object, ExpiringSession> event) {
|
||||
handleExpired(event.getKey().toString(), event.getOldValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
logger.error(String.format("error occurred publishing event (%1$s)", event), t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GemFireSession is a GemFire representation model of a Spring {@link ExpiringSession} for storing and accessing
|
||||
* Session state information in GemFire. This class implements GemFire's {@link DataSerializable} interface
|
||||
* to better handle replication of Session information across the GemFire cluster.
|
||||
*
|
||||
* @see java.lang.Comparable
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSessionAttributes
|
||||
* @see com.gemstone.gemfire.DataSerializable
|
||||
* @see com.gemstone.gemfire.DataSerializer
|
||||
* @see com.gemstone.gemfire.Delta
|
||||
* @see com.gemstone.gemfire.Instantiator
|
||||
*/
|
||||
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");
|
||||
|
||||
static {
|
||||
Instantiator.register(new Instantiator(GemFireSession.class, 800813552) {
|
||||
@Override public DataSerializable newInstance() {
|
||||
return new GemFireSession();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private transient boolean delta = false;
|
||||
|
||||
private int maxInactiveIntervalInSeconds;
|
||||
|
||||
private long creationTime;
|
||||
private long lastAccessedTime;
|
||||
|
||||
private transient final GemFireSessionAttributes sessionAttributes = new GemFireSessionAttributes(this);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static GemFireSession from(ExpiringSession expiringSession) {
|
||||
GemFireSession session = new GemFireSession(expiringSession);
|
||||
session.setLastAccessedTime(System.currentTimeMillis());
|
||||
return 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 id;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized long getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void setAttribute(String attributeName, Object attributeValue) {
|
||||
sessionAttributes.setAttribute(attributeName, attributeValue);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void removeAttribute(String attributeName) {
|
||||
sessionAttributes.removeAttribute(attributeName);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public <T> T getAttribute(String attributeName) {
|
||||
return sessionAttributes.getAttribute(attributeName);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public Set<String> getAttributeNames() {
|
||||
return sessionAttributes.getAttributeNames();
|
||||
}
|
||||
|
||||
/* (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 lastAccessedTime;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized void setMaxInactiveIntervalInSeconds(final int maxInactiveIntervalInSeconds) {
|
||||
this.delta |= (this.maxInactiveIntervalInSeconds != maxInactiveIntervalInSeconds);
|
||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized int getMaxInactiveIntervalInSeconds() {
|
||||
return maxInactiveIntervalInSeconds;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized void setPrincipalName(String principalName) {
|
||||
setAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME, principalName);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized String getPrincipalName() {
|
||||
return getAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
/* (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(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 {
|
||||
id = in.readUTF();
|
||||
creationTime = in.readLong();
|
||||
setLastAccessedTime(in.readLong());
|
||||
setMaxInactiveIntervalInSeconds(in.readInt());
|
||||
|
||||
int principalNameLength = in.readInt();
|
||||
|
||||
if (principalNameLength > 0) {
|
||||
setPrincipalName(in.readUTF());
|
||||
}
|
||||
|
||||
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 (delta || sessionAttributes.hasDelta());
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized void toDelta(DataOutput out) throws IOException {
|
||||
out.writeLong(getLastAccessedTime());
|
||||
out.writeInt(getMaxInactiveIntervalInSeconds());
|
||||
sessionAttributes.toDelta(out);
|
||||
this.delta = false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public synchronized void fromDelta(DataInput in) throws IOException {
|
||||
setLastAccessedTime(in.readLong());
|
||||
setMaxInactiveIntervalInSeconds(in.readInt());
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The GemFireSessionAttributes class is a container for a Session attributes that implements both
|
||||
* the {@link DataSerializable} and {@link Delta} GemFire interfaces for efficient storage and distribution
|
||||
* (replication) in GemFire.
|
||||
*
|
||||
* @see com.gemstone.gemfire.DataSerializable
|
||||
* @see com.gemstone.gemfire.DataSerializer
|
||||
* @see com.gemstone.gemfire.Delta
|
||||
* @see com.gemstone.gemfire.Instantiator
|
||||
*/
|
||||
public static class GemFireSessionAttributes implements DataSerializable, Delta {
|
||||
|
||||
protected static final boolean DEFAULT_ALLOW_JAVA_SERIALIZATION = true;
|
||||
|
||||
static {
|
||||
Instantiator.register(new Instantiator(GemFireSessionAttributes.class, 800828008) {
|
||||
@Override public DataSerializable newInstance() {
|
||||
return new GemFireSessionAttributes();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 (lock) {
|
||||
if (attributeValue != null) {
|
||||
if (!attributeValue.equals(sessionAttributes.put(attributeName, attributeValue))) {
|
||||
sessionAttributeDeltas.put(attributeName, attributeValue);
|
||||
}
|
||||
}
|
||||
else {
|
||||
removeAttribute(attributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void removeAttribute(String attributeName) {
|
||||
synchronized (lock) {
|
||||
if (sessionAttributes.remove(attributeName) != null) {
|
||||
sessionAttributeDeltas.put(attributeName, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getAttribute(String attributeName) {
|
||||
synchronized (lock) {
|
||||
return (T) sessionAttributes.get(attributeName);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public Set<String> getAttributeNames() {
|
||||
synchronized (lock) {
|
||||
return Collections.unmodifiableSet(new HashSet<String>(sessionAttributes.keySet()));
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected boolean allowJavaSerialization() {
|
||||
return DEFAULT_ALLOW_JAVA_SERIALIZATION;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void from(Session session) {
|
||||
synchronized (lock) {
|
||||
for (String attributeName : session.getAttributeNames()) {
|
||||
setAttribute(attributeName, session.getAttribute(attributeName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void from(GemFireSessionAttributes sessionAttributes) {
|
||||
synchronized (lock) {
|
||||
for (String attributeName : sessionAttributes.getAttributeNames()) {
|
||||
setAttribute(attributeName, sessionAttributes.getAttribute(attributeName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void toData(DataOutput out) throws IOException {
|
||||
synchronized (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 (lock) {
|
||||
for (int count = in.readInt(); count > 0; count--) {
|
||||
setAttribute(in.readUTF(), readObject(in));
|
||||
}
|
||||
|
||||
sessionAttributeDeltas.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
<T> T readObject(DataInput in) throws ClassNotFoundException , IOException {
|
||||
return DataSerializer.readObject(in);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public boolean hasDelta() {
|
||||
synchronized (lock) {
|
||||
return !sessionAttributeDeltas.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void toDelta(DataOutput out) throws IOException {
|
||||
synchronized (lock) {
|
||||
out.writeInt(sessionAttributeDeltas.size());
|
||||
|
||||
for (Map.Entry<String, Object> entry : sessionAttributeDeltas.entrySet()) {
|
||||
out.writeUTF(entry.getKey());
|
||||
writeObject(entry.getValue(), out);
|
||||
}
|
||||
|
||||
sessionAttributeDeltas.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void fromDelta(DataInput in) throws InvalidDeltaException, IOException {
|
||||
synchronized (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());
|
||||
sessionAttributeDeltas.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new InvalidDeltaException("class type in data not found", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return sessionAttributes.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.springframework.data.gemfire.GemfireOperations;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
|
||||
import com.gemstone.gemfire.cache.query.SelectResults;
|
||||
|
||||
/**
|
||||
* The GemFireOperationsSessionRepository class is a Spring SessionRepository implementation that interfaces with
|
||||
* and uses GemFire to back and store Spring Sessions.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.data.gemfire.GemfireOperations
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
* @see org.springframework.session.Session
|
||||
* @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class GemFireOperationsSessionRepository extends AbstractGemFireOperationsSessionRepository {
|
||||
|
||||
// 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 the available Sessions tied to the specific user identified by principal name.
|
||||
*
|
||||
* @param principalName the principal name (i.e. username) to search for all existing Spring Sessions.
|
||||
* @return a mapping of Session ID to Session instances.
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
*/
|
||||
public Map<String, ExpiringSession> findByPrincipalName(String principalName) {
|
||||
SelectResults<ExpiringSession> results = getTemplate().find(String.format(
|
||||
FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY, getFullyQualifiedRegionName()), principalName);
|
||||
|
||||
Map<String, ExpiringSession> sessions = new HashMap<String, ExpiringSession>(results.size());
|
||||
|
||||
for (ExpiringSession session : results.asList()) {
|
||||
sessions.put(session.getId(), session);
|
||||
}
|
||||
|
||||
return sessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link ExpiringSession} instance backed by GemFire.
|
||||
*
|
||||
* @return an instance of {@link ExpiringSession} backed by GemFire.
|
||||
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.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 org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession#from(ExpiringSession)
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
* @see #delete(String)
|
||||
*/
|
||||
public ExpiringSession getSession(String sessionId) {
|
||||
ExpiringSession storedSession = getTemplate().get(sessionId);
|
||||
|
||||
if (storedSession != null) {
|
||||
if (storedSession.isExpired()) {
|
||||
delete(storedSession.getId());
|
||||
}
|
||||
else {
|
||||
return GemFireSession.from(storedSession);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(), new GemFireSession(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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import com.gemstone.gemfire.cache.RegionShortcut;
|
||||
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
|
||||
/**
|
||||
* Add this annotation to an {@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 com.gemstone.gemfire.cache.Cache}
|
||||
* or {@link com.gemstone.gemfire.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 clientCacheFactoryBean = new CacheFactoryBean();
|
||||
* clientCacheFactoryBean.setLazyInitialize(false);
|
||||
* clientCacheFactoryBean.setProperties(gemfireProperties());
|
||||
* clientCacheFactoryBean.setUseBeanFactoryLocator(false);
|
||||
* return clientCacheFactoryBean;
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* Alternatively, a Spring Session can be configured to use Pivotal GemFire (Apache Geode) as a client
|
||||
* using a dedicated GemFire Server cluster and a {@link com.gemstone.gemfire.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 clientCacheFactoryBean = new ClientCacheFactoryBean();
|
||||
* clientCacheFactoryBean.setLazyInitialize(false);
|
||||
* clientCacheFactoryBean.setProperties(gemfireProperties());
|
||||
* clientCacheFactoryBean.setUseBeanFactoryLocator(false);
|
||||
* return clientCacheFactoryBean;
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* More advanced configurations can extend {@link GemFireHttpSessionConfiguration} instead.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
||||
* @see org.springframework.session.config.annotation.web.http.EnableSpringHttpSession
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@SuppressWarnings("unused")
|
||||
@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 com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
*/
|
||||
ClientRegionShortcut clientRegionShortcut() default ClientRegionShortcut.PROXY;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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 com.gemstone.gemfire.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 com.gemstone.gemfire.cache.RegionShortcut
|
||||
*/
|
||||
RegionShortcut serverRegionShortcut() default RegionShortcut.PARTITION;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.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.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.support.GemFireUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.ExpirationAction;
|
||||
import com.gemstone.gemfire.cache.ExpirationAttributes;
|
||||
import com.gemstone.gemfire.cache.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.RegionAttributes;
|
||||
import com.gemstone.gemfire.cache.RegionShortcut;
|
||||
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
|
||||
/**
|
||||
* The GemFireHttpSessionConfiguration class is a Spring @Configuration class used to configure and initialize
|
||||
* Pivotal GemFire (or Apache Geode) as a clustered, replicated HttpSession provider implementation in Spring Session.
|
||||
*
|
||||
* @author John Blum
|
||||
* @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.data.gemfire.IndexFactoryBean
|
||||
* @see org.springframework.data.gemfire.RegionAttributesFactoryBean
|
||||
* @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.support.GemFireCacheTypeAwareRegionFactoryBean
|
||||
* @see com.gemstone.gemfire.cache.ExpirationAttributes
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @see com.gemstone.gemfire.cache.RegionAttributes
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Configuration
|
||||
@SuppressWarnings("unused")
|
||||
public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
implements BeanClassLoaderAware, ImportAware {
|
||||
|
||||
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;
|
||||
|
||||
public static final ClientRegionShortcut DEFAULT_CLIENT_REGION_SHORTCUT = ClientRegionShortcut.PROXY;
|
||||
|
||||
public static final RegionShortcut DEFAULT_SERVER_REGION_SHORTCUT = RegionShortcut.PARTITION;
|
||||
|
||||
public static final String DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME = "ClusteredSpringSessions";
|
||||
|
||||
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 springSessionGemFireRegionName = DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME;
|
||||
|
||||
/**
|
||||
* 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 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 com.gemstone.gemfire.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 com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
*/
|
||||
protected ClientRegionShortcut getClientRegionShortcut() {
|
||||
return (clientRegionShortcut != null ? clientRegionShortcut : DEFAULT_CLIENT_REGION_SHORTCUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected int getMaxInactiveIntervalInSeconds() {
|
||||
return maxInactiveIntervalInSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 com.gemstone.gemfire.cache.RegionShortcut
|
||||
*/
|
||||
public void setServerRegionShortcut(RegionShortcut shortcut) {
|
||||
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 com.gemstone.gemfire.cache.RegionShortcut
|
||||
*/
|
||||
protected RegionShortcut getServerRegionShortcut() {
|
||||
return (serverRegionShortcut != null ? 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 com.gemstone.gemfire.cache.Region#getName()
|
||||
*/
|
||||
protected String getSpringSessionGemFireRegionName() {
|
||||
return (StringUtils.hasText(springSessionGemFireRegionName) ? 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.
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
|
||||
* @see org.springframework.core.type.AnnotationMetadata
|
||||
*/
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
AnnotationAttributes enableGemFireHttpSessionAnnotationAttributes = AnnotationAttributes.fromMap(
|
||||
importMetadata.getAnnotationAttributes(EnableGemFireHttpSession.class.getName()));
|
||||
|
||||
setClientRegionShortcut(ClientRegionShortcut.class.cast(enableGemFireHttpSessionAnnotationAttributes.getEnum(
|
||||
"clientRegionShortcut")));
|
||||
|
||||
setMaxInactiveIntervalInSeconds(enableGemFireHttpSessionAnnotationAttributes.getNumber(
|
||||
"maxInactiveIntervalInSeconds").intValue());
|
||||
|
||||
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.
|
||||
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
|
||||
* @see org.springframework.data.gemfire.GemfireOperations
|
||||
*/
|
||||
@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 com.gemstone.gemfire.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 com.gemstone.gemfire.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 com.gemstone.gemfire.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 org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.cache.RegionAttributes
|
||||
* @see #getClientRegionShortcut()
|
||||
* @see #getSpringSessionGemFireRegionName()
|
||||
* @see #getServerRegionShortcut()
|
||||
*/
|
||||
@Bean(name = DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME)
|
||||
public GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession> sessionRegion(GemFireCache gemfireCache,
|
||||
RegionAttributes<Object, ExpiringSession> sessionRegionAttributes) {
|
||||
|
||||
GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession> serverRegion =
|
||||
new GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession>();
|
||||
|
||||
serverRegion.setGemfireCache(gemfireCache);
|
||||
serverRegion.setClientRegionShortcut(getClientRegionShortcut());
|
||||
serverRegion.setRegionAttributes(sessionRegionAttributes);
|
||||
serverRegion.setRegionName(getSpringSessionGemFireRegionName());
|
||||
serverRegion.setServerRegionShortcut(getServerRegionShortcut());
|
||||
|
||||
return serverRegion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.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 Spring GemFire Index bean on the GemFire cache {@link Region} storing and managing Sessions,
|
||||
* specifically on the 'principalName' property for quick lookup and queries. This index will only be created
|
||||
* on a server @{link Region}.
|
||||
*
|
||||
* @param gemfireCache a reference to the GemFire cache.
|
||||
* @return a IndexFactoryBean creating an GemFire Index on the 'principalName' property of Sessions stored
|
||||
* in the GemFire cache {@link Region}.
|
||||
* @see org.springframework.data.gemfire.IndexFactoryBean
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
*/
|
||||
@Bean
|
||||
@DependsOn(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME)
|
||||
public IndexFactoryBean principalNameIndex(final GemFireCache gemfireCache) {
|
||||
IndexFactoryBean index = new IndexFactoryBean() {
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
if (GemFireUtils.isPeer(gemfireCache)) {
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
index.setCache(gemfireCache);
|
||||
index.setName("principalNameIdx");
|
||||
index.setExpression("principalName");
|
||||
index.setFrom(GemFireUtils.toRegionPath(getSpringSessionGemFireRegionName()));
|
||||
index.setOverride(true);
|
||||
index.setType(IndexType.HASH);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.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;
|
||||
|
||||
import com.gemstone.gemfire.cache.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.InterestResultPolicy;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.RegionAttributes;
|
||||
import com.gemstone.gemfire.cache.RegionShortcut;
|
||||
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
|
||||
/**
|
||||
* The 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.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.beans.factory.FactoryBean
|
||||
* @see org.springframework.beans.factory.InitializingBean
|
||||
* @see org.springframework.data.gemfire.GenericRegionFactoryBean
|
||||
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.cache.InterestResultPolicy
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @see com.gemstone.gemfire.cache.RegionAttributes
|
||||
* @see com.gemstone.gemfire.cache.RegionShortcut
|
||||
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class GemFireCacheTypeAwareRegionFactoryBean<K, V> implements 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_SPRING_SESSION_GEMFIRE_REGION_NAME =
|
||||
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME;
|
||||
|
||||
private ClientRegionShortcut clientRegionShortcut;
|
||||
|
||||
private GemFireCache gemfireCache;
|
||||
|
||||
private Region<K, V> region;
|
||||
|
||||
private RegionAttributes<K, V> regionAttributes;
|
||||
|
||||
private RegionShortcut serverRegionShortcut;
|
||||
|
||||
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();
|
||||
|
||||
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 com.gemstone.gemfire.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 com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.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.setCache(gemfireCache);
|
||||
serverRegion.setAttributes(getRegionAttributes());
|
||||
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 com.gemstone.gemfire.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 com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.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.setCache(gemfireCache);
|
||||
clientRegion.setAttributes(getRegionAttributes());
|
||||
clientRegion.setInterests(registerInterests(!GemFireUtils.isLocal(shortcut)));
|
||||
clientRegion.setRegionName(getRegionName());
|
||||
clientRegion.setShortcut(shortcut);
|
||||
clientRegion.afterPropertiesSet();
|
||||
|
||||
return clientRegion.getObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers interests in all keys when the client {@link Region} is non-local.
|
||||
*
|
||||
* @return an array of Interests specifying the server notifications of interests to the client.
|
||||
* @see org.springframework.data.gemfire.client.Interest
|
||||
*/
|
||||
/**
|
||||
* 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 com.gemstone.gemfire.cache.Region
|
||||
*/
|
||||
public Region<K, V> getObject() throws Exception {
|
||||
return 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 com.gemstone.gemfire.cache.Region
|
||||
* @see java.lang.Class
|
||||
*/
|
||||
public Class<?> getObjectType() {
|
||||
return (region != null ? 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 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 com.gemstone.gemfire.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 com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
*/
|
||||
protected ClientRegionShortcut getClientRegionShortcut() {
|
||||
return (clientRegionShortcut != null ? 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, "The GemFireCache reference 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(gemfireCache != null, "A reference to a GemFireCache was not properly configured");
|
||||
return gemfireCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 com.gemstone.gemfire.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 com.gemstone.gemfire.cache.RegionAttributes
|
||||
*/
|
||||
protected RegionAttributes<K, V> getRegionAttributes() {
|
||||
return 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 com.gemstone.gemfire.cache.Region#getName()
|
||||
*/
|
||||
protected String getRegionName() {
|
||||
return (StringUtils.hasText(regionName) ? 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 com.gemstone.gemfire.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 com.gemstone.gemfire.cache.RegionShortcut
|
||||
*/
|
||||
protected RegionShortcut getServerRegionShortcut() {
|
||||
return (serverRegionShortcut != null ? serverRegionShortcut : DEFAULT_SERVER_REGION_SHORTCUT);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 com.gemstone.gemfire.cache.Cache;
|
||||
import com.gemstone.gemfire.cache.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.RegionShortcut;
|
||||
import com.gemstone.gemfire.cache.client.ClientCache;
|
||||
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
import com.gemstone.gemfire.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.0.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 com.gemstone.gemfire.cache.client.ClientCache
|
||||
* @see com.gemstone.gemfire.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 com.gemstone.gemfire.cache.Cache
|
||||
* @see com.gemstone.gemfire.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 com.gemstone.gemfire.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 com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
*/
|
||||
public static boolean isProxy(ClientRegionShortcut shortcut) {
|
||||
switch (shortcut) {
|
||||
case PROXY:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 com.gemstone.gemfire.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 com.gemstone.gemfire.cache.Region#getFullPath()
|
||||
* @see com.gemstone.gemfire.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
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Matchers.isA;
|
||||
import static org.mockito.Matchers.same;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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.runners.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.events.AbstractSessionEvent;
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
|
||||
import com.gemstone.gemfire.cache.AttributesMutator;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.query.SelectResults;
|
||||
|
||||
/**
|
||||
* The GemFireOperationsSessionRepositoryTest class is a test suite of test cases testing the contract and functionality
|
||||
* of the GemFireOperationsSessionRepository class.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.junit.runner.RunWith
|
||||
* @see org.mockito.Mock
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.mockito.runners.MockitoJUnitRunner
|
||||
* @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@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 Region<Object, ExpiringSession> mockRegion;
|
||||
|
||||
@Mock
|
||||
private GemfireOperationsAccessor mockTemplate;
|
||||
|
||||
private GemFireOperationsSessionRepository sessionRepository;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
when(mockRegion.getAttributesMutator()).thenReturn(mockAttributesMutator);
|
||||
when(mockRegion.getFullPath()).thenReturn("/Example");
|
||||
when(mockTemplate.<Object, ExpiringSession>getRegion()).thenReturn(mockRegion);
|
||||
|
||||
sessionRepository = new GemFireOperationsSessionRepository(mockTemplate);
|
||||
sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher);
|
||||
sessionRepository.setMaxInactiveIntervalInSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
sessionRepository.afterPropertiesSet();
|
||||
|
||||
assertThat(sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher);
|
||||
assertThat(sessionRepository.getFullyQualifiedRegionName()).isEqualTo("/Example");
|
||||
assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
verify(mockAttributesMutator, times(1)).addCacheListener(same(sessionRepository));
|
||||
verify(mockRegion, times(1)).getFullPath();
|
||||
verify(mockTemplate, times(1)).getRegion();
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
when(mockSessionOne.getId()).thenReturn("1");
|
||||
when(mockSessionTwo.getId()).thenReturn("2");
|
||||
when(mockSessionThree.getId()).thenReturn("3");
|
||||
|
||||
SelectResults mockSelectResults = mock(SelectResults.class);
|
||||
|
||||
when(mockSelectResults.asList()).thenReturn(Arrays.asList(mockSessionOne, mockSessionTwo, mockSessionThree));
|
||||
|
||||
String principalName = "jblum";
|
||||
|
||||
String expectedOql = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
|
||||
sessionRepository.getFullyQualifiedRegionName());
|
||||
|
||||
when(mockTemplate.find(eq(expectedOql), eq(principalName))).thenReturn(mockSelectResults);
|
||||
|
||||
Map<String, ExpiringSession> sessions = sessionRepository.findByPrincipalName(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(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 mockSelectResults = mock(SelectResults.class);
|
||||
|
||||
when(mockSelectResults.asList()).thenReturn(Collections.emptyList());
|
||||
|
||||
String principalName = "jblum";
|
||||
|
||||
String expectedOql = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY,
|
||||
sessionRepository.getFullyQualifiedRegionName());
|
||||
|
||||
when(mockTemplate.find(eq(expectedOql), eq(principalName))).thenReturn(mockSelectResults);
|
||||
|
||||
Map<String, ExpiringSession> sessions = sessionRepository.findByPrincipalName(principalName);
|
||||
|
||||
assertThat(sessions).isNotNull();
|
||||
assertThat(sessions.isEmpty()).isTrue();
|
||||
|
||||
verify(mockTemplate, times(1)).find(eq(expectedOql), eq(principalName));
|
||||
verify(mockSelectResults, times(1)).asList();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createProperlyInitializedSession() {
|
||||
final long beforeOrAtCreationTime = System.currentTimeMillis();
|
||||
|
||||
ExpiringSession session = sessionRepository.createSession();
|
||||
|
||||
assertThat(session).isInstanceOf(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);
|
||||
|
||||
when(mockSession.isExpired()).thenReturn(true);
|
||||
when(mockSession.getId()).thenReturn(expectedSessionId);
|
||||
when(mockTemplate.get(eq(expectedSessionId))).thenReturn(mockSession);
|
||||
when(mockTemplate.remove(eq(expectedSessionId))).thenReturn(mockSession);
|
||||
|
||||
doAnswer(new Answer<Void>() {
|
||||
public Void answer(final InvocationOnMock invocation) throws Throwable {
|
||||
ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class);
|
||||
|
||||
assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class);
|
||||
|
||||
AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent;
|
||||
|
||||
assertThat(sessionEvent.getSource()).isSameAs(sessionRepository);
|
||||
assertThat(sessionEvent.getSession()).isSameAs(mockSession);
|
||||
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
|
||||
|
||||
return null;
|
||||
}
|
||||
}).when(mockApplicationEventPublisher).publishEvent(any(ApplicationEvent.class));
|
||||
|
||||
assertThat(sessionRepository.getSession(expectedSessionId)).isNull();
|
||||
|
||||
verify(mockTemplate, times(1)).get(eq(expectedSessionId));
|
||||
verify(mockTemplate, times(1)).remove(eq(expectedSessionId));
|
||||
verify(mockSession, times(1)).isExpired();
|
||||
verify(mockSession, times(2)).getId();
|
||||
verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionDeletedEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionFindsMatchingNonExpiredSessionById() {
|
||||
final String expectedId = "1";
|
||||
|
||||
final long expectedCreationTime = System.currentTimeMillis();
|
||||
final long currentLastAccessedTime = (expectedCreationTime + TimeUnit.MINUTES.toMillis(5));
|
||||
|
||||
ExpiringSession mockSession = mock(ExpiringSession.class);
|
||||
|
||||
when(mockSession.isExpired()).thenReturn(false);
|
||||
when(mockSession.getId()).thenReturn(expectedId);
|
||||
when(mockSession.getCreationTime()).thenReturn(expectedCreationTime);
|
||||
when(mockSession.getLastAccessedTime()).thenReturn(currentLastAccessedTime);
|
||||
when(mockSession.getAttributeNames()).thenReturn(Collections.singleton("attrOne"));
|
||||
when(mockSession.getAttribute(eq("attrOne"))).thenReturn("test");
|
||||
when(mockTemplate.get(eq(expectedId))).thenReturn(mockSession);
|
||||
|
||||
ExpiringSession actualSession = 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.getAttribute("attrOne"))).isEqualTo("test");
|
||||
|
||||
verify(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() {
|
||||
when(mockTemplate.get(anyString())).thenReturn(null);
|
||||
assertThat(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);
|
||||
|
||||
when(mockSession.getId()).thenReturn(expectedSessionId);
|
||||
when(mockSession.getCreationTime()).thenReturn(expectedCreationTime);
|
||||
when(mockSession.getLastAccessedTime()).thenReturn(expectedLastAccessTime);
|
||||
when(mockSession.getMaxInactiveIntervalInSeconds()).thenReturn(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
when(mockSession.getAttributeNames()).thenReturn(Collections.<String>emptySet());
|
||||
|
||||
when(mockTemplate.put(eq(expectedSessionId), isA(GemFireSession.class)))
|
||||
.thenAnswer(new Answer<ExpiringSession>() {
|
||||
public ExpiringSession answer(final InvocationOnMock invocation) throws Throwable {
|
||||
ExpiringSession session = invocation.getArgumentAt(1, ExpiringSession.class);
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
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(mockTemplate, times(1)).put(eq(expectedSessionId), isA(GemFireSession.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteRemovesExistingSessionAndHandlesDelete() {
|
||||
final String expectedSessionId = "1";
|
||||
|
||||
final ExpiringSession mockSession = mock(ExpiringSession.class);
|
||||
|
||||
when(mockSession.getId()).thenReturn(expectedSessionId);
|
||||
when(mockTemplate.remove(eq(expectedSessionId))).thenReturn(mockSession);
|
||||
|
||||
doAnswer(new Answer<Void>() {
|
||||
public Void answer(final InvocationOnMock invocation) throws Throwable {
|
||||
ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class);
|
||||
|
||||
assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class);
|
||||
|
||||
AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent;
|
||||
|
||||
assertThat(sessionEvent.getSource()).isSameAs(sessionRepository);
|
||||
assertThat(sessionEvent.getSession()).isSameAs(mockSession);
|
||||
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
|
||||
|
||||
return null;
|
||||
}
|
||||
}).when(mockApplicationEventPublisher).publishEvent(isA(SessionDeletedEvent.class));
|
||||
|
||||
sessionRepository.delete(expectedSessionId);
|
||||
|
||||
verify(mockSession, times(1)).getId();
|
||||
verify(mockTemplate, times(1)).remove(eq(expectedSessionId));
|
||||
verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionDeletedEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteRemovesNonExistingSessionAndHandlesDelete() {
|
||||
final String expectedSessionId = "1";
|
||||
|
||||
when(mockTemplate.remove(anyString())).thenReturn(null);
|
||||
|
||||
doAnswer(new Answer<Void>() {
|
||||
public Void answer(final InvocationOnMock invocation) throws Throwable {
|
||||
ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class);
|
||||
|
||||
assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class);
|
||||
|
||||
AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent;
|
||||
|
||||
assertThat(sessionEvent.getSource()).isSameAs(sessionRepository);
|
||||
assertThat(sessionEvent.getSession()).isNull();
|
||||
assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId);
|
||||
|
||||
return null;
|
||||
}
|
||||
}).when(mockApplicationEventPublisher).publishEvent(isA(SessionDeletedEvent.class));
|
||||
|
||||
sessionRepository.delete(expectedSessionId);
|
||||
|
||||
verify(mockTemplate, times(1)).remove(eq(expectedSessionId));
|
||||
verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionDeletedEvent.class));
|
||||
}
|
||||
|
||||
protected abstract class GemfireOperationsAccessor extends GemfireAccessor implements GemfireOperations {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
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.session.data.gemfire.GemFireOperationsSessionRepository;
|
||||
|
||||
import com.gemstone.gemfire.cache.Cache;
|
||||
import com.gemstone.gemfire.cache.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.RegionShortcut;
|
||||
import com.gemstone.gemfire.cache.client.ClientCache;
|
||||
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
|
||||
/**
|
||||
* The GemFireHttpSessionConfigurationTest class is a test suite of test cases testing the contract and functionality
|
||||
* of the {@link GemFireHttpSessionConfiguration} class.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration
|
||||
* @see com.gemstone.gemfire.cache.Cache
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @see com.gemstone.gemfire.cache.client.ClientCache
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class GemFireHttpSessionConfigurationTest {
|
||||
|
||||
private GemFireHttpSessionConfiguration gemfireConfiguration;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
gemfireConfiguration = new GemFireHttpSessionConfiguration();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetBeanClassLoader() {
|
||||
assertThat(gemfireConfiguration.getBeanClassLoader()).isNull();
|
||||
|
||||
gemfireConfiguration.setBeanClassLoader(Thread.currentThread().getContextClassLoader());
|
||||
|
||||
assertThat(gemfireConfiguration.getBeanClassLoader()).isEqualTo(Thread.currentThread().getContextClassLoader());
|
||||
|
||||
gemfireConfiguration.setBeanClassLoader(null);
|
||||
|
||||
assertThat(gemfireConfiguration.getBeanClassLoader()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetClientRegionShortcut() {
|
||||
assertThat(gemfireConfiguration.getClientRegionShortcut()).isEqualTo(
|
||||
GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT);
|
||||
|
||||
gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY);
|
||||
|
||||
assertThat(gemfireConfiguration.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY);
|
||||
|
||||
gemfireConfiguration.setClientRegionShortcut(null);
|
||||
|
||||
assertThat(gemfireConfiguration.getClientRegionShortcut()).isEqualTo(
|
||||
GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetMaxInactiveIntervalInSeconds() {
|
||||
assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(
|
||||
GemFireHttpSessionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
|
||||
gemfireConfiguration.setMaxInactiveIntervalInSeconds(300);
|
||||
|
||||
assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(300);
|
||||
|
||||
gemfireConfiguration.setMaxInactiveIntervalInSeconds(Integer.MAX_VALUE);
|
||||
|
||||
assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(Integer.MAX_VALUE);
|
||||
|
||||
gemfireConfiguration.setMaxInactiveIntervalInSeconds(-1);
|
||||
|
||||
assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(-1);
|
||||
|
||||
gemfireConfiguration.setMaxInactiveIntervalInSeconds(Integer.MIN_VALUE);
|
||||
|
||||
assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetServerRegionShortcut() {
|
||||
assertThat(gemfireConfiguration.getServerRegionShortcut()).isEqualTo(
|
||||
GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT);
|
||||
|
||||
gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE_PERSISTENT);
|
||||
|
||||
assertThat(gemfireConfiguration.getServerRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE_PERSISTENT);
|
||||
|
||||
gemfireConfiguration.setServerRegionShortcut(null);
|
||||
|
||||
assertThat(gemfireConfiguration.getServerRegionShortcut()).isEqualTo(
|
||||
GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetSpringSessionGemFireRegionName() {
|
||||
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo(
|
||||
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
||||
|
||||
gemfireConfiguration.setSpringSessionGemFireRegionName("test");
|
||||
|
||||
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("test");
|
||||
|
||||
gemfireConfiguration.setSpringSessionGemFireRegionName(" ");
|
||||
|
||||
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo(
|
||||
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
||||
|
||||
gemfireConfiguration.setSpringSessionGemFireRegionName("");
|
||||
|
||||
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo(
|
||||
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
||||
|
||||
gemfireConfiguration.setSpringSessionGemFireRegionName(null);
|
||||
|
||||
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo(
|
||||
GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setImportMetadata() {
|
||||
AnnotationMetadata mockAnnotationMetadata = mock(AnnotationMetadata.class, "testSetImportMetadata");
|
||||
|
||||
Map<String, Object> annotationAttributes = new HashMap<String, Object>(4);
|
||||
|
||||
annotationAttributes.put("clientRegionShortcut", ClientRegionShortcut.CACHING_PROXY);
|
||||
annotationAttributes.put("maxInactiveIntervalInSeconds", 600);
|
||||
annotationAttributes.put("serverRegionShortcut", RegionShortcut.REPLICATE);
|
||||
annotationAttributes.put("regionName", "TEST");
|
||||
|
||||
when(mockAnnotationMetadata.getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName())))
|
||||
.thenReturn(annotationAttributes);
|
||||
|
||||
gemfireConfiguration.setImportMetadata(mockAnnotationMetadata);
|
||||
|
||||
assertThat(gemfireConfiguration.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY);
|
||||
assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(600);
|
||||
assertThat(gemfireConfiguration.getServerRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE);
|
||||
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("TEST");
|
||||
|
||||
verify(mockAnnotationMetadata, times(1)).getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAndInitializeSpringSessionRepositoryBean() {
|
||||
GemfireOperations mockGemfireOperations = mock(GemfireOperations.class,
|
||||
"testCreateAndInitializeSpringSessionRepositoryBean");
|
||||
|
||||
gemfireConfiguration.setMaxInactiveIntervalInSeconds(120);
|
||||
|
||||
GemFireOperationsSessionRepository sessionRepository = gemfireConfiguration.sessionRepository(
|
||||
mockGemfireOperations);
|
||||
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(sessionRepository.getTemplate()).isSameAs(mockGemfireOperations);
|
||||
assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(120);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void createAndInitializeSpringSessionGemFireRegionTemplate() {
|
||||
GemFireCache mockGemFireCache = mock(GemFireCache.class);
|
||||
Region mockRegion = mock(Region.class);
|
||||
|
||||
when(mockGemFireCache.getRegion(eq("Example"))).thenReturn(mockRegion);
|
||||
|
||||
gemfireConfiguration.setSpringSessionGemFireRegionName("Example");
|
||||
|
||||
GemfireTemplate template = gemfireConfiguration.sessionRegionTemplate(mockGemFireCache);
|
||||
|
||||
assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("Example");
|
||||
assertThat(template).isNotNull();
|
||||
assertThat(template.getRegion()).isSameAs(mockRegion);
|
||||
|
||||
verify(mockGemFireCache, times(1)).getRegion(eq("Example"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expirationIsAllowed() {
|
||||
Cache mockCache = mock(Cache.class, "testExpirationIsAllowed.MockCache");
|
||||
ClientCache mockClientCache = mock(ClientCache.class, "testExpirationIsAllowed.MockClientCache");
|
||||
|
||||
gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.PROXY);
|
||||
gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE);
|
||||
|
||||
assertThat(gemfireConfiguration.isExpirationAllowed(mockCache)).isTrue();
|
||||
|
||||
gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT_OVERFLOW);
|
||||
|
||||
assertThat(gemfireConfiguration.isExpirationAllowed(mockCache)).isTrue();
|
||||
|
||||
gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY);
|
||||
gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION_PROXY);
|
||||
|
||||
assertThat(gemfireConfiguration.isExpirationAllowed(mockClientCache)).isTrue();
|
||||
|
||||
gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW);
|
||||
gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE_PROXY);
|
||||
|
||||
assertThat(gemfireConfiguration.isExpirationAllowed(mockClientCache)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expirationIsNotAllowed() {
|
||||
Cache mockCache = mock(Cache.class, "testExpirationIsAllowed.MockCache");
|
||||
ClientCache mockClientCache = mock(ClientCache.class, "testExpirationIsAllowed.MockClientCache");
|
||||
|
||||
gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.PROXY);
|
||||
gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION);
|
||||
|
||||
assertThat(gemfireConfiguration.isExpirationAllowed(mockClientCache)).isFalse();
|
||||
|
||||
gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.LOCAL);
|
||||
gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION_PROXY);
|
||||
|
||||
assertThat(gemfireConfiguration.isExpirationAllowed(mockCache)).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.data.gemfire.client.Interest;
|
||||
|
||||
import com.gemstone.gemfire.cache.Cache;
|
||||
import com.gemstone.gemfire.cache.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.InterestResultPolicy;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.RegionAttributes;
|
||||
import com.gemstone.gemfire.cache.RegionShortcut;
|
||||
import com.gemstone.gemfire.cache.client.ClientCache;
|
||||
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
|
||||
/**
|
||||
* The GemFireCacheTypeAwareRegionFactoryBeanTest class is a test suite of test cases testing the contract
|
||||
* and functionality of the GemFireCacheTypeAwareRegionFactoryBean class.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Rule
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean
|
||||
* @see com.gemstone.gemfire.cache.Cache
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.cache.InterestResultPolicy
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @see com.gemstone.gemfire.cache.RegionAttributes
|
||||
* @see com.gemstone.gemfire.cache.RegionShortcut
|
||||
* @see com.gemstone.gemfire.cache.client.ClientCache
|
||||
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class GemFireCacheTypeAwareRegionFactoryBeanTest {
|
||||
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
private GemFireCacheTypeAwareRegionFactoryBean regionFactoryBean;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
regionFactoryBean = new GemFireCacheTypeAwareRegionFactoryBean();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void afterPropertiesSetCreatesClientRegionForClientCache() throws Exception {
|
||||
final ClientCache mockClientCache = mock(ClientCache.class);
|
||||
|
||||
final Region mockClientRegion = mock(Region.class, "MockClientRegion");
|
||||
final Region mockServerRegion = mock(Region.class, "MockServerRegion");
|
||||
|
||||
regionFactoryBean = new GemFireCacheTypeAwareRegionFactoryBean() {
|
||||
@Override protected Region newClientRegion(GemFireCache gemfireCache) throws Exception {
|
||||
assertThat(gemfireCache).isSameAs(mockClientCache);
|
||||
return mockClientRegion;
|
||||
}
|
||||
|
||||
@Override protected Region newServerRegion(final GemFireCache gemfireCache) throws Exception {
|
||||
assertThat(gemfireCache).isSameAs(mockClientCache);
|
||||
return mockServerRegion;
|
||||
}
|
||||
};
|
||||
|
||||
regionFactoryBean.setGemfireCache(mockClientCache);
|
||||
regionFactoryBean.afterPropertiesSet();
|
||||
|
||||
assertThat(regionFactoryBean.getGemfireCache()).isSameAs(mockClientCache);
|
||||
assertThat(regionFactoryBean.getObject()).isEqualTo(mockClientRegion);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void afterPropertiesSetCreatesServerRegionForPeerCache() throws Exception {
|
||||
final Cache mockCache = mock(Cache.class);
|
||||
|
||||
final Region mockClientRegion = mock(Region.class, "MockClientRegion");
|
||||
final Region mockServerRegion = mock(Region.class, "MockServerRegion");
|
||||
|
||||
regionFactoryBean = new GemFireCacheTypeAwareRegionFactoryBean() {
|
||||
@Override protected Region newClientRegion(GemFireCache gemfireCache) throws Exception {
|
||||
assertThat(gemfireCache).isSameAs(mockCache);
|
||||
return mockClientRegion;
|
||||
}
|
||||
|
||||
@Override protected Region newServerRegion(final GemFireCache gemfireCache) throws Exception {
|
||||
assertThat(gemfireCache).isSameAs(mockCache);
|
||||
return mockServerRegion;
|
||||
}
|
||||
};
|
||||
|
||||
regionFactoryBean.setGemfireCache(mockCache);
|
||||
regionFactoryBean.afterPropertiesSet();
|
||||
|
||||
assertThat(regionFactoryBean.getGemfireCache()).isSameAs(mockCache);
|
||||
assertThat(regionFactoryBean.getObject()).isEqualTo(mockServerRegion);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allKeysInterestRegistration() {
|
||||
Interest[] interests = 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[] interests = regionFactoryBean.registerInterests(false);
|
||||
|
||||
assertThat(interests).isNotNull();
|
||||
assertThat(interests.length).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void getObjectTypeBeforeInitializationIsRegionClass() {
|
||||
assertThat((Class<Region>) regionFactoryBean.getObjectType()).isEqualTo(Region.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isSingletonIsTrue() {
|
||||
assertThat(regionFactoryBean.isSingleton()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetClientRegionShortcut() {
|
||||
assertThat(regionFactoryBean.getClientRegionShortcut()).isEqualTo(
|
||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_CLIENT_REGION_SHORTCUT);
|
||||
|
||||
regionFactoryBean.setClientRegionShortcut(ClientRegionShortcut.LOCAL_PERSISTENT);
|
||||
|
||||
assertThat(regionFactoryBean.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.LOCAL_PERSISTENT);
|
||||
|
||||
regionFactoryBean.setClientRegionShortcut(null);
|
||||
|
||||
assertThat(regionFactoryBean.getClientRegionShortcut()).isEqualTo(
|
||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_CLIENT_REGION_SHORTCUT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetGemfireCache() {
|
||||
Cache mockCache = mock(Cache.class);
|
||||
|
||||
regionFactoryBean.setGemfireCache(mockCache);
|
||||
|
||||
assertThat(regionFactoryBean.getGemfireCache()).isEqualTo(mockCache);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setGemfireCacheToNullThrowsIllegalArgumentException() {
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
expectedException.expectMessage("The GemFireCache reference must not be null");
|
||||
regionFactoryBean.setGemfireCache(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getGemfireCacheWhenNullThrowsIllegalStateException() {
|
||||
expectedException.expect(IllegalStateException.class);
|
||||
expectedException.expectMessage("A reference to a GemFireCache was not properly configured");
|
||||
regionFactoryBean.getGemfireCache();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setAndGetRegionAttributes() {
|
||||
RegionAttributes mockRegionAttributes = mock(RegionAttributes.class);
|
||||
|
||||
assertThat(regionFactoryBean.getRegionAttributes()).isNull();
|
||||
|
||||
regionFactoryBean.setRegionAttributes(mockRegionAttributes);
|
||||
|
||||
assertThat(regionFactoryBean.getRegionAttributes()).isSameAs(mockRegionAttributes);
|
||||
|
||||
regionFactoryBean.setRegionAttributes(null);
|
||||
|
||||
assertThat(regionFactoryBean.getRegionAttributes()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetRegionName() {
|
||||
assertThat(regionFactoryBean.getRegionName()).isEqualTo(
|
||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
||||
|
||||
regionFactoryBean.setRegionName("Example");
|
||||
|
||||
assertThat(regionFactoryBean.getRegionName()).isEqualTo("Example");
|
||||
|
||||
regionFactoryBean.setRegionName(" ");
|
||||
|
||||
assertThat(regionFactoryBean.getRegionName()).isEqualTo(
|
||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
||||
|
||||
regionFactoryBean.setRegionName("");
|
||||
|
||||
assertThat(regionFactoryBean.getRegionName()).isEqualTo(
|
||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
||||
|
||||
regionFactoryBean.setRegionName(null);
|
||||
|
||||
assertThat(regionFactoryBean.getRegionName()).isEqualTo(
|
||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetServerRegionShortcut() {
|
||||
assertThat(regionFactoryBean.getServerRegionShortcut()).isEqualTo(
|
||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SERVER_REGION_SHORTCUT);
|
||||
|
||||
regionFactoryBean.setServerRegionShortcut(RegionShortcut.LOCAL_PERSISTENT);
|
||||
|
||||
assertThat(regionFactoryBean.getServerRegionShortcut()).isEqualTo(RegionShortcut.LOCAL_PERSISTENT);
|
||||
|
||||
regionFactoryBean.setServerRegionShortcut(null);
|
||||
|
||||
assertThat(regionFactoryBean.getServerRegionShortcut()).isEqualTo(
|
||||
GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SERVER_REGION_SHORTCUT);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.gemstone.gemfire.cache.Cache;
|
||||
import com.gemstone.gemfire.cache.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.RegionShortcut;
|
||||
import com.gemstone.gemfire.cache.client.ClientCache;
|
||||
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
|
||||
/**
|
||||
* The GemFireUtilsTest class is a test suite of test cases testing the contract and functionality of the GemFireUtils
|
||||
* utility class.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.session.data.gemfire.support.GemFireUtils
|
||||
* @since 1.0.0
|
||||
*/
|
||||
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);
|
||||
doThrow(new IOException("test")).when(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 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("/");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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">GemFireHttpSessionConfigurationXmlTests</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:maxInactiveIntervalInSeconds="3600" p:springSessionGemFireRegionName="Example"
|
||||
p:serverRegionShortcut="#{T(com.gemstone.gemfire.cache.RegionShortcut).LOCAL}"/>
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user