Update to Spring Boot 1.4.1.RELEASE

* Upgrade to Spring Data GemFire 1.8.4.RELEASE
 * Upgrade to Spring Data MongoDB 1.9.4.RELEASE
 * Upgrade to Spring Framework 4.3.3.RELEASE

(Upgrade to Spring Data Redis 1.7.4.RELEASE failed)

Fixes gh-632
This commit is contained in:
John Blum
2016-09-17 12:46:32 -07:00
committed by Rob Winch
parent 7d680ff3ef
commit f0820c8038
24 changed files with 642 additions and 625 deletions

View File

@@ -18,7 +18,7 @@ plugins {
group = 'org.springframework.session'
ext.springBootVersion = '1.3.2.RELEASE'
ext.springBootVersion = '1.4.1.RELEASE'
ext.IDE_GRADLE = "$rootDir/gradle/ide.gradle"
ext.JAVA_GRADLE = "$rootDir/gradle/java.gradle"
ext.SPRING3_GRADLE = "$rootDir/gradle/spring3.gradle"

View File

@@ -38,8 +38,8 @@ dependencies {
"org.springframework.security:spring-security-config:${springSecurityVersion}",
"org.springframework.security:spring-security-web:${springSecurityVersion}",
"org.springframework.security:spring-security-test:${springSecurityVersion}",
'junit:junit:4.11',
'org.mockito:mockito-core:1.9.5',
"junit:junit:$junitVersion",
"org.mockito:mockito-core:$mockitoVersion",
"org.springframework:spring-test:$springVersion",
"org.assertj:assertj-core:$assertjVersion",
"com.hazelcast:hazelcast:$hazelcastVersion",

View File

@@ -89,16 +89,17 @@ We start with the _Spring Boot_ application for configuring and bootstrapping a
include::{samples-dir}httpsession-gemfire-boot/src/main/java/sample/server/GemFireServer.java[tags=class]
----
<1> The `@EnableGemFireHttpSession` annotation is used on the GemFire Server mainly to define the corresponding
<1> The `@EnableGemFireHttpSession` annotation is used on the GemFire Server to mainly define the corresponding
Region (e.g. `ClusteredSpringSessions`, the default) in which Session state information will be stored
and managed by GemFire. As well, we have specified an arbitrary expiration attribute (i.e. `maxInactiveIntervalInSeconds`)
for when the Session will timeout, which is triggered by a GemFire Region entry expiration event that also invalidates
the Session object in the Region.
<2> Next, we define a few `Properties` allowing us to configure certain aspects of the GemFire Server using
<2> Next, we define a few `Properties` that allow us to configure certain aspects of the GemFire Server using
http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System properties].
<3> Then, we create an instance of the GemFire Cache using our defined `Properties`.
<4> Finally, we setup a Cache Server instance running in the GemFire Server to listen for connections from cache clients.
The Cache Server `Socket` will be used to connect our GemFire client cache, _Spring Boot_ web application to the server.
<3> Then, we create an instance of the GemFire `Cache` using our defined `Properties`.
<4> Finally, we configure and start a `CacheServer` running in the GemFire Server to listen for connections
from cache clients. The `CacheServer's` `Socket` will be used to connect our GemFire cache client,
_Spring Boot_ web application to the server.
The sample also makes use of a `PropertySourcesPlaceholderConfigurer` bean in order to externalize the sample application
configuration to affect GemFire and application configuration/behavior from the command-line (e.g. such as GemFire's
@@ -106,9 +107,9 @@ configuration to affect GemFire and application configuration/behavior from the
=== Spring Boot-based GemFire cache client Web application
Now, we create the _Spring Boot_ web application (a GemFire cache client), exposing our Web service
using Spring MVC along with _Spring Session_ backed by GemFire, connected to our _Spring Boot_-based GemFire Server,
in order to manage Session state in a cluster/replicated-enabled fashion.
Now, we create our _Spring Boot_ Web application exposing our Web service with Spring MVC, running as a
GemFire cache client connected to our _Spring Boot_-based GemFire Server, using Spring Session backed by GemFire
to manage Session state in a clustered, replicated fashion.
[source,java]
----
@@ -116,28 +117,29 @@ include::{samples-dir}httpsession-gemfire-boot/src/main/java/sample/client/Appli
----
<1> Here, again, we use the `@EnableGemFireHttpSession` annotation to not only configure the GemFire cache client,
but also to override the (HTTP) Web application container's `HttpSession` and replace it with a Session implementation
backed by _Spring Session_ in conjunction with GemFire. Also notice, we did not define any Session expiration timeout
with the `maxInactiveIntervalInSeconds` attribute this time. That is because the Session expiration is managed
by GemFire, which will appropriately notify the cache client when the Session times out. Again, we have just resorted
to using the default Region, `ClusteredSpringSessions`. Of course, we can change the Region name, but we must do so
on both the client and the server. That is a GemFire requirement, not a _Spring Session Data GemFire_ requirement.
<2> Similary to the server configuration, we set a few basic GemFire System `Properties` on the client.
<3> Although, this time, an instance of `ClientCache` is created with the `ClientCacheFactoryBean` from _Spring Data GemFire_.
<4> However, in order to connect to the GemFire Server we must define a GemFire `Pool` bean containing pre-populated
connections to the server. Whenever a client Region entry operation corresponding to a Session update occurs,
but to also override the (HTTP) Web application container's `HttpSession` and replace it with a Session implementation
backed by _Spring Session_ and GemFire. Also notice, we did not define any Session expiration timeout with the
`maxInactiveIntervalInSeconds` attribute this time. That is because the Session expiration is managed by GemFire,
on the server, which will appropriately notify the cache client when the Session times out. Again, we have just
resorted to using the default named Region, `ClusteredSpringSessions`. Of course, we can change the Region name,
but we must do so on both the client and the server. That is a GemFire requirement, not a
_Spring Session Data GemFire_ requirement.
<2> Similarly to the server configuration, we set a few basic GemFire System `Properties` on the client.
<3> Although, this time, an instance of `ClientCache` is created with the `ClientCacheFactoryBean`
from _Spring Data GemFire_.
<4> However, in order to connect to the GemFire Server we must define a GemFire `Pool` bean containing a
pool of connections to the server. Whenever a client Region entry operation corresponding to a Session update occurs,
the client-side Region will use an existing, pooled connection to route the operation to the server.
<5> The following _Spring_ `BeanPostProcessor` (along with some utility methods) are only needed for testing purposes
and is not required by any production code. Specifically, the `BeanPostProcessor` along with the code referenced in *6*
and are not required by any production code. Specifically, the `BeanPostProcessor` along with the code referenced in *6*
is useful in integration test cases where the client and server processes are forked by the test framework. It is pretty
easy to figure out that a race condition is imminent without proper coordination between the client and the server,
therefore, the BPP and `ClientMembershipListener` help sync the interaction between the client and the server
on startup during automated testing.
<6> See <5> above.
<7> Navigates the Web application to the home page (`index.html`), which uses **Thymeleaf** templates for server-side
<6> Navigates the Web application to the home page (`index.html`), which uses **Thymeleaf** templates for server-side
pages.
<8> Heartbeat web service endpoint (useful for manual testing purposes).
<9> Web service endpoint for adding a Session attributes defined by the user using the Web application UI. In addition,
<7> Heartbeat Web service endpoint (useful for manual testing purposes).
<8> Web service endpoint allowing the user to add a Session attribute using the Web application UI. In addition,
the webapp stores an additional Session attribute (`requestCount`) to keep track of how many HTTP requests the user
has sent during the current "session".
@@ -159,11 +161,13 @@ and GemFire out-of-the-box using the following attributes:
* `clientRegionShort` - specifies GemFire's http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
with a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
(default is `PROXY`). This attribute is only used when configuring client Region.
* `poolName` - name of the dedicated GemFire Pool used to connect a client to the cluster of servers. The attribute
is only used when the application is a GemFire cache client. Defaults to `gemfirePool`.
* `serverRegionShort` - specifies GemFire's http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
using a GemFire http://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html[RegionShortcut]
(default is `PARTITION`). This attribute is only used when configuring server Regions, or when a p2p topology is employed.
NOTE: It is important to note that the GemFire client Region name must match a server Region by the same name if
NOTE: It is important to remember that the GemFire client Region name must match a server Region by the same name if
the client Region is a `PROXY` or `CACHING_PROXY`. Client and server Region names are not required to match if
the client Region used to store Spring Sessions is `LOCAL`. However, keep in mind that your session state will not
be propagated to the server and you lose all the benefits of using GemFire to store and manage distributed, replicated

View File

@@ -83,23 +83,23 @@ Add the following Spring Configuration:
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
<1> Spring annotation configuration support is enabled with `<context:annotation-config/>` element so that any
Spring beans declared in the XML config that are annotated with either Spring or Standard Java annotations supported
by Spring will be configured appropriately.
<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
<2> The `META-INF/spring/application.properties` file are used along with the `PropertySourcesPlaceholderConfigurer`
bean to replace placeholders in the Spring XML configuration meta-data with the approrpriate property values.
<3> Then the `GemFireCacheSeverReadyBeanPostProcessor`is registered to determine whether a GemFire Server
at the designated host/port is running and listening for client connections, blocking client startup until
the server is available and ready.
<4> Next, we include a `Properties` bean to configure certain aspects of the GemFire client cache using
http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System Properties].
In this case, we are just setting GemFire's `log-level` from a application-specific System property, defaulting
to `warning` if unspecified.
<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.
<5> Then we create a instance of a GemFire `ClientCache` initialized with our `gemfireProperties`.
<6> We configure a Pool of client connections to talk to the GemFire Server in our Client/Server topology.
In our configuration, we use sensible settings for timeouts, number of connections and so on. Also, our `Pool`
has been configured to connect directly to a server.
<7> Finally, the `GemFireHttpSessionConfiguration` is registered to enable Spring Session functionality.
TIP: In typical GemFire deployments, where the cluster includes potentially hundreds of GemFire data nodes (servers),
it is more common for clients to connect to one or more GemFire Locators running in the cluster. A Locator passes meta-data
@@ -110,8 +110,8 @@ NOTE: For more information on configuring _Spring Data GemFire_, refer to the ht
=== 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.
We have only covered one side of the equation. We also need a GemFire Server for our client to talk to and send
session state information to the server to manage.
In this sample, we will use the following GemFire Server Java Configuration:
@@ -124,15 +124,16 @@ include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/resources/ME
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.
meta-data with property values in the `META-INF/spring/application.properties` file.
<3> Next, we configure the GemFire Server using GemFire System Properties very much like our P2P samples.
With the `mcast-port` set to 0 and no `locators` property specified, our server will be standalone. We also allow a
JMX client (e.g. _Gfsh_) to connect to our server with the use of the GemFire-specific JMX System properties.
<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.
<4> Then we create an instance of a GemFire peer `Cache` initialized with our GemFire System Properties.
<5> We also setup a GemFire `CacheServer` instance running on *localhost*, listening to port **11235**,
ready to accept our client connection.
<6> Finally, we enable the same Spring Session functionality we used on the client by registering an instance of
`GemFireHttpSessionConfiguration`, except that we set the session expiration timeout to **30 seconds**.
We will explain later what this means.
The GemFire Server configuration gets bootstrapped with the following:

View File

@@ -84,33 +84,26 @@ include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/Clie
----
<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.
implements `Filter`. The filter is what replaces the `HttpSession` with an implementation backed by Spring Session
and GemFire.
<2> Next, we register a `Properties` bean that allows us to configure certain aspects of the GemFire client cache
using http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System properties].
<3> Then, we configure a `Pool` of client connections to talk to the GemFire Server in our Client/Server topology. In our
<3> We use the `Properties` to configure an instance of a GemFire `ClientCache`.
<4> Then, we configure a `Pool` of client connections to talk to the GemFire Server in our Client/Server topology. In our
configuration, we have used sensible settings for timeouts, number of connections and so on. Also, the `Pool` has been
configured to connect directly to a server. Learn more about various `Pool` configuration settings from the
http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/PoolFactory.html[PoolFactory API].
<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,
<56> 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.
running, such as in production.
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]
The `BeanPostProcessor` uses a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/management/membership/ClientMembershipListener.html[ClientMembershipListener]
that will be notified when the client has successfully connected to the server. Once a connection has been established,
the listener releases the latch that the `BeanPostProcessor` will wait on (up to the specified timeout) in the
`postProcessAfterInitialization` callback to block the client. 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.
`postProcessAfterInitialization` callback to block the client.
TIP: In typical GemFire deployments, where the cluster includes potentially hundreds of GemFire data nodes (servers),
it is more common for clients to connect to one or more GemFire Locators running in the cluster. A Locator passes meta-data
@@ -122,11 +115,16 @@ NOTE: For more information on configuring _Spring Data GemFire_, refer to the ht
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]
* `maxInactiveIntervalInSeconds` - controls _HttpSession_ idle-timeout expiration (defaults to **30 minutes**).
* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "*ClusteredSpringSessions*").
* `clientRegionShort` - specifies GemFire's http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
with a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
(default is `PROXY`).
(default is `PROXY`). This attribute is only used when configuring client Region.
* `poolName` - name of the dedicated GemFire Pool used to connect a client to the cluster of servers. The attribute
is only used when the application is a GemFire cache client. Defaults to `gemfirePool`.
* `serverRegionShort` - specifies GemFire's http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
using a GemFire http://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html[RegionShortcut]
(default is `PARTITION`). This attribute is only used when configuring server Regions, or when a p2p topology is employed.
NOTE: It is important to note that the GemFire client Region name must match a server Region by the same name if
the client Region is a `PROXY` or `CACHING_PROXY`. Names are not required to match if the client Region used to
@@ -134,13 +132,13 @@ store Spring Sessions is `LOCAL`, however, keep in mind that your session state
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.
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.
We have only covered one side of the equation. We also need a GemFire Server for our client to talk to and send
session state to the server to manage.
In this sample, we will use the following GemFire Server Java Configuration:
@@ -149,15 +147,15 @@ In this sample, we will use the following GemFire Server Java Configuration:
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_").
<1> On the server, we also configure Spring Session using the `@EnableGemFireHttpSession` annotation. This ensures
the Region names on both the client and server match (in this sample, we use the default "_ClusteredSpringSessions_").
We have also set the session timeout to **30 seconds**. Later, we will see how this timeout is used.
<2> Next, we configure the GemFire Server using GemFire System properties very much like our P2P samples.
<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.
<3> Then, we create an instance of a GemFire peer `Cache` initialized with our GemFire System Properties.
<4> We also setup a GemFire `CacheServer` instance running on **localhost**, listening to port **12480**,
ready to accept our client connection.
<5> Finally, we declare a `main` method as an entry point for launching and running our GemFire Server
from the command-line.

View File

@@ -5,6 +5,8 @@ jspApiVersion=2.0
servletApiVersion=3.0.1
jstlelVersion=1.2.5
version=1.3.0.BUILD-SNAPSHOT
# TODO: upgrade to SD Redis 1.7.4.RELEASE - spring3Test suite Redis tests fail due to upgrade...
# Caused by: java.lang.NoSuchMethodError: org.springframework.core.serializer.support.DeserializingConverter.<init>(Ljava/lang/ClassLoader;)V
springDataRedisVersion=1.7.1.RELEASE
html5ShivVersion=3.7.3
commonsLoggingVersion=1.2
@@ -15,13 +17,13 @@ hazelcastVersion=3.6.5
springDataGeodeVersion=1.0.0.APACHE-GEODE-INCUBATING-M2
seleniumVersion=2.52.0
springSecurityVersion=4.2.0.RELEASE
springVersion=4.2.5.RELEASE
springVersion=4.3.3.RELEASE
httpClientVersion=4.5.1
jedisVersion=2.8.1
h2Version=1.4.192
springDataMongoVersion=1.9.1.RELEASE
springDataMongoVersion=1.9.4.RELEASE
springShellVersion=1.1.0.RELEASE
springDataGemFireVersion=1.8.1.RELEASE
springDataGemFireVersion=1.8.4.RELEASE
assertjVersion=2.5.0
spockVersion=1.0-groovy-2.4
webjarsTaglibVersion=0.3

View File

@@ -16,8 +16,6 @@
package sample.client;
import java.io.IOException;
import java.net.Socket;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
@@ -26,7 +24,6 @@ import java.util.Map;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.http.HttpSession;
@@ -34,8 +31,6 @@ 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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
@@ -47,8 +42,8 @@ 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.support.ConnectionEndpoint;
import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
import org.springframework.session.data.gemfire.support.GemFireUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
@@ -59,16 +54,14 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* A Spring Boot-based GemFire cache client web application that reveals the current state
* of the HTTP Session.
* A Spring Boot, GemFire cache client, web application that reveals the current state of the HTTP Session.
*
* @author John Blum
* @see javax.servlet.http.HttpSession
* @see org.springframework.boot.SpringApplication
* @see org.springframework.boot.autoconfigure.SpringBootApplication
* @see org.springframework.context.annotation.Bean
* @see org.springframework.session.data.gemfire.config.annotation.web.http.
* EnableGemFireHttpSession
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
* @see org.springframework.stereotype.Controller
* @see com.gemstone.gemfire.cache.client.ClientCache
* @since 1.2.1
@@ -79,63 +72,42 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class Application {
static final int MAX_CONNECTIONS = 50;
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_GEMFIRE_LOG_LEVEL = "config";
static final String DEFAULT_GEMFIRE_LOG_LEVEL = "warning";
static final String INDEX_TEMPLATE_VIEW_NAME = "index";
static final String PING_RESPONSE = "PONG";
static final String REQUEST_COUNT_ATTRIBUTE_NAME = "requestCount";
static { // <6>
ClientMembership
.registerClientMembershipListener(new ClientMembershipListenerAdapter() {
@Override
public void memberJoined(ClientMembershipEvent event) {
if (!event.isClient()) {
latch.countDown();
}
}
});
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
protected final Logger logger = LoggerFactory.getLogger(getClass());
@Bean
static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
String applicationName() {
return "samples:httpsession-gemfire-boot-"
.concat(Application.class.getSimpleName());
}
String gemfireLogLevel() {
return System.getProperty("gemfire.log-level", DEFAULT_GEMFIRE_LOG_LEVEL);
}
ConnectionEndpoint newConnectionEndpoint(String host, int port) {
return new ConnectionEndpoint(host, port);
}
Properties gemfireProperties() { // <2>
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", applicationName());
gemfireProperties.setProperty("log-level", gemfireLogLevel());
gemfireProperties.setProperty("log-level", logLevel());
return gemfireProperties;
}
String applicationName() {
return "samples:httpsession-gemfire-boot:"
.concat(getClass().getSimpleName());
}
String logLevel() {
return System.getProperty("gemfire.log-level", DEFAULT_GEMFIRE_LOG_LEVEL);
}
@Bean
ClientCacheFactoryBean gemfireCache() { // <3>
ClientCacheFactoryBean gemfireCache = new ClientCacheFactoryBean();
@@ -153,18 +125,23 @@ public class Application {
PoolFactoryBean gemfirePool = new PoolFactoryBean();
gemfirePool.setMaxConnections(MAX_CONNECTIONS);
gemfirePool.setPingInterval(TimeUnit.SECONDS.toMillis(15));
gemfirePool.setKeepAlive(false);
gemfirePool.setPingInterval(TimeUnit.SECONDS.toMillis(5));
gemfirePool.setReadTimeout(Long.valueOf(TimeUnit.SECONDS.toMillis(2)).intValue());
gemfirePool.setRetryAttempts(1);
gemfirePool.setSubscriptionEnabled(true);
gemfirePool.setServerEndpoints(
Collections.singleton(newConnectionEndpoint(host, port)));
gemfirePool.setThreadLocalConnections(false);
gemfirePool.setServers(Collections.singleton(newConnectionEndpoint(host, port)));
return gemfirePool;
}
ConnectionEndpoint newConnectionEndpoint(String host, int port) {
return new ConnectionEndpoint(host, port);
}
@Bean
BeanPostProcessor gemfireCacheServerAvailabilityBeanPostProcessor(
BeanPostProcessor gemfireCacheServerReadyBeanPostProcessor(
@Value("${gemfire.cache.server.host:localhost}") final String host,
@Value("${gemfire.cache.server.port:12480}") final int port) { // <5>
@@ -172,12 +149,15 @@ public class Application {
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof PoolFactoryBean || bean instanceof Pool) {
if (!waitForCacheServerToStart(host, port)) {
Application.this.logger.warn(
"No GemFire Cache Server found on [host: {}, port: {}]",
host, port);
}
if ("gemfirePool".equals(beanName)) {
ClientMembership.registerClientMembershipListener(
new ClientMembershipListenerAdapter() {
@Override
public void memberJoined(ClientMembershipEvent event) {
latch.countDown();
}
});
}
return bean;
@@ -185,13 +165,12 @@ public class Application {
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof PoolFactoryBean || bean instanceof Pool) {
if (bean instanceof Pool && "gemfirePool".equals(beanName)) {
try {
Assert.state(
latch.await(DEFAULT_WAIT_DURATION, TimeUnit.MILLISECONDS),
String.format(
"GemFire Cache Server failed to start on [host: %1$s, port: %2$d]",
host, port));
Assert.state(latch.await(DEFAULT_WAIT_DURATION, TimeUnit.MILLISECONDS),
String.format("GemFire Cache Server failed to start on host [%1$s] and port [%2$d]",
host, port));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
@@ -204,23 +183,23 @@ public class Application {
}
@RequestMapping("/")
public String index() { // <7>
public String index() { // <6>
return INDEX_TEMPLATE_VIEW_NAME;
}
@RequestMapping(method = RequestMethod.GET, path = "/ping")
@ResponseBody
public String ping() { // <8>
public String ping() { // <7>
return PING_RESPONSE;
}
@RequestMapping(method = RequestMethod.POST, path = "/session")
public String session(HttpSession session, ModelMap modelMap,
@RequestParam(name = "attributeName", required = false) String name,
@RequestParam(name = "attributeValue", required = false) String value) { // <9>
@RequestParam(name = "attributeValue", required = false) String value) { // <8>
modelMap.addAttribute("sessionAttributes",
attributes(setAttribute(updateRequestCount(session), name, value)));
attributes(setAttribute(updateRequestCount(session), name, value)));
return INDEX_TEMPLATE_VIEW_NAME;
}
@@ -230,27 +209,24 @@ public class Application {
@SuppressWarnings("all")
HttpSession updateRequestCount(HttpSession session) {
synchronized (session) {
Integer currentRequestCount = (Integer) session
.getAttribute(REQUEST_COUNT_ATTRIBUTE_NAME);
session.setAttribute(REQUEST_COUNT_ATTRIBUTE_NAME,
nullSafeIncrement(currentRequestCount));
Integer currentRequestCount = (Integer) session.getAttribute(REQUEST_COUNT_ATTRIBUTE_NAME);
session.setAttribute(REQUEST_COUNT_ATTRIBUTE_NAME, nullSafeIncrement(currentRequestCount));
return session;
}
}
/* (non-Javadoc) */
Integer nullSafeIncrement(Integer value) {
return (nullSafeInt(value) + 1);
return (nullSafeIntValue(value) + 1);
}
/* (non-Javadoc) */
int nullSafeInt(Number value) {
int nullSafeIntValue(Number value) {
return (value != null ? value.intValue() : 0);
}
/* (non-Javadoc) */
HttpSession setAttribute(HttpSession session, String attributeName,
String attributeValue) {
HttpSession setAttribute(HttpSession session, String attributeName, String attributeValue) {
if (isSet(attributeName, attributeValue)) {
session.setAttribute(attributeName, attributeValue);
}
@@ -269,97 +245,25 @@ public class Application {
return set;
}
/* (non-Javadoc) */
Map<String, String> attributes(HttpSession session) {
Map<String, String> sessionAttributes = new HashMap<String, String>();
for (String attributeName : toIterable(session.getAttributeNames())) {
sessionAttributes.put(attributeName,
String.valueOf(session.getAttribute(attributeName)));
String.valueOf(session.getAttribute(attributeName)));
}
return sessionAttributes;
}
/* (non-Javadoc) */
<T> Iterable<T> toIterable(final Enumeration<T> enumeration) {
return new Iterable<T>() {
public Iterator<T> iterator() {
return (enumeration == null ? Collections.<T>emptyIterator()
: new Iterator<T>() {
public boolean hasNext() {
return enumeration.hasMoreElements();
}
public T next() {
return enumeration.nextElement();
}
public void remove() {
throw new UnsupportedOperationException(
"Auto-generated method stub");
}
});
: CollectionUtils.toIterator(enumeration));
}
};
}
/* (non-Javadoc) */
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 (!this.connected.get()) {
socket = new Socket(host, port);
this.connected.set(true);
}
}
catch (IOException ignore) {
}
finally {
GemFireUtils.close(socket);
}
return this.connected.get();
}
}, duration);
}
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();
}
interface Condition {
boolean evaluate();
}
}

View File

@@ -47,7 +47,7 @@ import org.springframework.session.data.gemfire.config.annotation.web.http.Enabl
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 20) // <1>
public class GemFireServer {
static final String DEFAULT_GEMFIRE_LOG_LEVEL = "config";
static final String DEFAULT_GEMFIRE_LOG_LEVEL = "warning";
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(GemFireServer.class);
@@ -60,25 +60,27 @@ public class GemFireServer {
return new PropertySourcesPlaceholderConfigurer();
}
String applicationName() {
return "samples:httpsession-gemfire-boot-"
.concat(GemFireServer.class.getSimpleName());
}
String gemfireLogLevel() {
return System.getProperty("gemfire.log-level", DEFAULT_GEMFIRE_LOG_LEVEL);
}
Properties gemfireProperties() { // <2>
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", applicationName());
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-level", gemfireLogLevel());
gemfireProperties.setProperty("log-level", logLevel());
gemfireProperties.setProperty("jmx-manager", "true");
gemfireProperties.setProperty("jmx-manager-start", "true");
return gemfireProperties;
}
String applicationName() {
return "samples:httpsession-gemfire-boot:"
.concat(getClass().getSimpleName());
}
String logLevel() {
return System.getProperty("gemfire.log-level", DEFAULT_GEMFIRE_LOG_LEVEL);
}
@Bean
CacheFactoryBean gemfireCache() { // <3>
CacheFactoryBean gemfireCache = new CacheFactoryBean();
@@ -98,12 +100,11 @@ public class GemFireServer {
CacheServerFactoryBean gemfireCacheServer = new CacheServerFactoryBean();
gemfireCacheServer.setAutoStartup(true);
gemfireCacheServer.setCache(gemfireCache);
gemfireCacheServer.setBindAddress(bindAddress);
gemfireCacheServer.setCache(gemfireCache);
gemfireCacheServer.setHostNameForClients(hostnameForClients);
gemfireCacheServer.setMaxTimeBetweenPings(
Long.valueOf(TimeUnit.MINUTES.toMillis(1)).intValue());
gemfireCacheServer.setNotifyBySubscription(true);
Long.valueOf(TimeUnit.SECONDS.toMillis(60)).intValue());
gemfireCacheServer.setPort(port);
return gemfireCacheServer;

View File

@@ -17,6 +17,8 @@ dependencies {
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
runtime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
testCompile "junit:junit:$junitVersion"
integrationTestCompile gebDependencies

View File

@@ -27,9 +27,8 @@ import org.springframework.context.annotation.ImportResource;
public class Application {
public static void main(final String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(Application.class);
context.registerShutdownHook();
new AnnotationConfigApplicationContext(Application.class)
.registerShutdownHook();
}
}
// tag::end[]

View File

@@ -16,14 +16,8 @@
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 com.gemstone.gemfire.cache.client.Pool;
import com.gemstone.gemfire.management.membership.ClientMembership;
@@ -33,53 +27,42 @@ import com.gemstone.gemfire.management.membership.ClientMembershipListenerAdapte
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;
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;
@Value("${application.gemfire.client-server.host:localhost}")
String host;
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, this.port),
String.format("GemFire Server failed to start [host: '%1$s', port: %2$d]%n",
host, this.port));
if ("gemfirePool".equals(beanName)) {
ClientMembership.registerClientMembershipListener(
new ClientMembershipListenerAdapter() {
@Override
public void memberJoined(ClientMembershipEvent event) {
latch.countDown();
}
});
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof PoolFactoryBean || bean instanceof Pool) {
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof Pool && "gemfirePool".equals(beanName)) {
try {
latch.await(DEFAULT_WAIT_DURATION, TimeUnit.MILLISECONDS);
Assert.state(latch.await(DEFAULT_WAIT_DURATION, TimeUnit.MILLISECONDS),
String.format("GemFire Cache Server failed to start on host [%1$s] and port [%2$d]",
this.host, this.port));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
@@ -89,70 +72,4 @@ public class GemFireCacheServerReadyBeanPostProcessor implements BeanPostProcess
return bean;
}
// tag::end[]
interface Condition {
boolean evaluate();
}
String getServerHost(String defaultServerHost) {
return this.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);
}
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();
}
}

View File

@@ -1,3 +1,2 @@
application.gemfire.client-server.host=localhost
application.gemfire.client-server.port=11235
application.gemfire.client-server.max-connections=50
application.gemfire.client-server.port=12480

View File

@@ -20,10 +20,6 @@
<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>
@@ -32,15 +28,18 @@
<prop key="jmx-manager-start">true</prop>
</util:properties>
<!--5-->
<gfe:cache properties-ref="gemfireProperties"
use-bean-factory-locator="false"/>
<!--4-->
<gfe:cache properties-ref="gemfireProperties"/>
<!--6-->
<!--5-->
<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}"/>
host-name-for-clients="${application.gemfire.client-server.host}"
port="${spring.session.data.gemfire.port:${application.gemfire.client-server.port}}"/>
<!--6-->
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
p:maxInactiveIntervalInSeconds="30"/>
<!-- end::beans[] -->
</beans>

View File

@@ -14,41 +14,36 @@
<!-- 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"/>
<!--2-->
<context:property-placeholder location="classpath:META-INF/spring/application.properties"/>
<!--5-->
<!--3-->
<bean class="sample.GemFireCacheServerReadyBeanPostProcessor"/>
<!--6-->
<!--4-->
<util:properties id="gemfireProperties">
<prop key="log-level">${sample.httpsession.gemfire.log-level:warning}</prop>
</util:properties>
<!--5-->
<gfe:client-cache properties-ref="gemfireProperties"/>
<!--7-->
<gfe:pool free-connection-timeout="5000"
keep-alive="false"
<!--6-->
<gfe:pool keep-alive="false"
ping-interval="5000"
read-timeout="5000"
retry-attempts="2"
retry-attempts="1"
subscription-enabled="true"
thread-local-connections="false"
max-connections="${application.gemfire.client-server.max-connections}">
thread-local-connections="false">
<gfe:server host="${application.gemfire.client-server.host}"
port="${spring.session.data.gemfire.port:${application.gemfire.client-server.port}}"/>
</gfe:pool>
<!--7-->
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
p:maxInactiveIntervalInSeconds="30"/>
<!-- end::beans[] -->
</beans>

View File

@@ -12,6 +12,8 @@ dependencies {
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
runtime "org.springframework.shell:spring-shell:1.0.0.RELEASE"
testCompile "junit:junit:$junitVersion"
integrationTestCompile gebDependencies

View File

@@ -16,13 +16,10 @@
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 com.gemstone.gemfire.cache.client.Pool;
import com.gemstone.gemfire.management.membership.ClientMembership;
@@ -38,7 +35,6 @@ import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
import org.springframework.data.gemfire.client.PoolFactoryBean;
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;
// tag::class[]
@@ -46,39 +42,35 @@ import org.springframework.util.Assert;
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", logLevel());
ClientMembership.registerClientMembershipListener(
new ClientMembershipListenerAdapter() {
public void memberJoined(ClientMembershipEvent event) {
if (!event.isClient()) {
latch.countDown();
}
}
});
}
private static String logLevel() {
return System.getProperty("sample.httpsession.gemfire.log-level", "warning");
}
static final String DEFAULT_GEMFIRE_LOG_LEVEL = "warning";
@Bean
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
Properties gemfireProperties() { // <2>
return new Properties();
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", applicationName());
gemfireProperties.setProperty("log-level", logLevel());
return gemfireProperties;
}
String applicationName() {
return "samples:httpsession-gemfire-clientserver:"
.concat(getClass().getSimpleName());
}
String logLevel() {
return System.getProperty("sample.httpsession.gemfire.log-level",
DEFAULT_GEMFIRE_LOG_LEVEL);
}
@Bean
ClientCacheFactoryBean gemfireCache() { // <4>
ClientCacheFactoryBean gemfireCache() { // <3>
ClientCacheFactoryBean clientCacheFactory = new ClientCacheFactoryBean();
clientCacheFactory.setClose(true);
@@ -88,46 +80,56 @@ public class ClientConfig {
}
@Bean
PoolFactoryBean gemfirePool(// <3>
PoolFactoryBean gemfirePool(// <4>
@Value("${spring.session.data.gemfire.port:" + ServerConfig.SERVER_PORT + "}") int port) {
PoolFactoryBean poolFactory = new PoolFactoryBean();
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.setRetryAttempts(1);
poolFactory.setSubscriptionEnabled(true);
poolFactory.setThreadLocalConnections(false);
poolFactory.setServers(Collections.singletonList(
new ConnectionEndpoint(ServerConfig.SERVER_HOSTNAME, port)));
newConnectionEndpoint(ServerConfig.SERVER_HOST, port)));
return poolFactory;
}
@Bean
BeanPostProcessor gemfireCacheServerReadyBeanPostProcessor(// <5>
@Value("${spring.session.data.gemfire.port:" + ServerConfig.SERVER_PORT + "}") final int port) {
ConnectionEndpoint newConnectionEndpoint(String host, int port) {
return new ConnectionEndpoint(host, port);
}
@Bean
BeanPostProcessor gemfireCacheServerReadyBeanPostProcessor() { // <5>
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));
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if ("gemfirePool".equals(beanName)) {
ClientMembership.registerClientMembershipListener(
new ClientMembershipListenerAdapter() {
@Override
public void memberJoined(ClientMembershipEvent event) {
latch.countDown();
}
});
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof PoolFactoryBean || bean instanceof Pool) {
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof Pool && "gemfirePool".equals(beanName)) {
try {
latch.await(DEFAULT_WAIT_DURATION, TimeUnit.MILLISECONDS);
Assert.state(latch.await(DEFAULT_WAIT_DURATION, TimeUnit.MILLISECONDS),
String.format("GemFire Cache Server failed to start on host [%1$s] and port [%2$d]",
ServerConfig.SERVER_HOST, ServerConfig.SERVER_PORT));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
@@ -139,65 +141,4 @@ public class ClientConfig {
};
}
// 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);
}
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();
}
}

View File

@@ -18,6 +18,7 @@ package sample;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import com.gemstone.gemfire.cache.Cache;
@@ -33,13 +34,19 @@ import org.springframework.session.data.gemfire.config.annotation.web.http.Enabl
@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";
static final String DEFAULT_GEMFIRE_LOG_LEVEL = "warning";
static final String SERVER_HOST = "localhost";
@SuppressWarnings("resource")
public static void main(String[] args) throws IOException { // <5>
new AnnotationConfigApplicationContext(ServerConfig.class)
.registerShutdownHook();
}
@Bean
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@@ -47,7 +54,7 @@ public class ServerConfig {
Properties gemfireProperties() { // <2>
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", "GemFireClientServerHttpSessionSample");
gemfireProperties.setProperty("name", applicationName());
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-level", logLevel());
gemfireProperties.setProperty("jmx-manager", "true");
@@ -56,8 +63,14 @@ public class ServerConfig {
return gemfireProperties;
}
private String logLevel() {
return System.getProperty("sample.httpsession.gemfire.log-level", "warning");
String applicationName() {
return "samples:httpsession-gemfire-clientserver:"
.concat(getClass().getSimpleName());
}
String logLevel() {
return System.getProperty("sample.httpsession.gemfire.log-level",
DEFAULT_GEMFIRE_LOG_LEVEL);
}
@Bean
@@ -71,25 +84,19 @@ public class ServerConfig {
}
@Bean
CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache, // <4>
@Value("${spring.session.data.gemfire.port:" + SERVER_PORT + "}") int port) {
CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache,
@Value("${spring.session.data.gemfire.port:" + SERVER_PORT + "}") int port) { // <4>
CacheServerFactoryBean cacheServerFactory = new CacheServerFactoryBean();
CacheServerFactoryBean gemfireCacheServer = new CacheServerFactoryBean();
cacheServerFactory.setAutoStartup(true);
cacheServerFactory.setBindAddress(SERVER_HOSTNAME);
cacheServerFactory.setCache(gemfireCache);
cacheServerFactory.setHostNameForClients(SERVER_HOSTNAME);
cacheServerFactory.setMaxConnections(MAX_CONNECTIONS);
cacheServerFactory.setPort(port);
gemfireCacheServer.setAutoStartup(true);
gemfireCacheServer.setBindAddress(SERVER_HOST);
gemfireCacheServer.setCache(gemfireCache);
gemfireCacheServer.setHostNameForClients(SERVER_HOST);
gemfireCacheServer.setMaxTimeBetweenPings(Long.valueOf(TimeUnit.SECONDS.toMillis(60)).intValue());
gemfireCacheServer.setPort(port);
return cacheServerFactory;
return gemfireCacheServer;
}
@SuppressWarnings("resource")
public static void main(final String[] args) throws IOException { // <5>
new AnnotationConfigApplicationContext(ServerConfig.class).registerShutdownHook();
}
}
// end::class[]

View File

@@ -42,9 +42,8 @@ import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* The GemFireHttpSessionJavaConfigurationTests class is a test suite of test cases
* testing the configuration of Spring Session backed by GemFire using Java-based
* configuration meta-data.
* Test suite of test cases testing the configuration of Spring Session backed by GemFire
* using Java-based configuration meta-data.
*
* @author John Blum
* @since 1.1.0
@@ -62,14 +61,14 @@ import static org.assertj.core.api.Assertions.assertThat;
@ContextConfiguration
@DirtiesContext
@WebAppConfiguration
public class GemFireHttpSessionJavaConfigurationTests
extends AbstractGemFireIntegrationTests {
public class GemFireHttpSessionJavaConfigurationTests extends AbstractGemFireIntegrationTests {
@Autowired
private Cache gemfireCache;
protected <K, V> Region<K, V> assertCacheAndRegion(Cache gemfireCache,
String regionName, DataPolicy dataPolicy) {
assertThat(GemFireUtils.isPeer(gemfireCache)).isTrue();
Region<K, V> region = gemfireCache.getRegion(regionName);
@@ -81,16 +80,16 @@ public class GemFireHttpSessionJavaConfigurationTests
@Test
public void gemfireCacheConfigurationIsValid() {
Region<Object, ExpiringSession> example = assertCacheAndRegion(this.gemfireCache,
"JavaExample", DataPolicy.REPLICATE);
Region<Object, ExpiringSession> example =
assertCacheAndRegion(this.gemfireCache, "JavaExample", DataPolicy.REPLICATE);
assertEntryIdleTimeout(example, ExpirationAction.INVALIDATE, 900);
}
@Test
public void verifyGemFireExampleCacheRegionPrincipalNameIndexWasCreatedSuccessfully() {
Region<Object, ExpiringSession> example = assertCacheAndRegion(this.gemfireCache,
"JavaExample", DataPolicy.REPLICATE);
Region<Object, ExpiringSession> example =
assertCacheAndRegion(this.gemfireCache, "JavaExample", DataPolicy.REPLICATE);
QueryService queryService = example.getRegionService().getQueryService();
@@ -103,32 +102,38 @@ public class GemFireHttpSessionJavaConfigurationTests
@Test
public void verifyGemFireExampleCacheRegionSessionAttributesIndexWasNotCreated() {
Region<Object, ExpiringSession> example = assertCacheAndRegion(this.gemfireCache,
"JavaExample", DataPolicy.REPLICATE);
Region<Object, ExpiringSession> example =
assertCacheAndRegion(this.gemfireCache, "JavaExample", DataPolicy.REPLICATE);
QueryService queryService = example.getRegionService().getQueryService();
assertThat(queryService).isNotNull();
Index sessionAttributesIndex = queryService.getIndex(example,
"sessionAttributesIndex");
Index sessionAttributesIndex = queryService.getIndex(example, "sessionAttributesIndex");
assertThat(sessionAttributesIndex).isNull();
}
@EnableGemFireHttpSession(indexableSessionAttributes = {}, maxInactiveIntervalInSeconds = 900, regionName = "JavaExample", serverRegionShortcut = RegionShortcut.REPLICATE)
@EnableGemFireHttpSession(indexableSessionAttributes = {}, maxInactiveIntervalInSeconds = 900,
regionName = "JavaExample", serverRegionShortcut = RegionShortcut.REPLICATE)
public static class GemFireConfiguration {
@Bean
Properties gemfireProperties() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name",
GemFireHttpSessionJavaConfigurationTests.class.getName());
gemfireProperties.setProperty("name", applicationName());
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-level", "warning");
gemfireProperties.setProperty("log-level", logLevel());
return gemfireProperties;
}
String applicationName() {
return GemFireHttpSessionJavaConfigurationTests.class.getName();
}
String logLevel() {
return "warning";
}
@Bean
CacheFactoryBean gemfireCache() {
CacheFactoryBean cacheFactory = new CacheFactoryBean();
@@ -139,5 +144,4 @@ public class GemFireHttpSessionJavaConfigurationTests
return cacheFactory;
}
}
}

View File

@@ -37,9 +37,8 @@ import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* The GemFireHttpSessionXmlConfigurationTests class is a test suite of test cases testing
* the configuration of Spring Session backed by GemFire using XML configuration
* meta-data.
* Test suite of test cases testing the configuration of Spring Session backed by GemFire
* using XML configuration meta-data.
*
* @author John Blum
* @since 1.1.0
@@ -57,14 +56,14 @@ import static org.assertj.core.api.Assertions.assertThat;
@ContextConfiguration
@DirtiesContext
@WebAppConfiguration
public class GemFireHttpSessionXmlConfigurationTests
extends AbstractGemFireIntegrationTests {
public class GemFireHttpSessionXmlConfigurationTests extends AbstractGemFireIntegrationTests {
@Autowired
private Cache gemfireCache;
protected <K, V> Region<K, V> assertCacheAndRegion(Cache gemfireCache,
String regionName, DataPolicy dataPolicy) {
assertThat(GemFireUtils.isPeer(gemfireCache)).isTrue();
Region<K, V> region = gemfireCache.getRegion(regionName);
@@ -76,16 +75,16 @@ public class GemFireHttpSessionXmlConfigurationTests
@Test
public void gemfireCacheConfigurationIsValid() {
Region<Object, ExpiringSession> example = assertCacheAndRegion(this.gemfireCache,
"XmlExample", DataPolicy.NORMAL);
Region<Object, ExpiringSession> example =
assertCacheAndRegion(this.gemfireCache, "XmlExample", DataPolicy.NORMAL);
assertEntryIdleTimeout(example, ExpirationAction.INVALIDATE, 3600);
}
@Test
public void verifyGemFireExampleCacheRegionPrincipalNameIndexWasCreatedSuccessfully() {
Region<Object, ExpiringSession> example = assertCacheAndRegion(this.gemfireCache,
"XmlExample", DataPolicy.NORMAL);
Region<Object, ExpiringSession> example =
assertCacheAndRegion(this.gemfireCache, "XmlExample", DataPolicy.NORMAL);
QueryService queryService = example.getRegionService().getQueryService();
@@ -98,18 +97,16 @@ public class GemFireHttpSessionXmlConfigurationTests
@Test
public void verifyGemFireExampleCacheRegionSessionAttributesIndexWasCreatedSuccessfully() {
Region<Object, ExpiringSession> example = assertCacheAndRegion(this.gemfireCache,
"XmlExample", DataPolicy.NORMAL);
Region<Object, ExpiringSession> example =
assertCacheAndRegion(this.gemfireCache, "XmlExample", DataPolicy.NORMAL);
QueryService queryService = example.getRegionService().getQueryService();
assertThat(queryService).isNotNull();
Index sessionAttributesIndex = queryService.getIndex(example,
"sessionAttributesIndex");
Index sessionAttributesIndex = queryService.getIndex(example, "sessionAttributesIndex");
assertIndex(sessionAttributesIndex, "s.attributes['one', 'two', 'three']",
String.format("%1$s s", example.getFullPath()));
String.format("%1$s s", example.getFullPath()));
}
}

View File

@@ -46,8 +46,8 @@ import org.springframework.session.config.annotation.web.http.SpringHttpSessionC
import org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSession;
import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository;
import org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean;
import org.springframework.session.data.gemfire.config.annotation.web.http.support.SessionAttributesIndexFactoryBean;
import org.springframework.session.data.gemfire.support.GemFireUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -56,7 +56,15 @@ import org.springframework.util.StringUtils;
* HttpSession provider implementation in Spring Session.
*
* @author John Blum
* @see EnableGemFireHttpSession
* @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.GemfireTemplate
* @see org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.cache.Region
* @since 1.1.0
*/
@Configuration
@@ -97,7 +105,7 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat
/**
* The default names of all Session attributes that should be indexed by GemFire.
*/
public static final String[] DEFAULT_INDEXABLE_SESSION_ATTRIBUTES = new String[0];
public static final String[] DEFAULT_INDEXABLE_SESSION_ATTRIBUTES = {};
private int maxInactiveIntervalInSeconds = DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS;
@@ -185,28 +193,6 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat
: DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
}
/**
* Gets the names of all Session attributes that will be indexed by GemFire as single
* String value constituting the Index expression of the Index definition.
*
* @return a String composed of all the named Session attributes on which a GemFire
* Index will be created as an Index definition expression. If the indexable Session
* attributes were not specified, then the wildcard ("*") is returned.
* @see com.gemstone.gemfire.cache.query.Index#getIndexedExpression()
*/
protected String getIndexableSessionAttributesAsGemFireIndexExpression() {
StringBuilder builder = new StringBuilder();
for (String sessionAttribute : getIndexableSessionAttributes()) {
builder.append(builder.length() > 0 ? ", " : "");
builder.append(String.format("'%1$s'", sessionAttribute));
}
String indexExpression = builder.toString();
return (indexExpression.isEmpty() ? "*" : indexExpression);
}
/**
* Sets the maximum interval in seconds in which a Session can remain inactive before
* it is considered expired.
@@ -385,7 +371,7 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat
*/
@Bean(name = DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME)
public GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession> sessionRegion(GemFireCache gemfireCache,
RegionAttributes<Object, ExpiringSession> sessionRegionAttributes) {
@Qualifier("sessionRegionAttributes") RegionAttributes<Object, ExpiringSession> sessionRegionAttributes) {
GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession> sessionRegion =
new GemFireCacheTypeAwareRegionFactoryBean<Object, ExpiringSession>();
@@ -448,27 +434,19 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat
}
/**
* 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}.
* Defines a GemFire Index bean on the GemFire cache {@link Region} storing and managing Sessions,
* specifically on the 'principalName' property for quick lookup of Sessions by 'principalName'.
*
* @param gemfireCache a reference to the GemFire cache.
* @return a IndexFactoryBean creating an GemFire Index on the 'principalName'
* property of Sessions stored in the GemFire cache {@link Region}.
* @return a {@link IndexFactoryBean} to create an GemFire Index on the 'principalName' property
* for Sessions stored in the GemFire cache {@link Region}.
* @see org.springframework.data.gemfire.IndexFactoryBean
* @see 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();
}
}
};
public IndexFactoryBean principalNameIndex(GemFireCache gemfireCache) {
IndexFactoryBean index = new IndexFactoryBean();
index.setCache(gemfireCache);
index.setName("principalNameIndex");
@@ -481,37 +459,25 @@ public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfigurat
}
/**
* Defines a Spring GemFire Index bean on the GemFire cache {@link Region} storing and
* managing Sessions, specifically on Session attributes for quick lookup and queries
* on Session attribute names with a given value. This index will only be created on a
* server @{link Region}.
* Defines a GemFire Index bean on the GemFire cache {@link Region} storing and managing Sessions,
* specifically on all Session attributes for quick lookup and queries on Session attribute names
* with a given value.
*
* @param gemfireCache a reference to the GemFire cache.
* @return a IndexFactoryBean creating an GemFire Index on attributes of Sessions
* @return a {@link IndexFactoryBean} to create an GemFire Index on attributes of Sessions
* stored in the GemFire cache {@link Region}.
* @see org.springframework.data.gemfire.IndexFactoryBean
* @see com.gemstone.gemfire.cache.GemFireCache
*/
@Bean
@DependsOn(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME)
public IndexFactoryBean sessionAttributesIndex(final GemFireCache gemfireCache) {
IndexFactoryBean index = new IndexFactoryBean() {
@Override
public void afterPropertiesSet() throws Exception {
if (GemFireUtils.isPeer(gemfireCache) && !ObjectUtils.isEmpty(getIndexableSessionAttributes())) {
super.afterPropertiesSet();
}
}
};
public SessionAttributesIndexFactoryBean sessionAttributesIndex(GemFireCache gemfireCache) {
SessionAttributesIndexFactoryBean sessionAttributesIndex = new SessionAttributesIndexFactoryBean();
index.setCache(gemfireCache);
index.setName("sessionAttributesIndex");
index.setExpression(String.format("s.attributes[%1$s]",
getIndexableSessionAttributesAsGemFireIndexExpression()));
index.setFrom(String.format("%1$s s", GemFireUtils.toRegionPath(getSpringSessionGemFireRegionName())));
index.setOverride(true);
sessionAttributesIndex.setGemFireCache(gemfireCache);
sessionAttributesIndex.setIndexableSessionAttributes(getIndexableSessionAttributes());
sessionAttributesIndex.setRegionName(getSpringSessionGemFireRegionName());
return index;
return sessionAttributesIndex;
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.data.gemfire.config.annotation.web.http.support;
import javax.servlet.http.HttpSession;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.query.Index;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.gemfire.IndexFactoryBean;
import org.springframework.session.data.gemfire.support.GemFireUtils;
import org.springframework.util.ObjectUtils;
/**
* The SessionAttributesIndexFactoryBean class is a Spring {@link FactoryBean} that creates a GemFire {@link Index}
* on the session attributes of the {@link HttpSession} object.
*
* @author John Blum
* @since 1.3.0
* @see org.springframework.beans.factory.BeanFactoryAware
* @see org.springframework.beans.factory.BeanNameAware
* @see org.springframework.beans.factory.FactoryBean
* @see org.springframework.beans.factory.InitializingBean
* @see com.gemstone.gemfire.cache.query.Index
*/
public class SessionAttributesIndexFactoryBean implements FactoryBean<Index>,
InitializingBean, BeanFactoryAware, BeanNameAware {
protected static final String[] DEFAULT_INDEXABLE_SESSION_ATTRIBUTES = {};
private BeanFactory beanFactory;
private GemFireCache gemfireCache;
private Index sessionAttributesIndex;
private String beanName;
private String regionName;
private String[] indexableSessionAttributes;
/* (non-Javadoc) */
public void afterPropertiesSet() throws Exception {
if (isIndexableSessionAttributesConfigured()) {
this.sessionAttributesIndex = newIndex();
}
}
/**
* Determines whether any indexable Session attributes were configured for this {@link FactoryBean}.
*
* @return a boolean value indicating whether any indexable Session attributes were configured
* for this {@link FactoryBean}
* @see #setIndexableSessionAttributes(String[])
*/
protected boolean isIndexableSessionAttributesConfigured() {
return !ObjectUtils.isEmpty(this.indexableSessionAttributes);
}
/**
* Constructs a GemFire {@link Index} over the attributes of the {@link HttpSession}.
*
* @return a GemFire {@link Index} over the {@link HttpSession} attributes.
* @throws Exception if an error occurs while initializing the GemFire {@link Index}.
* @see org.springframework.data.gemfire.IndexFactoryBean
*/
protected Index newIndex() throws Exception {
IndexFactoryBean indexFactory = new IndexFactoryBean();
indexFactory.setBeanFactory(this.beanFactory);
indexFactory.setBeanName(this.beanName);
indexFactory.setCache(this.gemfireCache);
indexFactory.setName("sessionAttributesIndex");
indexFactory.setExpression(String.format("s.attributes[%1$s]",
getIndexableSessionAttributesAsGemFireIndexExpression()));
indexFactory.setFrom(String.format("%1$s s", GemFireUtils.toRegionPath(this.regionName)));
indexFactory.setOverride(true);
indexFactory.afterPropertiesSet();
return indexFactory.getObject();
}
/**
* Gets the names of all Session attributes that will be indexed by GemFire as single, comma-delimited
* String value constituting the Index expression of the Index definition.
*
* @return a String composed of all the named Session attributes for which GemFire
* will create an Index as an Index definition expression. If the indexable Session
* attributes were not configured, then the wildcard ("*") is returned.
* @see com.gemstone.gemfire.cache.query.Index#getIndexedExpression()
*/
protected String getIndexableSessionAttributesAsGemFireIndexExpression() {
StringBuilder builder = new StringBuilder();
for (String sessionAttribute : getIndexableSessionAttributes()) {
builder.append(builder.length() > 0 ? ", " : "");
builder.append(String.format("'%s'", sessionAttribute));
}
String indexExpression = builder.toString();
return (indexExpression.isEmpty() ? "*" : indexExpression);
}
/* (non-Javadoc) */
public Index getObject() throws Exception {
return this.sessionAttributesIndex;
}
/* (non-Javadoc) */
public Class<?> getObjectType() {
return (this.sessionAttributesIndex != null ? this.sessionAttributesIndex.getClass() : Index.class);
}
/* (non-Javadoc) */
public boolean isSingleton() {
return true;
}
/* (non-Javadoc) */
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
/* (non-Javadoc) */
public void setBeanName(String beanName) {
this.beanName = beanName;
}
/* (non-Javadoc) */
public void setGemFireCache(GemFireCache gemfireCache) {
this.gemfireCache = gemfireCache;
}
/* (non-Javadoc) */
public void setIndexableSessionAttributes(String[] indexableSessionAttributes) {
this.indexableSessionAttributes = indexableSessionAttributes;
}
/* (non-Javadoc) */
protected String[] getIndexableSessionAttributes() {
return (this.indexableSessionAttributes != null ? this.indexableSessionAttributes
: DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
}
/* (non-Javadoc) */
public void setRegionName(String regionName) {
this.regionName = regionName;
}
/* (non-Javadoc) */
protected String getRegionName() {
return this.regionName;
}
}

View File

@@ -127,34 +127,6 @@ public class GemFireHttpSessionConfigurationTest {
GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT);
}
@Test
public void setAndGetIndexableSessionAttributes() {
assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()).isEqualTo(
GemFireHttpSessionConfiguration.DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
assertThat(this.gemfireConfiguration.getIndexableSessionAttributesAsGemFireIndexExpression()).isEqualTo("*");
this.gemfireConfiguration.setIndexableSessionAttributes(toArray("one", "two", "three"));
assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()).isEqualTo(
toArray("one", "two", "three"));
assertThat(this.gemfireConfiguration.getIndexableSessionAttributesAsGemFireIndexExpression()).isEqualTo(
"'one', 'two', 'three'");
this.gemfireConfiguration.setIndexableSessionAttributes(toArray("one"));
assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()).isEqualTo(toArray("one"));
assertThat(this.gemfireConfiguration.getIndexableSessionAttributesAsGemFireIndexExpression()).isEqualTo("'one'");
this.gemfireConfiguration.setIndexableSessionAttributes(null);
assertThat(this.gemfireConfiguration.getIndexableSessionAttributes()).isEqualTo(
GemFireHttpSessionConfiguration.DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
assertThat(this.gemfireConfiguration.getIndexableSessionAttributesAsGemFireIndexExpression()).isEqualTo("*");
}
@Test
public void setAndGetMaxInactiveIntervalInSeconds() {
assertThat(this.gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(
@@ -363,6 +335,7 @@ public class GemFireHttpSessionConfigurationTest {
}
@Test
@SuppressWarnings("unchecked")
public void createsAndInitializesSessionRegionAttributesWithoutExpiration() throws Exception {
ClientCache mockClientCache = mock(ClientCache.class);
@@ -431,5 +404,4 @@ public class GemFireHttpSessionConfigurationTest {
assertThat(this.gemfireConfiguration.isExpirationAllowed(mockCache)).isFalse();
}
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.data.gemfire.config.annotation.web.http.support;
import com.gemstone.gemfire.cache.query.Index;
import org.junit.Before;
import org.junit.Test;
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Test suite of test cases testing the contract and functionality
* of the {@link SessionAttributesIndexFactoryBean} class.
*
* @author John Blum
* @since 1.3.0
* @see org.junit.Test
* @see org.mockito.Mockito
* @see org.springframework.session.data.gemfire.config.annotation.web.http.support.SessionAttributesIndexFactoryBean
*/
public class SessionAttributesIndexFactoryBeanTests {
static <T> T[] toArray(T... array) {
return array;
}
private SessionAttributesIndexFactoryBean indexFactoryBean;
@Before
public void setup() {
this.indexFactoryBean = new SessionAttributesIndexFactoryBean();
}
@Test
public void indexIsNotInitializedWhenNoIndexableSessionAttributesAreConfigured() throws Exception {
final Index mockIndex = mock(Index.class);
SessionAttributesIndexFactoryBean indexFactoryBean = new SessionAttributesIndexFactoryBean() {
@Override
protected Index newIndex() throws Exception {
return mockIndex;
}
};
indexFactoryBean.afterPropertiesSet();
assertThat(indexFactoryBean.getObject()).isNull();
assertThat(indexFactoryBean.getObjectType()).isEqualTo(Index.class);
}
@Test
public void initializesIndexWhenIndexableSessionAttributesAreConfigured() throws Exception {
final Index mockIndex = mock(Index.class);
SessionAttributesIndexFactoryBean indexFactoryBean = new SessionAttributesIndexFactoryBean() {
@Override
protected Index newIndex() throws Exception {
return mockIndex;
}
};
indexFactoryBean.setIndexableSessionAttributes(toArray("one", "two"));
indexFactoryBean.afterPropertiesSet();
assertThat(indexFactoryBean.getObject()).isEqualTo(mockIndex);
assertThat(indexFactoryBean.getObjectType()).isEqualTo(mockIndex.getClass());
}
@Test
public void isSingletonIsTrue() {
assertThat(this.indexFactoryBean.isSingleton()).isTrue();
}
@Test
public void setAndGetIndexableSessionAttributes() {
assertThat(this.indexFactoryBean.getIndexableSessionAttributes()).isEqualTo(
GemFireHttpSessionConfiguration.DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
assertThat(this.indexFactoryBean.getIndexableSessionAttributesAsGemFireIndexExpression()).isEqualTo("*");
this.indexFactoryBean.setIndexableSessionAttributes(toArray("one", "two", "three"));
assertThat(this.indexFactoryBean.getIndexableSessionAttributes()).isEqualTo(
toArray("one", "two", "three"));
assertThat(this.indexFactoryBean.getIndexableSessionAttributesAsGemFireIndexExpression()).isEqualTo(
"'one', 'two', 'three'");
this.indexFactoryBean.setIndexableSessionAttributes(toArray("one"));
assertThat(this.indexFactoryBean.getIndexableSessionAttributes()).isEqualTo(toArray("one"));
assertThat(this.indexFactoryBean.getIndexableSessionAttributesAsGemFireIndexExpression()).isEqualTo("'one'");
this.indexFactoryBean.setIndexableSessionAttributes(null);
assertThat(this.indexFactoryBean.getIndexableSessionAttributes()).isEqualTo(
GemFireHttpSessionConfiguration.DEFAULT_INDEXABLE_SESSION_ATTRIBUTES);
assertThat(this.indexFactoryBean.getIndexableSessionAttributesAsGemFireIndexExpression()).isEqualTo("*");
}
@Test
public void setAndGetRegionName() {
assertThat(this.indexFactoryBean.getRegionName()).isNull();
this.indexFactoryBean.setRegionName("Example");
assertThat(this.indexFactoryBean.getRegionName()).isEqualTo("Example");
this.indexFactoryBean.setRegionName(null);
assertThat(this.indexFactoryBean.getRegionName()).isNull();
}
}

View File

@@ -16,7 +16,7 @@
<prop key="log-level">warning</prop>
</util:properties>
<gfe:cache properties-ref="gemfireProperties" close="true"/>
<gfe:cache properties-ref="gemfireProperties"/>
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
p:indexableSessionAttributes="one, two, three" p:maxInactiveIntervalInSeconds="3600"