DATAGEODE-142 - Add annotation configuration support to create client Regions from Cluster-defined Regions.

This commit is contained in:
John Blum
2018-09-05 11:49:02 -07:00
parent 2742d806f2
commit 847c11b8cd
13 changed files with 1061 additions and 280 deletions

View File

@@ -491,6 +491,30 @@ See <<bootstrap-annotation-config-regions>> for more details.
See <<gemfire-repositories>> for more details.
[[bootstap-annotations-quickstart-cluster-defined-regions]]
== Configure Client Regions from Cluster-defined Regions
Alternatively, you can define client [*PROXY] Regions from Regions already defined in the cluster
using `@EnableClusterDefinedRegions`, as follows:
[source,java]
----
@SpringBootApplication
@ClientCacheApplication
@EnableClusterDefinedRegions
@EnableGemfireRepositories
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
...
}
----
See <<bootstrap-annotation-config-region-cluster-defined>> for more details.
[[bootstap-annotations-quickstart-functions]]
== Configure Functions

View File

@@ -1,5 +1,5 @@
[[bootstrap-annotation-config]]
= Bootstrapping {data-store-name} using Spring Annotations
= Bootstrapping {data-store-name} with the Spring Container using Annotations
{sdg-name} ({sdg-acronym}) 2.0 introduces a new annotation-based configuration model
to configure and bootstrap {data-store-name} using the Spring container.
@@ -66,7 +66,7 @@ You can find all the new {sdg-acronym} Java `Annotations` in the `org.springfram
package.
[[bootstrap-annotation-config-geode-applications]]
== Bootstrapping {data-store-name} Applications with Spring
== Configuring {data-store-name} Applications with Spring
Like all Spring Boot applications that begin by annotating the application class with `@SpringBootApplication`,
a Spring Boot application can easily become a {data-store-name} cache application by declaring any one of three
@@ -83,8 +83,8 @@ that can be created with {data-store-name}: a client cache or a peer cache.
You can configure a Spring Boot application as a {data-store-name} cache client with an instance of `ClientCache`,
which can communicate with an existing cluster of {data-store-name} servers used to manage the application's data.
The client-server topology is the most common system architecture employed when using {data-store-name} and you
can make your Spring Boot application a cache client, with a `ClientCache` instance, simply by annotating it with
The client-server topology is the most common system architecture employed when using {data-store-name} and you can
make your Spring Boot application a cache client, with a `ClientCache` instance, simply by annotating it with
`@ClientCacheApplication`.
Alternatively, a Spring Boot application may be a peer member of a {data-store-name} cluster. That is, the application
@@ -94,15 +94,15 @@ an "embedded", peer `Cache` instance when you annotate your application class wi
By extension, a peer cache application may also serve as a `CacheServer` too, allowing cache clients to connect
and perform data access operations on the server. This is accomplished by annotating the application class with
`@CacheServerApplication` in place of `@PeerCacheApplication`, which creates a peer `Cache` instance along with
the `CacheServer`.
the `CacheServer` that allows cache clients to connect.
NOTE: A {data-store-name} server is not necessarily a cache server by default. That is, a server is not necessarily
set up to serve cache clients just because it is a server. A {data-store-name} server can be a peer member (data node)
of the cluster managing data without serving any clients while other peer members in the cluster are indeed set up
to serve clients in addition to managing data. It is also possible to set up certain peer members in the cluster as
non-data nodes, called {x-data-store-docs}/developing/region_options/data_hosts_and_accessors.html[data accessors],
which do not store data, but act as a proxy to service clients as `CacheServers`. This is beyond the scope
of this document.
which do not store data, but act as a proxy to service clients as `CacheServers`. Many different topologies
and cluster arrangements are supported by {data-store-name}, but are beyond the scope of this document.
By way of example, if you want to create a Spring Boot cache client application, start with the following:
@@ -115,7 +115,7 @@ class ClientApplication { .. }
----
Or, if you want to create a Spring Boot application with an embedded peer `Cache` instance, where your application
will be a server and peer member of a cluster, or distributed system, formed by {data-store-name},
will be a server and peer member of a cluster (distributed system) formed by {data-store-name},
start with the following:
.Spring-based {data-store-name} embedded peer `Cache` application
@@ -127,7 +127,7 @@ class ServerApplication { .. }
----
Alternatively, you can use the `@CacheServerApplication` annotation in place of `@PeerCacheApplication` to create
both an embedded peer `Cache` instance and a `CacheServer` running on `localhost`, listening on the default
both an embedded peer `Cache` instance along with a `CacheServer` running on `localhost`, listening on the default
cache server port, `40404`, as follows:
.Spring-based {data-store-name} embedded peer `Cache` application with `CacheServer`
@@ -147,25 +147,25 @@ The most common and recommended approach is to use {data-store-name} Locators.
NOTE: A cache client can connect to one or more Locators in the {data-store-name} cluster instead of directly to a
`CacheServer`. The advantage of using Locators over direct `CacheServer` connections is that Locators provide metadata
about the cluster to which the client is connected. This metadata includes information such as which servers contain
the data of interest or which servers have the least amount of load. A client `Pool` in conjuction with a Locator
the data of interest or which servers have the least amount of load. A client `Pool` in conjunction with a Locator
also provides fail-over capabilities in case a `CacheServer` crashes. By enabling the `PARTITION` Region (PR)
single-hop feature in the client `Pool`, the client is routed directly to the server containing the data requested
and needed by the client.
NOTE: Locators are also peer members in a cluster. Locators actually constitute what makes up a cluster of {data-store-name}
nodes. That is, all nodes connected by a Locator are peers in the cluster, and new members use Locators to join a cluster
and find other members.
NOTE: Locators are also peer members in a cluster. Locators actually constitute what makes up a cluster of
{data-store-name} nodes. That is, all nodes connected by a Locator are peers in the cluster, and new members
use Locators to join a cluster and find other members.
By default, {data-store-name} sets up a "DEFAULT" `Pool` connected to a `CacheServer` running on `localhost`,
listening on port `40404` when a `ClientCache` instance is created. A `CacheServer` listens on port `40404`,
accepting connections from all system NICs. You do not need to do anything special to use the client-server topology.
Simply annotate your server-side Spring Boot application with `@CacheServerApplication` and your client-side Spring Boot
application with `@ClientCacheApplication`, and you are ready to go.
accepting connections on all system NICs. You do not need to do anything special to use the client-server topology.
Simply annotate your server-side Spring Boot application with `@CacheServerApplication` and your client-side
Spring Boot application with `@ClientCacheApplication`, and you are ready to go.
If you prefer, you can even start your servers with Gfsh's `start server` command. Your Spring Boot `@ClientCacheApplication`
can still connect to the server regardless of how it was started. However, you may prefer to configure and start
your servers by using the {sdg-name} approach since a properly annotated Spring Boot application class is far more
intuitive and easier to debug.
can still connect to the server regardless of how it was started. However, you may prefer to configure and start your
servers by using the {sdg-name} approach since a properly annotated Spring Boot application class is far more intuitive
and easier to debug.
As an application developer, you will no doubt want to customize the "DEFAULT" `Pool` set up by {data-store-name}
to possibly connect to one or more Locators, as the following example demonstrates:
@@ -691,7 +691,7 @@ the annotation attributes or corresponding configuration properties to adjust th
Follow the earlier links for more details on HTTP support and the services provided.
[[bootstrap-annotation-config-embedded-services-memcached]]
=== Configuring the embedded Memcached Server (Gemcached)
=== Configuring the Embedded Memcached Server (Gemcached)
{data-store-name} also implements the Memcached protocol with the ability to service Memcached clients. That is,
Memcached clients can connect to a {data-store-name} cluster and perform Memcached operations as if
@@ -883,51 +883,6 @@ or associated configuration properties.
See the https://docs.spring.io/spring-data/gemfire/docs/current/api/org/springframework/data/gemfire/config/annotation/EnablePdx.html[`@EnablePdx` annotation Javadoc]
for more details.
[[bootstrap-annotation-config-ssl]]
== Configuring SSL
Equally important to serializing data to be transferred over the wire is securing the data while in transit.
Of course, the common way to accomplish this in Java is by using the Secure Sockets Extension (SSE)
and Transport Layer Security (TLS).
To enable SSL, annotate your application class with `@EnableSsl`, as follows:
.Spring `ClientCache` application with SSL enabled
[source, java]
----
@SpringBootApplication
@ClientCacheApplication
@EnableSsl
public class ClientApplication { .. }
----
Then you need to set the necessary SSL configuration attributes or properties: keystores, usernames/passwords, and so on.
You can individually configure different {data-store-name} components (`GATEWAY`, `HTTP`, `JMX`, `LOCATOR`, and `SERVER`)
with SSL, or you can collectively configure them to use SSL by using the `CLUSTER` enumerated value.
You can specify which {data-store-name} components the SSL configuration settings should applied by using
the nested `@EnableSsl` annotation, `components` attribute with enumerated values from the `Component` enum,
as follows:
.Spring `ClientCache` application with SSL enabled by component
[source, java]
----
@SpringBootApplication
@ClientCacheApplication
@EnableSsl(components = { GATEWAY, LOCATOR, SERVER })
public class ClientApplication { .. }
----
In addition, you can also specify component-level SSL configuration (`ciphers`, `protocols` and `keystore`/`truststore`
information) by using the corresponding annotation attribute or associated configuration properties.
See the https://docs.spring.io/spring-data/gemfire/docs/current/api/org/springframework/data/gemfire/config/annotation/EnableSsl.html[`@EnableSsl` annotation Javadoc]
for more details.
More details on {data-store-name} SSL support can be found
{x-data-store-docs}/managing/security/ssl_overview.html[here].
[[bootstrap-annotation-config-gemfire-properties]]
== Configuring {data-store-name} Properties
@@ -971,9 +926,9 @@ More details on {data-store-name} properties can be found
== Configuring Regions
So far, outside of PDX, our discussion has centered around configuring {data-store-name}'s more administrative functions:
creating a cache instance, starting embedded services, enabling logging, statistics, SSL, and using `gemfire.properties`
to affect low-level configuration and behavior. While all these configuration options are important, none of them
relate directly to your application. In other words, we still need some place to store our application data
creating a cache instance, starting embedded services, enabling logging and statistics, configuring PDX, and using
`gemfire.properties` to affect low-level configuration and behavior. While all these configuration options are important,
none of them relate directly to your application. In other words, we still need some place to store our application data
and make it generally available and accessible.
{data-store-name} organizes data in a cache into {x-data-store-docs}/basic_config/data_regions/chapter_overview.html[Regions].
@@ -981,9 +936,9 @@ You can think of a Region as a table in a relational database. Generally, a Regi
which makes it more conducive for building effective indexes and writing queries. We cover indexing
<<bootstrap-annotation-config-indexes,later>>.
Previously, {sdg-name} users needed to explicitly define and declare the Regions used in their applications to store data
Previously, {sdg-name} users needed to explicitly define and declare the Regions used by their applications to store data
by writing very verbose Spring configuration metadata, whether using {sdg-acronym}'s `FactoryBeans` from the API
in Spring's {spring-framework-docs}/core.html#beans-java[Java-based container configuration]
with Spring's {spring-framework-docs}/core.html#beans-java[Java-based container configuration]
or using <<bootstrap:region, XML>>.
The following example demonstrates how to configure a Region bean in Java:
@@ -1011,7 +966,7 @@ class GemFireConfiguration {
}
----
The following example shows how to configure the same Region bean in XML:
The following example demonstrates how to configure the same Region bean in XML:
.Example Region bean definition using {sdg-acronym}'s XML Namespace
[source, xml]
@@ -1022,8 +977,10 @@ The following example shows how to configure the same Region bean in XML:
----
While neither Java nor XML configuration is all that difficult to specify, either one can be cumbersome, especially if
an application requires a large number of Regions. Many relational database-based applications can literally
have hundreds or even thousands of tables.
an application requires a large number of Regions. Many relational database-based applications can have hundreds
or even thousands of tables.
Defining and declaring all these Regions by hand would be cumbersome and error prone. Well, now there is a better way.
Now you can define and configure Regions based on their application domain objects (entities) themselves. No longer do
you need to explicitly define `Region` bean definitions in Spring configuration metadata, unless you require
@@ -1037,7 +994,7 @@ NOTE: Most Spring Data application developers should already be familiar with th
and {sdg-name}'s <<gemfire-repositories,implementation/extension>>,
which has been specifically customized to optimize data access operations for {data-store-name}.
First, an application developer starts by defining the application's domain objects, as follows:
First, an application developer starts by defining the application's domain objects (entities), as follows:
.Application domain object type modeling a Book
[source, java]
@@ -1075,7 +1032,7 @@ operations (CRUD) along with support for simple queries (such as `findById(..)`)
more sophisticated queries by declaring query methods on the repository interface
(for example, `List<BooK> findByAuthor(Author author);`).
Under the hood, {sdg-name} provides an implementation of your applications repository interfaces when
Under the hood, {sdg-name} provides an implementation of your application's repository interfaces when
the Spring container is bootstrapped. {sdg-acronym} even implements the query methods you define so long as you follow
the <<gemfire-repositories.executing-queries,conventions>>.
@@ -1104,6 +1061,9 @@ TIP: Creating Regions from entity classes is most useful when using Spring Data
{sdg-name}'s Repository support is enabled with the `@EnableGemfireRepositories` annotation, as shown in
the preceding example.
NOTE: Currently, only entity classes explicitly annotated with `@Region` are picked up by the scan
and will have Regions created. If an entity class is not explicitly mapped with `@Region` no Region will be created.
By default, the `@EnableEntityDefinedRegions` annotation scans for entity classes recursively, starting from
the package of the configuration class on which the `@EnableEntityDefinedRegions` annotation is declared.
@@ -1148,13 +1108,13 @@ See {x-data-store-docs}/developing/region_options/storage_distribution_options.h
in the {data-store-name} User Guide for more details.
When you annotate your application domain object types with the generic `@Region` mapping annotation, {sdg-name} decides
which type of Region to create. {sdg-acronym}'s default strategy takes the cache type into consideration when determining
the type of Region to create.
which type of Region to create. {sdg-acronym}'s default strategy takes the cache type into consideration when
determining the type of Region to create.
For example, if you declare the application as a `ClientCache` by using the `@ClientCacheApplication` annotation,
{sdg-acronym} creates a client `PROXY` `Region`. Alternatively, if you declare the application as a peer `Cache`
by using either the `@PeerCacheApplication` or `@CacheServerApplication` annotations, {sdg-acronym} creates
a server `PARTITION` `Region`.
{sdg-acronym} creates a client `PROXY` `Region` by default. Alternatively, if you declare the application as a
peer `Cache` by using either the `@PeerCacheApplication` or `@CacheServerApplication` annotations,
{sdg-acronym} creates a server `PARTITION` `Region` by default.
Of course, you can always override the default when necessary. To override the default applied by {sdg-name},
four new Region mapping annotations have been introduced:
@@ -1165,7 +1125,7 @@ four new Region mapping annotations have been introduced:
* `@ReplicateRegion`
The `@ClientRegion` mapping annotation is specific to client applications. All of the other Region mapping annotations
listed above can be used only in server applications that have an embedded peer `Cache`.
listed above can only be used in server applications that have an embedded peer `Cache`.
It is sometimes necessary for client applications to create and use local-only Regions, perhaps to aggregate data
from other Regions in order to analyze the data locally and carry out some function performed by the application
@@ -1177,8 +1137,8 @@ accomplished with Idle-Timeout (TTI) and Time-To-Live (TTL) expiration policies
NOTE: Region-level Idle-Timeout (TTI) and Time-To-Live (TTL) expiration policies are independent of and different from
entry-level TTI and TTL expiration policies.
In any case, if you want to create a local-only client Region where the data is not going to be distributed to
a corresponding Region with the same name on the server, you can declare the `@ClientRegion` mapping annotation
In any case, if you want to create a local-only client Region where the data is not going to be distributed back to
a corresponding Region on the server with the same name, you can declare the `@ClientRegion` mapping annotation
and set the `shortcut` attribute to `ClientRegionShortcut.LOCAL`, as follows:
.Spring `ClientCache` application with a local-only, client Region
@@ -1190,11 +1150,185 @@ class ClientLocalEntityType { .. }
All Region type-specific annotations provide additional attributes that are both common across Region types
as well as specific to only that type of Region. For example, the `collocatedWith` and `redundantCopies` attributes
in the `PartitionRegion` annotation apply to `PARTITION` Regions only.
in the `PartitionRegion` annotation apply to server-side, `PARTITION` Regions only.
More details on {data-store-name} Region types can be found
{x-data-store-docs}/developing/region_options/region_types.html[here].
[[bootstrap-annotation-config-region-cluster-defined]]
=== Configured Cluster-defined Regions
In addition to the `@EnableEntityDefinedRegions` annotation, {sdg-name} also provides the inverse annotation,
`@EnableClusterDefinedRegions`. Rather than basing your Regions on the entity classes defined and driven from
your application use cases (UC) and requirements (the most common and logical approach), alternatively, you can
declare your Regions from the Regions already defined in the cluster to which your `ClientCache` application
will connect.
This allows you to centralize your configuration using the cluster of servers as the primary source of data definitions
and ensure that all client applications of the cluster have a consistent configuration. This is particularly useful
when quickly scaling up a large number instances of the same client application to handle the increased load
in a cloud-managed environment.
The idea is, rather than the client application(s) driving the data dictionary, the user defines Regions
using {data-store-name}'s _Gfsh_ CLI shell tool. This has the added advantage that when additional peers are added
to the cluster, they too will also have and share the same configuration since it is remembered by {data-store-name}'s
_Cluster Configuration Service_.
By way of example, a user might defined a Region in _Gfsh_, as follows:
.Defining a Region with Gfsh
[source,txt]
----
gfsh>create region --name=Books --type=PARTITION
Member | Status
--------- | --------------------------------------
ServerOne | Region "/Books" created on "ServerOne"
ServerTwo | Region "/Books" created on "ServerTwo"
gfsh>list regions
List of regions
---------------
Books
gfsh>describe region --name=/Books
..........................................................
Name : Books
Data Policy : partition
Hosting Members : ServerTwo
ServerOne
Non-Default Attributes Shared By Hosting Members
Type | Name | Value
------ | ----------- | ---------
Region | size | 0
| data-policy | PARTITION
----
With {data-store-name}'s _Cluster Configuration Service_, any additional peer members added to the cluster of servers
to handle the increased load (on the backend) will also have the same configuration, for example:
.Adding an additional peer member to the cluster
[source,txt]
----
gfsh>list members
Name | Id
--------- | ----------------------------------------------
Locator | 10.0.0.121(Locator:68173:locator)<ec><v0>:1024
ServerOne | 10.0.0.121(ServerOne:68242)<v3>:1025
ServerTwo | 10.0.0.121(ServerTwo:68372)<v4>:1026
gfsh>start server --name=ServerThree --log-level=config --server-port=41414
Starting a Geode Server in /Users/you/geode/cluster/ServerThree...
...
Server in /Users/you/geode/cluster/ServerThree... on 10.0.0.121[41414] as ServerThree is currently online.
Process ID: 68467
Uptime: 3 seconds
Geode Version: 1.2.1
Java Version: 1.8.0_152
Log File: /Users/you/geode/cluster/ServerThree/ServerThree.log
JVM Arguments: -Dgemfire.default.locators=10.0.0.121[10334]
-Dgemfire.use-cluster-configuration=true
-Dgemfire.start-dev-rest-api=false
-Dgemfire.log-level=config
-XX:OnOutOfMemoryError=kill -KILL %p
-Dgemfire.launcher.registerSignalHandlers=true
-Djava.awt.headless=true
-Dsun.rmi.dgc.server.gcInterval=9223372036854775806
Class-Path: /Users/you/geode/cluster/apache-geode-1.2.1/lib/geode-core-1.2.1.jar
:/Users/you/geode/cluster/apache-geode-1.2.1/lib/geode-dependencies.jar
gfsh>list members
Name | Id
----------- | ----------------------------------------------
Locator | 10.0.0.121(Locator:68173:locator)<ec><v0>:1024
ServerOne | 10.0.0.121(ServerOne:68242)<v3>:1025
ServerTwo | 10.0.0.121(ServerTwo:68372)<v4>:1026
ServerThree | 10.0.0.121(ServerThree:68467)<v5>:1027
gfsh>describe member --name=ServerThree
Name : ServerThree
Id : 10.0.0.121(ServerThree:68467)<v5>:1027
Host : 10.0.0.121
Regions : Books
PID : 68467
Groups :
Used Heap : 37M
Max Heap : 3641M
Working Dir : /Users/you/geode/cluster/ServerThree
Log file : /Users/you/geode/cluster/ServerThree/ServerThree.log
Locators : 10.0.0.121[10334]
Cache Server Information
Server Bind :
Server Port : 41414
Running : true
Client Connections : 0
----
As you can see, "ServerThree" now has the "Books" Region. If the any or all of the server go down, they will have
the same configuration along with the "Books" Region when they come back up.
On the client-side, many Book Store client application instances might be started to process books against
the Book Store online service. The "Books" Region might be 1 of many different Regions needed to implement
the Book Store application service. Rather than have to create and configure each Region individually, {sdg-acronym}
conveniently allows the client application Regions to be defined from the cluster, as follows:
.Defining Client Regions from the Cluster with `@EnableClusterDefinedRegions`
[source,java]
----
@ClientCacheApplication
@EnableClusterDefinedRegions
class BookStoreClientApplication {
public static void main(String[] args) {
....
}
...
}
----
NOTE: `@EnableClusterDefinedRegions` can only used on the client.
TIP: You can use the `clientRegionShortcut` annotation attribute to control the type of Region created on the client.
By default, a client `PROXY` Region is created. Set `clientRegionShortcut` to `ClientRegionShortcut.CACHING_PROXY`
to implement "_near caching_". This setting applies to all client Regions created from Cluster-defined Regions.
If you want to control individual settings (like data policy) of the client Regions created from Regions defined
on the Cluster, then you can implement a
{sdg-javadoc}/org/springframework/data/gemfire/config/annotation/RegionConfigurer.html[`RegionConfigurer`]
with custom logic based on the Region name.
Then, it becomes a simple matter to use the "Books" Region in your application. You can inject the "Books" Region
directly, as follows:
.Using the "Books" Region
[source,java]
----
@org.springframework.stereotype.Repository
class BooksDataAccessObject {
@Resource(name = "Books")
private Region<ISBN, Book> books;
// implement CRUD and queries with the "Books" Region
}
----
Or, even define a Spring Data Repository definition based on the application domain type (entity), `Book`,
mapped to the "Books" Region, as follows:
.Using the "Books" Region with a SD Repository
[source,java]
----
interface BookRepository extends CrudRepository<Book, ISBN> {
...
}
----
You can then either inject your custom `BooksDataAccessObject` or the `BookRepository` into your application service
components to carry out whatever business function required.
[[bootstrap-annotation-config-region-eviction]]
=== Configuring Eviction
@@ -1942,6 +2076,51 @@ with very little effort:
See the https://docs.spring.io/spring-data/gemfire/docs/current/api/index.html?org/springframework/data/gemfire/config/annotation/EnableClusterConfiguration.html[`@EnableClusterConfiguration` annotation
Javadoc] for more details.
[[bootstrap-annotation-config-ssl]]
== Configuring SSL
Equally important to serializing data to be transferred over the wire is securing the data while in transit.
Of course, the common way to accomplish this in Java is by using the Secure Sockets Extension (SSE)
and Transport Layer Security (TLS).
To enable SSL, annotate your application class with `@EnableSsl`, as follows:
.Spring `ClientCache` application with SSL enabled
[source, java]
----
@SpringBootApplication
@ClientCacheApplication
@EnableSsl
public class ClientApplication { .. }
----
Then you need to set the necessary SSL configuration attributes or properties: keystores, usernames/passwords, and so on.
You can individually configure different {data-store-name} components (`GATEWAY`, `HTTP`, `JMX`, `LOCATOR`, and `SERVER`)
with SSL, or you can collectively configure them to use SSL by using the `CLUSTER` enumerated value.
You can specify which {data-store-name} components the SSL configuration settings should applied by using
the nested `@EnableSsl` annotation, `components` attribute with enumerated values from the `Component` enum,
as follows:
.Spring `ClientCache` application with SSL enabled by component
[source, java]
----
@SpringBootApplication
@ClientCacheApplication
@EnableSsl(components = { GATEWAY, LOCATOR, SERVER })
public class ClientApplication { .. }
----
In addition, you can also specify component-level SSL configuration (`ciphers`, `protocols` and `keystore`/`truststore`
information) by using the corresponding annotation attribute or associated configuration properties.
See the https://docs.spring.io/spring-data/gemfire/docs/current/api/org/springframework/data/gemfire/config/annotation/EnableSsl.html[`@EnableSsl` annotation Javadoc]
for more details.
More details on {data-store-name} SSL support can be found
{x-data-store-docs}/managing/security/ssl_overview.html[here].
[[bootstrap-annotation-config-security]]
== Configuring Security
@@ -2178,9 +2357,10 @@ to accomplishing the function that the annotation provides:
`@EnableSecurity` annotation, as described in "`<<bootstrap-annotation-config-security>>`".)
* `@EnableAutoRegionLookup`: Not recommended. Essentially, this annotation supports finding Regions defined in external
configuration metadata (such as `cache.xml` or Cluster Configuration when applied to a server) and automatically
registers those Regions as beans in the Spring container. Users should generally prefer Spring configuration when
using Spring and {sdg-name}. See "`<<bootstrap-annotation-config-regions>>`" and "`<<bootstrap-annotation-config-cluster>>`"
instead.
registers those Regions as beans in the Spring container. This annotation corresponds with the `<gfe:auto-region-lookup>`
element in SDG's XML namespace. More details can found <<bootstrap:region:lookup:auto, here>>. Users should generally
prefer Spring configuration when using Spring and {sdg-name}. See "`<<bootstrap-annotation-config-regions>>`"
and "`<<bootstrap-annotation-config-cluster>>`" instead.
* `@EnableBeanFactoryLocator`: Enables the {sdg-acronym} `GemfireBeanFactoryLocator` feature, which is only useful
when using external configuration metadata (for example, `cache.xml`). For example, if you define a `CacheLoader` on
a Region defined in `cache.xml`, you can still autowire this `CacheLoader` with, say, a relational database

View File

@@ -1,5 +1,5 @@
[[bootstrap]]
= Bootstrapping {data-store-name} with the Spring container
= Bootstrapping {data-store-name} with the Spring Container
{sdg-name} provides full configuration and initialization of the {data-store-name} In-Memory Data Grid (IMDG)
using the Spring IoC container. The framework includes several classes to help simplify the configuration of

View File

@@ -61,7 +61,7 @@ or setup infrastructure.
[[bootstrap:region:lookup:auto]]
== Auto Region Lookup
"`auto-lookup`" lets you import all Regions defined in a {data-store-name} native `cache.xml` file into
`auto-region-lookup` lets you import all Regions defined in a {data-store-name} native `cache.xml` file into
a Spring `ApplicationContext` when you use the `cache-xml-location` attribute on the `<gfe:cache>` element.
For instance, consider the following `cache.xml` file:
@@ -105,7 +105,7 @@ It is important to realize that {sdg-name} uses a Spring
to post-process the cache after it is both created and initialized to determine the Regions defined in {data-store-name}
to add as beans in the Spring `ApplicationContext`.
You may inject these "`auto-looked-up`" Regions as you would any other bean defined in the Spring `ApplicationContext`,
You may inject these "auto-looked-up" Regions as you would any other bean defined in the Spring `ApplicationContext`,
with one exception: You may need to define a `depends-on` association with the '`gemfireCache`' bean, as follows:
[source,java]
@@ -138,7 +138,7 @@ If you declare your components by using Spring XML config, then you would do the
----
Doing so ensures that the {data-store-name} cache and all the Regions defined in `cache.xml` get created before
any components with auto-wire references when using the new `<gfe:auto-region-lookup>` element.
any components with auto-wire references when using the `<gfe:auto-region-lookup>` element.
[[bootstrap:region:overview]]
== Configuring Regions

View File

@@ -15,7 +15,9 @@ package org.springframework.data.gemfire.client;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.client.ClientRegionFactory;
import org.apache.geode.cache.client.ClientRegionShortcut;
@@ -33,34 +35,42 @@ import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* A Spring {@link BeanFactoryPostProcessor} used to register a Client Region Proxy bean for each Region
* accessible to a GemFire DataSource. If the Region is already defined, the bean definition will not be overridden.
* A Spring {@link BeanFactoryPostProcessor} used to register a Client Region beans for each Region accessible to
* an Apache Geode or Pivotal GemFire DataSource. If the Region is already defined, the bean definition
* will not be overridden.
*
* @author David Turanski
* @author John Blum
* @see org.springframework.beans.factory.config.BeanFactoryPostProcessor
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory
* @see org.springframework.data.gemfire.function.execution.GemfireOnServersFunctionTemplate
* @see ListRegionsOnServerFunction
* @see org.apache.geode.cache.Region
* @see org.apache.geode.cache.client.ClientCache
* @see org.apache.geode.cache.client.ClientRegionFactory
* @see org.apache.geode.cache.client.ClientRegionShortcut
* @see org.apache.geode.cache.execute.Function
* @see org.apache.geode.management.internal.cli.functions.GetRegionsFunction
* @see org.springframework.beans.factory.config.BeanFactoryPostProcessor
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory
* @see org.springframework.data.gemfire.client.function.ListRegionsOnServerFunction
* @see org.springframework.data.gemfire.function.execution.GemfireOnServersFunctionTemplate
* @see ListRegionsOnServerFunction
* @since 1.2.0
*/
public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor {
protected final Logger logger = LoggerFactory.getLogger(getClass());
private static final ClientRegionShortcut DEFAULT_CLIENT_REGION_SHORTCUT = ClientRegionShortcut.PROXY;
private final ClientCache clientCache;
private ClientRegionShortcut clientRegionShortcut = DEFAULT_CLIENT_REGION_SHORTCUT;
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Constructs an instance of the GemfireDataSourcePostProcessor BeanFactoryPostProcessor class initialized
* with the specified GemFire ClientCache instance for creating client PROXY Regions for all data Regions
* configured in the GemFire cluster.
* Constructs an instance of the {@link GemfireDataSourcePostProcessor} {@link BeanFactoryPostProcessor} class
* initialized * with the specified {@link ClientCache} instance for creating client {@link Region Regions}
* for all data {@link Region Regions} configured in the cluster.
*
* @param clientCache the GemFire ClientCache instance.
* @param clientCache reference to the {@link ClientCache} instance.
* @throws IllegalArgumentException if {@link ClientCache} is {@literal null}.
* @see org.apache.geode.cache.client.ClientCache
*/
public GemfireDataSourcePostProcessor(ClientCache clientCache) {
@@ -70,13 +80,57 @@ public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor
this.clientCache = clientCache;
}
/**
* Returns a reference to the {@link ClientCache}.
*
* @return a reference to the {@link ClientCache}.
* @see org.apache.geode.cache.client.ClientCache
*/
protected ClientCache getClientCache() {
return this.clientCache;
}
/**
* Set the data policy used to configure the client {@link Region}.
*
* @param clientRegionShortcut {@link ClientRegionShortcut} used to define the data policy
* used by the client {@link Region}.
* @see org.apache.geode.cache.client.ClientRegionShortcut
*/
public void setClientRegionShortcut(ClientRegionShortcut clientRegionShortcut) {
this.clientRegionShortcut = clientRegionShortcut;
}
/**
* Returns the data policy used to configure the client {@link Region}.
*
* @return the configured {@link ClientRegionShortcut} used to define the data policy
* used by the client {@link Region}.
* @see org.apache.geode.cache.client.ClientRegionShortcut
* @see java.util.Optional
*/
public Optional<ClientRegionShortcut> getClientRegionShortcut() {
return Optional.ofNullable(this.clientRegionShortcut);
}
/**
* Returns a reference to the configured {@link Logger} used to log messages.
*
* @return a reference to the configured {@link Logger}.
* @see org.slf4j.Logger
*/
protected Logger getLogger() {
return this.logger;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory)
* @see org.springframework.beans.factory.config.BeanFactoryPostProcessor
* #postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory)
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
createClientRegionProxies(beanFactory, regionNames());
createClientProxyRegions(beanFactory, regionNames());
}
// TODO: remove this logic and delegate to o.s.d.g.config.remote.GemfireAdminOperations
@@ -86,6 +140,7 @@ public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor
return execute(new ListRegionsOnServerFunction());
}
catch (Exception ignore) {
try {
Object results = execute(new GetRegionsFunction());
@@ -106,7 +161,7 @@ public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor
return regionNames;
}
catch (Exception cause) {
log("Failed to determine the Regions available on the Server: %n%1$s", cause);
logDebug("Failed to determine the Regions available on the Server: %n%1$s", cause);
return Collections.emptyList();
}
}
@@ -114,51 +169,71 @@ public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor
@SuppressWarnings("unchecked")
<T> T execute(Function gemfireFunction, Object... arguments) {
return new GemfireOnServersFunctionTemplate(this.clientCache).executeAndExtract(gemfireFunction, arguments);
return new GemfireOnServersFunctionTemplate(getClientCache()).executeAndExtract(gemfireFunction, arguments);
}
boolean containsRegionInformation(Object results) {
return results instanceof Object[] && ((Object[]) results).length > 0
&& ((Object[]) results)[0] instanceof RegionInformation;
}
void createClientRegionProxies(ConfigurableListableBeanFactory beanFactory, Iterable<String> regionNames) {
void createClientProxyRegions(ConfigurableListableBeanFactory beanFactory, Iterable<String> regionNames) {
if (regionNames.iterator().hasNext()) {
ClientRegionShortcut resolvedClientRegionShortcut = getClientRegionShortcut()
.orElse(DEFAULT_CLIENT_REGION_SHORTCUT);
ClientRegionFactory<?, ?> clientRegionFactory =
this.clientCache.createClientRegionFactory(ClientRegionShortcut.PROXY);
this.clientCache.createClientRegionFactory(resolvedClientRegionShortcut);
for (String regionName : regionNames) {
boolean createRegion = true;
if (beanFactory.containsBean(regionName)) {
Object existingBean = beanFactory.getBean(regionName);
Object bean = beanFactory.getBean(regionName);
if (logger.isWarnEnabled()) {
logger.warn("Cannot create a client PROXY Region bean named {}; A bean with name {} having type {} already exists",
regionName, regionName, ObjectUtils.nullSafeClassName(existingBean));
}
createRegion = false;
}
if (createRegion) {
log("Creating Region bean with name '%s'...", regionName);
beanFactory.registerSingleton(regionName, clientRegionFactory.create(regionName));
logWarn("Cannot create a client {} Region bean named {}; A bean with name {} having type {} already exists",
resolvedClientRegionShortcut.name(), regionName, regionName, ObjectUtils.nullSafeClassName(bean));
}
else {
log("A Region with name '%s' is already defined", regionName);
logInfo("Creating Region bean with name {}...", regionName);
beanFactory.registerSingleton(regionName, clientRegionFactory.create(regionName));
}
}
}
}
void log(String message, Object... arguments) {
void logDebug(String message, Object... arguments) {
Logger logger = getLogger();
if (logger.isDebugEnabled()) {
logger.debug(String.format(message, arguments));
}
}
void logInfo(String message, Object... arguments) {
Logger logger = getLogger();
if (logger.isInfoEnabled()) {
logger.info(message, arguments);
}
}
void logWarn(String message, Object... arguments) {
Logger logger = getLogger();
if (logger.isWarnEnabled()) {
logger.warn(message, arguments);
}
}
public GemfireDataSourcePostProcessor using(ClientRegionShortcut clientRegionShortcut) {
setClientRegionShortcut(clientRegionShortcut);
return this;
}
}

View File

@@ -17,7 +17,7 @@
package org.springframework.data.gemfire.config.annotation;
import java.util.Map;
import java.lang.annotation.Annotation;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -30,7 +30,7 @@ import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.env.Environment;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport;
import org.springframework.data.gemfire.config.support.AutoRegionLookupBeanPostProcessor;
@@ -73,15 +73,12 @@ public class AutoRegionLookupConfiguration extends AbstractAnnotationConfigSuppo
private static final AtomicBoolean AUTO_REGION_LOOKUP_BEAN_POST_PROCESSOR_REGISTERED = new AtomicBoolean(false);
private Environment environment;
private ExpressionParser spelParser = new SpelExpressionParser();
private StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
/* (non-Javadoc) */
@Override
protected Class getAnnotationType() {
protected Class<? extends Annotation> getAnnotationType() {
return EnableAutoRegionLookup.class;
}
@@ -99,8 +96,7 @@ public class AutoRegionLookupConfiguration extends AbstractAnnotationConfigSuppo
ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
this.evaluationContext.setTypeLocator(new StandardTypeLocator(
configurableBeanFactory.getBeanClassLoader()));
this.evaluationContext.setTypeLocator(new StandardTypeLocator(configurableBeanFactory.getBeanClassLoader()));
Optional.ofNullable(configurableBeanFactory.getConversionService())
.ifPresent(conversionService ->
@@ -114,24 +110,23 @@ public class AutoRegionLookupConfiguration extends AbstractAnnotationConfigSuppo
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<String, Object> enableAutoRegionLookupAttributes =
importingClassMetadata.getAnnotationAttributes(EnableAutoRegionLookup.class.getName());
AnnotationAttributes enableAutoRegionLookupAttributes = getAnnotationAttributes(importingClassMetadata);
Optional.ofNullable(resolveProperty(propertyName("enable-auto-region-lookup"),
(Boolean) enableAutoRegionLookupAttributes.get("enabled")))
Optional.ofNullable(resolveProperty(cacheProperty("enable-auto-region-lookup"),
resolveProperty(propertyName("enable-auto-region-lookup"),
enableAutoRegionLookupAttributes.getBoolean("enabled"))))
.filter(Boolean.TRUE::equals)
.ifPresent(enabled -> registerAutoRegionLookupBeanPostProcessor(registry));
}
/**
* (non-Javadoc)
*
* This method was used to support Spring property placeholders and SpEL Expressions
* in the {@link EnableAutoRegionLookup#enabled()} attribute. However, this required the attribute to be
* of type {@link String}, which violates type-safety. We are favoring type-safety over configuration
* flexibility and offering alternative means to achieve flexible and dynamic configuration, e.g. properties
* This method is used to support Spring property placeholders and SpEL Expressions
* in the {@link EnableAutoRegionLookup#enabled()} attribute. However, this requires the attribute to be of type
* {@link String}, which violates type-safety. We are favoring type-safety over configuration flexibility
* and offering alternative means to achieve flexible and dynamic configuration, e.g. properties
* from an {@literal application.properties} file.
*/
@SuppressWarnings("unused")
private boolean isEnabled(String enabled) {
enabled = StringUtils.trimWhitespace(enabled);
@@ -139,7 +134,8 @@ public class AutoRegionLookupConfiguration extends AbstractAnnotationConfigSuppo
if (!Boolean.parseBoolean(enabled)) {
try {
// try parsing as a SpEL expression...
return this.spelParser.parseExpression(enabled).getValue(this.evaluationContext, Boolean.TYPE);
return Boolean.TRUE.equals(this.spelParser.parseExpression(enabled)
.getValue(this.evaluationContext, Boolean.TYPE));
}
catch (EvaluationException ignore) {
return false;
@@ -153,10 +149,10 @@ public class AutoRegionLookupConfiguration extends AbstractAnnotationConfigSuppo
return DEFAULT_ENABLED;
}
/* (non-Javadoc) */
private void registerAutoRegionLookupBeanPostProcessor(BeanDefinitionRegistry registry) {
if (AUTO_REGION_LOOKUP_BEAN_POST_PROCESSOR_REGISTERED.compareAndSet(false, true)) {
AbstractBeanDefinition autoRegionLookupBeanPostProcessor = BeanDefinitionBuilder
.rootBeanDefinition(AutoRegionLookupBeanPostProcessor.class)
.setRole(AbstractBeanDefinition.ROLE_INFRASTRUCTURE)

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2018 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.data.gemfire.config.annotation;
import java.lang.annotation.Annotation;
import java.util.Optional;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.apache.shiro.util.Assert;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.gemfire.client.GemfireDataSourcePostProcessor;
import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport;
import org.springframework.data.gemfire.util.CacheUtils;
/**
* The {@link ClusterDefinedRegionsConfiguration} class configures client Proxy-based {@link Region Regions}
* for all {@link Region Regions} defined in the cluster to which the cache client is connected.
*
* @author John Blum
* @see org.apache.geode.cache.Region
* @see org.apache.geode.cache.client.ClientCache
* @see org.apache.geode.cache.client.ClientRegionShortcut
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.context.annotation.ImportAware
* @see org.springframework.core.annotation.AnnotationAttributes
* @see org.springframework.core.type.AnnotationMetadata
* @see org.springframework.data.gemfire.client.GemfireDataSourcePostProcessor
* @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport
* @since 2.1.0
*/
@Configuration
public class ClusterDefinedRegionsConfiguration extends AbstractAnnotationConfigSupport implements ImportAware {
protected static final ClientRegionShortcut DEFAULT_CLIENT_REGION_SHORTCUT = ClientRegionShortcut.PROXY;
private ClientRegionShortcut clientRegionShortcut = DEFAULT_CLIENT_REGION_SHORTCUT;
@Override
protected Class<? extends Annotation> getAnnotationType() {
return EnableClusterDefinedRegions.class;
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
AnnotationAttributes enableClusterDefinedRegionsAttributes = getAnnotationAttributes(importMetadata);
setClientRegionShortcut(enableClusterDefinedRegionsAttributes.getEnum("clientRegionShortcut"));
}
protected void setClientRegionShortcut(ClientRegionShortcut clientRegionShortcut) {
this.clientRegionShortcut = clientRegionShortcut;
}
protected Optional<ClientRegionShortcut> getClientRegionShortcut() {
return Optional.ofNullable(this.clientRegionShortcut);
}
protected ClientRegionShortcut resolveClientRegionShortcut() {
return getClientRegionShortcut().orElse(DEFAULT_CLIENT_REGION_SHORTCUT);
}
@Bean
public GemfireDataSourcePostProcessor gemfireDataSourcePostProcessor(GemFireCache gemfireCache) {
Assert.isTrue(CacheUtils.isClient(gemfireCache), "GemFireCache must be an instance of ClientCache");
return new GemfireDataSourcePostProcessor((ClientCache) gemfireCache).using(resolveClientRegionShortcut());
}
}

View File

@@ -24,20 +24,19 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.apache.geode.cache.Region;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.gemfire.PeerRegionFactoryBean;
/**
* The {@link EnableAutoRegionLookup} annotation configures a Spring {@link org.springframework.context.annotation.Configuration}
* annotated class with the ability to automatically look up and register GemFire {@link org.apache.geode.cache.Region Regions}
* which may have be defined in {@literal cache.xml} or by using GemFire's Cluster Configuration Service.
*
* This annotation defines the {@code enabled} attribute to allow users to dynamically change the behavior
* of auto {@link org.apache.geode.cache.Region} lookup at application configuration time using either a SpEL
* expression or a Spring property placeholder.
* The {@link EnableAutoRegionLookup} annotation configures a Spring {@link Configuration} annotated class
* with the ability to automatically look up and register any Apache Geode or Pivotal GemFire {@link Region Regions}
* which may have be defined in {@literal cache.xml} or by using the Cluster Configuration Service.
*
* @author John Blum
* @see PeerRegionFactoryBean#setLookupEnabled(Boolean)
* @see org.apache.geode.cache.Region
* @see org.springframework.context.annotation.Import
* @see org.springframework.data.gemfire.ResolvableRegionFactoryBean#setLookupEnabled(Boolean)
* @see org.springframework.data.gemfire.config.annotation.AutoRegionLookupConfiguration
* @since 1.9.0
*/
@@ -54,7 +53,7 @@ public @interface EnableAutoRegionLookup {
*
* Defaults to {@literal true}.
*
* Use the {@literal spring.data.gemfire.enable-auto-region-lookup} in {@literal application.properties}
* Use the {@literal spring.data.gemfire.cache.enable-auto-region-lookup} in {@literal application.properties}
* to dynamically customize this configuration setting.
*/
boolean enabled() default true;

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2018 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.data.gemfire.config.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* The {@link EnableClusterDefinedRegions} annotation marks a Spring {@link Configuration @Configuration} application
* annotated class to enable the creation of client Proxy-based {@link Region Regions} for all {@link Region Regions}
* defined in an Apache Geode/Pivotal GemFire cluster.
*
* @author John Blum
* @see org.apache.geode.cache.Region
* @see org.apache.geode.cache.client.ClientCache
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.context.annotation.Import
* @see org.springframework.data.gemfire.config.annotation.ClusterDefinedRegionsConfiguration
* @since 2.1.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import(ClusterDefinedRegionsConfiguration.class)
@SuppressWarnings("unused")
public @interface EnableClusterDefinedRegions {
/**
* Configures the client {@link Region} data management policy for all client {@link Region Regions} created from
* the corresponding server-side {@link Region}.
*
* Defaults to {@link ClientRegionShortcut#PROXY}.
*
* @see org.apache.geode.cache.client.ClientRegionShortcut
*/
ClientRegionShortcut clientRegionShortcut() default ClientRegionShortcut.PROXY;
}

View File

@@ -47,6 +47,7 @@ import org.springframework.util.StringUtils;
* @see org.springframework.beans.factory.BeanFactory
* @see org.springframework.beans.factory.BeanFactoryAware
* @see org.springframework.beans.factory.config.BeanPostProcessor
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory
* @since 1.5.0
*/
public class AutoRegionLookupBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
@@ -54,7 +55,13 @@ public class AutoRegionLookupBeanPostProcessor implements BeanPostProcessor, Bea
private ConfigurableListableBeanFactory beanFactory;
/**
* {@inheritDoc}
* Sets a reference to the configured Spring {@link BeanFactory}.
*
* @param beanFactory configured Spring {@link BeanFactory}.
* @throws IllegalArgumentException if the given {@link BeanFactory} is not an instance of
* {@link ConfigurableListableBeanFactory}.
* @see org.springframework.beans.factory.BeanFactoryAware
* @see org.springframework.beans.factory.BeanFactory
*/
@Override
@SuppressWarnings("all")
@@ -67,15 +74,18 @@ public class AutoRegionLookupBeanPostProcessor implements BeanPostProcessor, Bea
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
/* (non-Javadoc) */
/**
* Returns a reference to the containing Spring {@link BeanFactory}.
*
* @return a reference to the containing Spring {@link BeanFactory}.
* @throws IllegalStateException if the {@link BeanFactory} was not configured.
* @see org.springframework.beans.factory.BeanFactory
*/
protected ConfigurableListableBeanFactory getBeanFactory() {
return Optional.ofNullable(this.beanFactory).orElseThrow(() ->
newIllegalStateException("BeanFactory was not properly configured"));
return Optional.ofNullable(this.beanFactory)
.orElseThrow(() -> newIllegalStateException("BeanFactory was not properly configured"));
}
/**
* {@inheritDoc}
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
@@ -86,12 +96,10 @@ public class AutoRegionLookupBeanPostProcessor implements BeanPostProcessor, Bea
return bean;
}
/* (non-Javadoc) */
void registerCacheRegionsAsBeans(GemFireCache cache) {
cache.rootRegions().forEach(this::registerCacheRegionAsBean);
}
/* (non-Javadoc) */
void registerCacheRegionAsBean(Region<?, ?> region) {
if (region != null) {
@@ -108,7 +116,6 @@ public class AutoRegionLookupBeanPostProcessor implements BeanPostProcessor, Bea
}
}
/* (non-Javadoc) */
String getBeanName(Region region) {
return Optional.ofNullable(region.getFullPath())
@@ -117,7 +124,6 @@ public class AutoRegionLookupBeanPostProcessor implements BeanPostProcessor, Bea
.orElseGet(region::getName);
}
/* (non-Javadoc) */
Set<Region<?, ?>> nullSafeSubregions(Region<?, ?> parentRegion) {
return Optional.ofNullable(parentRegion.subregions(false)).orElse(Collections.emptySet());
}

View File

@@ -16,19 +16,18 @@
package org.springframework.data.gemfire.client;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -52,14 +51,10 @@ import org.apache.geode.cache.client.ClientRegionShortcut;
import org.apache.geode.cache.execute.Function;
import org.apache.geode.management.internal.cli.domain.RegionInformation;
import org.apache.geode.management.internal.cli.functions.GetRegionsFunction;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.data.gemfire.client.function.ListRegionsOnServerFunction;
import org.springframework.data.gemfire.util.RegionUtils;
@@ -84,20 +79,13 @@ public class GemfireDataSourcePostProcessorTest {
@Mock
private ClientCache mockClientCache;
@Rule
public ExpectedException exception = ExpectedException.none();
private RegionInformation newRegionInformation(Region<?, ?> region) {
return new RegionInformation(region, false);
}
@SuppressWarnings("unchecked")
private Region<Object, Object> mockRegion(String name) {
Region<Object, Object> mockRegion = mock(Region.class, name);
RegionAttributes<Object, Object> mockRegionAttributes =
mock(RegionAttributes.class, String.format("%1$s-RegionAttributes", name));
mock(RegionAttributes.class, String.format("%s-RegionAttributes", name));
when(mockRegion.getParentRegion()).thenReturn(null);
when(mockRegion.getFullPath()).thenReturn(RegionUtils.toRegionPath(name));
@@ -108,6 +96,43 @@ public class GemfireDataSourcePostProcessorTest {
return mockRegion;
}
private RegionInformation newRegionInformation(Region<?, ?> region) {
return new RegionInformation(region, false);
}
@Test
public void constructGemfireDataSourcePostProcessor() {
GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(mockClientCache);
assertThat(postProcessor).isNotNull();
assertThat(postProcessor.getClientCache()).isEqualTo(this.mockClientCache);
assertThat(postProcessor.getClientRegionShortcut().orElse(null)).isEqualTo(ClientRegionShortcut.PROXY);
assertThat(postProcessor.getLogger()).isNotNull();
}
@Test
public void setAndGetClientRegionShortcut() {
GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(this.mockClientCache);
assertThat(postProcessor).isNotNull();
assertThat(postProcessor.getClientRegionShortcut().orElse(null)).isEqualTo(ClientRegionShortcut.PROXY);
postProcessor.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY);
assertThat(postProcessor.getClientRegionShortcut().orElse(null))
.isEqualTo(ClientRegionShortcut.CACHING_PROXY);
postProcessor.setClientRegionShortcut(ClientRegionShortcut.LOCAL);
assertThat(postProcessor.getClientRegionShortcut().orElse(null)).isEqualTo(ClientRegionShortcut.LOCAL);
postProcessor.setClientRegionShortcut(null);
assertThat(postProcessor.getClientRegionShortcut().isPresent()).isFalse();
}
@Test
public void postProcessBeanFactoryCallsCreateClientRegionProxiesWithRegionNames() {
@@ -116,166 +141,152 @@ public class GemfireDataSourcePostProcessorTest {
ConfigurableListableBeanFactory mockBeanFactory =
mock(ConfigurableListableBeanFactory.class, "MockSpringBeanFactory");
List<String> testRegionNames = Collections.singletonList("Test");
String[] testRegionNames = { "Test" };
GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(this.mockClientCache) {
@Override
Iterable<String> regionNames() {
return testRegionNames;
return Arrays.asList(testRegionNames);
}
@Override void createClientRegionProxies(ConfigurableListableBeanFactory beanFactory, Iterable<String> regionNames) {
assertThat(beanFactory, is(sameInstance(mockBeanFactory)));
assertSame(testRegionNames, regionNames);
@Override
void createClientProxyRegions(ConfigurableListableBeanFactory beanFactory, Iterable<String> regionNames) {
assertThat(beanFactory).isSameAs(mockBeanFactory);
assertThat(regionNames).containsExactly(testRegionNames);
createClientRegionProxiesCalled.compareAndSet(false, true);
}
};
postProcessor.postProcessBeanFactory(mockBeanFactory);
assertThat(createClientRegionProxiesCalled.get(), is(true));
assertThat(createClientRegionProxiesCalled.get()).isTrue();
}
@Test
public void regionNamesWithListRegionsOnServerFunction() {
List<String> expectedRegionNames = Arrays.asList("ExampleOne", "ExampleTwo");
String[] expectedRegionNames = { "ExampleOne", "ExampleTwo" };
GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(this.mockClientCache) {
GemfireDataSourcePostProcessor postProcessor = spy(new GemfireDataSourcePostProcessor(this.mockClientCache));
@Override @SuppressWarnings("unchecked")
<T> T execute(Function gemfireFunction, Object... arguments) {
assertThat(gemfireFunction, is(instanceOf(ListRegionsOnServerFunction.class)));
return (T) expectedRegionNames;
}
};
doReturn(Arrays.asList(expectedRegionNames)).when(postProcessor)
.execute(isA(ListRegionsOnServerFunction.class), any());
Iterable<String> actualRegionNames = postProcessor.regionNames();
assertSame(expectedRegionNames, actualRegionNames);
assertThat(actualRegionNames).containsExactly(expectedRegionNames);
verify(postProcessor, times(1)).execute(isA(ListRegionsOnServerFunction.class));
verify(postProcessor, never()).execute(any(GetRegionsFunction.class));
}
@Test
@SuppressWarnings("unchecked")
public void regionNamesWithGetRegionsFunction() {
List<String> expectedRegionNames = Arrays.asList("ExampleOne", "ExampleTwo");
String[] expectedRegionNames = { "ExampleOne", "ExampleTwo" };
GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(this.mockClientCache) {
GemfireDataSourcePostProcessor postProcessor = spy(new GemfireDataSourcePostProcessor(this.mockClientCache));
@Override @SuppressWarnings("unchecked")
<T> T execute(Function gemfireFunction, Object... arguments) {
doThrow(new RuntimeException("fail")).when(postProcessor).execute(isA(ListRegionsOnServerFunction.class), any());
if (gemfireFunction instanceof ListRegionsOnServerFunction) {
throw new RuntimeException("fail");
}
else if (gemfireFunction instanceof GetRegionsFunction) {
return (T) Arrays.asList(newRegionInformation(mockRegion(expectedRegionNames.get(0))),
newRegionInformation(mockRegion(expectedRegionNames.get(1)))).toArray();
}
throw new IllegalArgumentException(String.format("GemFire Function [%1$s] with ID [%2$s] not registered",
gemfireFunction.getClass().getName(), gemfireFunction.getId()));
}
};
doAnswer(invocation ->
Arrays.stream(expectedRegionNames)
.map(this::mockRegion)
.map(this::newRegionInformation)
.collect(Collectors.toList())
.toArray()
).when(postProcessor).execute(isA(GetRegionsFunction.class), any());
List<String> actualRegionNames =
StreamSupport.stream(postProcessor.regionNames().spliterator(), false).collect(Collectors.toList());
assertThat(actualRegionNames.containsAll(expectedRegionNames), is(true));
assertThat(actualRegionNames).containsExactly(expectedRegionNames);
verify(postProcessor, times(1)).execute(isA(ListRegionsOnServerFunction.class));
verify(postProcessor, times(1)).execute(isA(GetRegionsFunction.class));
}
@Test
public void regionNamesWithGetRegionsFunctionReturningNoResults() {
GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(this.mockClientCache) {
GemfireDataSourcePostProcessor postProcessor = spy(new GemfireDataSourcePostProcessor(this.mockClientCache));
@Override @SuppressWarnings("unchecked") <T> T
execute(Function gemfireFunction, Object... arguments) {
if (gemfireFunction instanceof ListRegionsOnServerFunction) {
throw new RuntimeException("fail");
}
else if (gemfireFunction instanceof GetRegionsFunction) {
return null;
}
throw new IllegalArgumentException(String.format("GemFire Function (%1$s) with ID (%2$s) not registered",
gemfireFunction.getClass().getName(), gemfireFunction.getId()));
}
};
doThrow(new RuntimeException("fail")).when(postProcessor).execute(isA(ListRegionsOnServerFunction.class), any());
doReturn(null).when(postProcessor).execute(isA(GetRegionsFunction.class), any());
Iterable<String> actualRegionNames = postProcessor.regionNames();
assertThat(actualRegionNames, is(not(nullValue())));
assertThat(actualRegionNames.iterator(), (is(not(nullValue()))));
assertThat(actualRegionNames.iterator().hasNext(), is(false));
assertThat(actualRegionNames).isNotNull();
assertThat(actualRegionNames).isEmpty();
verify(postProcessor, times(1)).execute(isA(ListRegionsOnServerFunction.class));
verify(postProcessor, times(1)).execute(isA(GetRegionsFunction.class));
}
@Test
public void regionNamesWithGetRegionsFunctionThrowingException() {
AtomicBoolean logMethodCalled = new AtomicBoolean(false);
GemfireDataSourcePostProcessor postProcessor = spy(new GemfireDataSourcePostProcessor(this.mockClientCache));
GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(this.mockClientCache) {
doAnswer(invocation -> {
@Override @SuppressWarnings("unchecked") <T> T
execute(Function gemfireFunction, Object... arguments) {
throw new IllegalArgumentException(String.format("GemFire Function (%1$s) with ID (%2$s) not registered",
gemfireFunction.getClass().getName(), gemfireFunction.getId()));
}
Function function = invocation.getArgument(0);
@Override
void log(final String message, final Object... arguments) {
assertThat(message.startsWith("Failed to determine the Regions available on the Server:"), is(true));
logMethodCalled.compareAndSet(false, true);
}
};
throw new IllegalArgumentException(String.format("Function [%1$s] with ID [%2$s] not registered",
function.getClass().getName(), function.getId()));
}).when(postProcessor).execute(any(Function.class), any());
Iterable<String> actualRegionNames = postProcessor.regionNames();
assertThat(actualRegionNames, is(not(nullValue())));
assertThat(actualRegionNames.iterator(), (is(not(nullValue()))));
assertThat(actualRegionNames.iterator().hasNext(), is(false));
assertThat(logMethodCalled.get(), is(true));
assertThat(actualRegionNames).isNotNull();
assertThat(actualRegionNames).isEmpty();
verify(postProcessor, times(1)).execute(isA(ListRegionsOnServerFunction.class));
verify(postProcessor, times(1)).execute(isA(GetRegionsFunction.class));
verify(postProcessor, times(1))
.logDebug(startsWith("Failed to determine the Regions available on the Server:"), any());
}
@Test
public void containsRegionInformationIsTrue() {
assertThat(new GemfireDataSourcePostProcessor(this.mockClientCache)
.containsRegionInformation(new Object[] { newRegionInformation(mockRegion("Example")) }),
is(true));
}
@Test
public void containsRegionInformationWithListOfRegionInformationIsFalse() {
assertThat(new GemfireDataSourcePostProcessor(this.mockClientCache)
.containsRegionInformation(Collections.singletonList(newRegionInformation(mockRegion("Example")))),
is(false));
}
@Test
public void containsRegionInformationWithNonEmptyArrayContainingNonRegionInformationIsFalse() {
assertThat(new GemfireDataSourcePostProcessor(this.mockClientCache)
.containsRegionInformation(new Object[] { "test" }), is(false));
.containsRegionInformation(new Object[] { newRegionInformation(mockRegion("Example")) }))
.isTrue();
}
@Test
public void containsRegionInformationWithEmptyArrayIsFalse() {
assertThat(new GemfireDataSourcePostProcessor(this.mockClientCache)
.containsRegionInformation(new Object[0]), is(false));
.containsRegionInformation(new Object[0]))
.isFalse();
}
@Test
public void containsRegionInformationWithListOfRegionInformationIsFalse() {
assertThat(new GemfireDataSourcePostProcessor(this.mockClientCache)
.containsRegionInformation(Collections.singletonList(newRegionInformation(mockRegion("Example")))))
.isFalse();
}
@Test
public void containsRegionInformationWithNonEmptyArrayContainingNonRegionInformationIsFalse() {
assertThat(new GemfireDataSourcePostProcessor(this.mockClientCache)
.containsRegionInformation(new Object[] { "test" }))
.isFalse();
}
@Test
public void containsRegionInformationWithNullIsFalse() {
assertThat(new GemfireDataSourcePostProcessor(this.mockClientCache).containsRegionInformation(null),
is(false));
assertThat(new GemfireDataSourcePostProcessor(this.mockClientCache).containsRegionInformation(null))
.isFalse();
}
@Test
@SuppressWarnings("unchecked")
public void createClientRegionProxies() {
public void createClientProxyRegionsIsSuccessful() {
ClientCache mockClientCache = mock(ClientCache.class, "MockGemFireClientCache");
@@ -287,17 +298,19 @@ public class GemfireDataSourcePostProcessorTest {
Region mockRegionOne = mock(Region.class, "MockGemFireRegionOne");
Region mockRegionTwo = mock(Region.class, "MockGemFireRegionTwo");
final Map<String, Region<?, ?>> regionMap = new HashMap<String, Region<?, ?>>(2);
Map<String, Region<?, ?>> regionMap = new HashMap<>(2);
regionMap.put("RegionOne", mockRegionOne);
regionMap.put("RegionTwo", mockRegionTwo);
doAnswer(new Answer<Region<?, ?>>() {
@Override public Region<?, ?> answer(final InvocationOnMock invocation) throws Throwable {
String regionName = invocation.getArgument(0);
assertThat(regionMap.containsKey(regionName), is(true));
return regionMap.get(regionName);
}
doAnswer(invocation -> {
String regionName = invocation.getArgument(0);
assertThat(regionMap.containsKey(regionName)).isTrue();
return regionMap.get(regionName);
}).when(mockClientRegionFactory).create(any(String.class));
ConfigurableListableBeanFactory mockBeanFactory = mock(ConfigurableListableBeanFactory.class, "MockSpringBeanFactory");
@@ -306,7 +319,7 @@ public class GemfireDataSourcePostProcessorTest {
GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(mockClientCache);
postProcessor.createClientRegionProxies(mockBeanFactory, regionMap.keySet());
postProcessor.createClientProxyRegions(mockBeanFactory, regionMap.keySet());
verify(mockClientCache, times(1)).createClientRegionFactory(eq(ClientRegionShortcut.PROXY));
verify(mockClientRegionFactory, times(1)).create(eq("RegionOne"));
@@ -317,7 +330,7 @@ public class GemfireDataSourcePostProcessorTest {
@Test
@SuppressWarnings("unchecked")
public void createClientRegionProxiesWhenRegionBeanExists() {
public void createClientProxyRegionsWhenRegionBeanExists() {
ClientCache mockClientCache = mock(ClientCache.class, "MockGemFireClientCache");
@@ -335,10 +348,25 @@ public class GemfireDataSourcePostProcessorTest {
GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(mockClientCache);
postProcessor.createClientRegionProxies(mockBeanFactory, Collections.singletonList("Example"));
postProcessor.createClientProxyRegions(mockBeanFactory, Collections.singletonList("Example"));
verify(mockClientCache, times(1)).createClientRegionFactory(eq(ClientRegionShortcut.PROXY));
verify(mockClientRegionFactory, never()).create(any(String.class));
verify(mockBeanFactory, never()).registerSingleton(any(String.class), any(Region.class));
}
@Test
public void usingIsCorrect() {
GemfireDataSourcePostProcessor postProcessor = new GemfireDataSourcePostProcessor(this.mockClientCache)
.using(ClientRegionShortcut.LOCAL);
assertThat(postProcessor).isNotNull();
assertThat(postProcessor.getClientRegionShortcut().orElse(null)).isEqualTo(ClientRegionShortcut.LOCAL);
assertThat(postProcessor.using(ClientRegionShortcut.CACHING_PROXY)).isEqualTo(postProcessor);
assertThat(postProcessor.getClientRegionShortcut()
.orElse(null)).isEqualTo(ClientRegionShortcut.CACHING_PROXY);
assertThat(postProcessor.using(null)).isEqualTo(postProcessor);
assertThat(postProcessor.getClientRegionShortcut().isPresent()).isFalse();
}
}

View File

@@ -0,0 +1,181 @@
/*
* Copyright 2018 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.data.gemfire.config.annotation;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collections;
import javax.annotation.Resource;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.Region;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.data.gemfire.LocalRegionFactoryBean;
import org.springframework.data.gemfire.PartitionedRegionFactoryBean;
import org.springframework.data.gemfire.ReplicatedRegionFactoryBean;
import org.springframework.data.gemfire.process.ProcessWrapper;
import org.springframework.data.gemfire.support.ConnectionEndpoint;
import org.springframework.data.gemfire.test.support.ClientServerIntegrationTestsSupport;
import org.springframework.data.gemfire.util.RegionUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Integration tests for {@link EnableClusterDefinedRegions} and {@link ClusterDefinedRegionsConfiguration}.
*
* @author John Blum
* @see org.junit.Test
* @see org.apache.geode.cache.GemFireCache
* @see org.apache.geode.cache.Region
* @see org.springframework.data.gemfire.config.annotation.ClusterDefinedRegionsConfiguration
* @see org.springframework.data.gemfire.config.annotation.EnableClusterDefinedRegions
* @see org.springframework.data.gemfire.process.ProcessWrapper
* @see org.springframework.data.gemfire.test.support.ClientServerIntegrationTestsSupport
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringRunner
* @since 2.1.0
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = EnableClusterDefinedRegionsIntegrationTests.ClientTestConfiguration.class)
@SuppressWarnings("unused")
public class EnableClusterDefinedRegionsIntegrationTests extends ClientServerIntegrationTestsSupport {
private static final String GEMFIRE_LOG_LEVEL = "error";
private static ProcessWrapper gemfireServer;
@BeforeClass
public static void startGemFireServer() throws Exception {
int availablePort = findAvailablePort();
gemfireServer = run(ServerTestConfiguration.class,
String.format("-D%s=%d", GEMFIRE_CACHE_SERVER_PORT_PROPERTY, availablePort));
waitForServerToStart("localhost", availablePort);
System.setProperty(GEMFIRE_CACHE_SERVER_PORT_PROPERTY, String.valueOf(availablePort));
}
@AfterClass
public static void stopGemFireServer() {
System.clearProperty(GEMFIRE_CACHE_SERVER_PORT_PROPERTY);
stop(gemfireServer);
}
@Resource(name = "LocalRegion")
private Region<?, ?> localClientProxyRegion;
@Resource(name = "PartitionRegion")
private Region<?, ?> partitionClientProxyRegion;
@Resource(name = "ReplicateRegion")
private Region<?, ?> replicateClientProxyRegion;
private void assertRegion(Region<?, ?> region, String expectedName) {
assertThat(region).isNotNull();
assertThat(region.getName()).isEqualTo(expectedName);
assertThat(region.getFullPath()).isEqualTo(RegionUtils.toRegionPath(expectedName));
assertThat(region.getAttributes()).isNotNull();
assertThat(region.getAttributes().getDataPolicy()).isEqualTo(DataPolicy.EMPTY);
}
@Test
public void clusterRegionsExistOnClient() {
assertRegion(this.localClientProxyRegion, "LocalRegion");
assertRegion(this.partitionClientProxyRegion, "PartitionRegion");
assertRegion(this.replicateClientProxyRegion, "ReplicateRegion");
}
@ClientCacheApplication(logLevel = GEMFIRE_LOG_LEVEL)
@EnableClusterDefinedRegions
static class ClientTestConfiguration {
@Bean
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
ClientCacheConfigurer clientCachePoolPortConfigurer(
@Value("${" + GEMFIRE_CACHE_SERVER_PORT_PROPERTY + ":40404}") int port) {
return (bean, clientCacheFactoryBean) -> clientCacheFactoryBean.setServers(
Collections.singletonList(new ConnectionEndpoint("localhost", port)));
}
}
@CacheServerApplication(name = "EnableClusterDefinedRegionsIntegrationTests",
logLevel = GEMFIRE_LOG_LEVEL)
static class ServerTestConfiguration {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(ServerTestConfiguration.class);
applicationContext.registerShutdownHook();
}
@Bean("LocalRegion")
public LocalRegionFactoryBean<Object, Object> localRegion(GemFireCache gemfireCache) {
LocalRegionFactoryBean<Object, Object> localRegion = new LocalRegionFactoryBean<>();
localRegion.setCache(gemfireCache);
localRegion.setClose(false);
localRegion.setPersistent(false);
return localRegion;
}
@Bean("PartitionRegion")
public PartitionedRegionFactoryBean<Object, Object> partitionRegion(GemFireCache gemfireCache) {
PartitionedRegionFactoryBean<Object, Object> partitionRegion = new PartitionedRegionFactoryBean<>();
partitionRegion.setCache(gemfireCache);
partitionRegion.setClose(false);
partitionRegion.setPersistent(false);
return partitionRegion;
}
@Bean("ReplicateRegion")
public ReplicatedRegionFactoryBean<Object, Object> replicateRegion(GemFireCache gemfireCache) {
ReplicatedRegionFactoryBean<Object, Object> replicateRegion = new ReplicatedRegionFactoryBean<>();
replicateRegion.setCache(gemfireCache);
replicateRegion.setClose(false);
replicateRegion.setPersistent(false);
return replicateRegion;
}
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2018 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.data.gemfire.config.annotation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.Map;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.junit.Test;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.gemfire.client.GemfireDataSourcePostProcessor;
/**
* Unit tests for {@link EnableClusterDefinedRegions} and {@link ClusterDefinedRegionsConfiguration}.
*
* @author John Blum
* @see org.junit.Test
* @see org.apache.geode.cache.Cache
* @see org.apache.geode.cache.client.ClientCache
* @see org.apache.geode.cache.client.ClientRegionShortcut
* @see org.springframework.core.type.AnnotationMetadata
* @see org.springframework.data.gemfire.client.GemfireDataSourcePostProcessor
* @see org.springframework.data.gemfire.config.annotation.ClusterDefinedRegionsConfiguration
* @see org.springframework.data.gemfire.config.annotation.EnableClusterDefinedRegions
* @since 2.1.0
*/
public class EnableClusterDefinedRegionsUnitTests {
@Test
public void configuresClientRegionShortcutUsingAnnotationMetadata() {
Map<String, Object> enableClusterDefinedRegionsAttributes =
Collections.singletonMap("clientRegionShortcut", ClientRegionShortcut.LOCAL);
AnnotationMetadata mockAnnotationMetadata = mock(AnnotationMetadata.class);
when(mockAnnotationMetadata.getAnnotationAttributes(EnableClusterDefinedRegions.class.getName()))
.thenReturn(enableClusterDefinedRegionsAttributes);
ClusterDefinedRegionsConfiguration configuration = new ClusterDefinedRegionsConfiguration();
configuration.setImportMetadata(mockAnnotationMetadata);
assertThat(configuration.resolveClientRegionShortcut()).isEqualTo(ClientRegionShortcut.LOCAL);
}
@Test
public void setGetAndResolveClientRegionShortcut() {
ClusterDefinedRegionsConfiguration configuration = new ClusterDefinedRegionsConfiguration();
assertThat(configuration.getClientRegionShortcut().orElse(null)).isEqualTo(ClientRegionShortcut.PROXY);
assertThat(configuration.resolveClientRegionShortcut()).isEqualTo(ClientRegionShortcut.PROXY);
configuration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY);
assertThat(configuration.getClientRegionShortcut().orElse(null)).isEqualTo(ClientRegionShortcut.CACHING_PROXY);
assertThat(configuration.resolveClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY);
configuration.setClientRegionShortcut(ClientRegionShortcut.LOCAL);
assertThat(configuration.getClientRegionShortcut().orElse(null)).isEqualTo(ClientRegionShortcut.LOCAL);
assertThat(configuration.resolveClientRegionShortcut()).isEqualTo(ClientRegionShortcut.LOCAL);
configuration.setClientRegionShortcut(null);
assertThat(configuration.getClientRegionShortcut().orElse(null)).isNull();
assertThat(configuration.resolveClientRegionShortcut()).isEqualTo(ClientRegionShortcut.PROXY);
}
@Test
public void configureGemfireDataSourcePostProcessor() {
ClientCache mockClientCache = mock(ClientCache.class);
ClusterDefinedRegionsConfiguration configuration = new ClusterDefinedRegionsConfiguration();
configuration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY);
GemfireDataSourcePostProcessor postProcessor = configuration.gemfireDataSourcePostProcessor(mockClientCache);
assertThat(postProcessor.getClientRegionShortcut().orElse(null))
.isEqualTo(ClientRegionShortcut.CACHING_PROXY);
}
@Test(expected = IllegalArgumentException.class)
public void configureGemfireDataSourcePostProcessorWithNullCache() {
try {
new ClusterDefinedRegionsConfiguration().gemfireDataSourcePostProcessor(null);
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage("GemFireCache must be an instance of ClientCache");
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test(expected = IllegalArgumentException.class)
public void configureGemfireDataSourcePostProcessorWithPeerCache() {
try {
Cache mockPeerCache = mock(Cache.class);
new ClusterDefinedRegionsConfiguration().gemfireDataSourcePostProcessor(mockPeerCache);
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage("GemFireCache must be an instance of ClientCache");
assertThat(expected).hasNoCause();
throw expected;
}
}
}