DATAGEODE-142 - Add annotation configuration support to create client Regions from Cluster-defined Regions.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user