Details
-
Improvement
-
Status: Closed
-
Major
-
Resolution: Complete
-
1.4.4 (Dijkstra SR4), 1.5 GA (Evans)
-
Spring + GemFire 8
Description
A problem was identified and discussed in our internal social channels regarding mixing GemFire configuration (either cache.xml or GemFire 8's new Cluster-based Configuration) with Spring config and using Gfsh to record GemFire schema-like changes that might include the declaration of GemFire callbacks that need to be wired in the presence of a Spring ApplicationContext.
The problem was...
"the listener that extends LazyWiringDeclarableSupport must be attached to the regions defined in the cache.xml, otherwise an Exception is thrown that init() method has not been called when I try to create the region with attached listener in gfsh dynamically."
The Exception is caused by the Assert statement in the LazyWiringDeclarableSupport class's onApplicationEvent(..) handler method, thus leading to...
[info 2014/10/23 14:24:38.165 PDT springServer <main> tid=0x1] Partitioned Region /SpringDeclaredRegion is born with prId=3 ident:#SpringDeclaredRegion
Exception in thread "main" java.lang.IllegalStateException: This Declarable object's (org.spring.data.gemfire.cache.DeclarableReplicatingCacheListener) init method has not been invoked!
at org.springframework.util.Assert.state(Assert.java:385)
at org.springframework.data.gemfire.LazyWiringDeclarableSupport.onApplicationEvent(LazyWiringDeclarableSupport.java:198)
at org.springframework.data.gemfire.LazyWiringDeclarableSupport.onApplicationEvent(LazyWiringDeclarableSupport.java:51)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:96)
at org.springframe...gemfire.support.SpringContextBootstrappingInitializer.onApplicationEvent(SpringContextBootstrappingInitializer.java:281)
at org.springframe...gemfire.support.SpringContextBootstrappingInitializer.onApplicationEvent(SpringContextBootstrappingInitializer.java:58)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:96)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:334)
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:950)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
at org.springframe...gemfire.support.SpringContextBootstrappingInitializer.init(SpringContextBootstrappingInitializer.java:246)
at com.gemstone.gemfire.distributed.ServerLauncher.startWithSpring(ServerLauncher.java:763)
at com.gemstone.gemfire.distributed.ServerLauncher.start(ServerLauncher.java:695)
at com.gemstone.gemfire.distributed.ServerLauncher.run(ServerLauncher.java:625)
at com.gemstone.gemfire.distributed.ServerLauncher.main(ServerLauncher.java:200)
When starting a Spring-configured GemFire Server in Gfsh like so...
gfsh>start server --name=springServer --log-level=config --disable-default-server --classpath=/Users/jblum/vmdev/lab/lib/example-app-dependencies.jar --spring-xml-location=spring-gemfire-context-with-clusterconfig-example.xml
Based on the following Spring config (abbreviated)...
... <gfe:partitioned-region id="SpringDeclaredRegion" persistent="false" key-constraint="java.lang.String" value-constraint="java.lang.Integer"> <gfe:cache-listener> <bean class="org.spring.data.gemfire.cache.DeclarableReplicatingCacheListener"> <property name="backingRegion" ref="BackingRegion"/> </bean> ... </gfe:cache-listener> .... </gfe:partitioned-region>
Where the class org.spring.data.gemfire.cache.DeclarableReplicatingCacheListener extends LazyWiringDeclarableSupport.
Note, however, the following will work in the presence of a non-Spring-configured GemFire Server (i.e. a Server configured purely with Gfsh)...
gfsh>start server --name=gemfireServer --log-level=config --disable-default-server --classpath=/Users/jblum/vmdev/lab/lib/example-app-dependencies.jar gfsh>list members Name | Id -------- | --------------------------------------------- locatorX | jblum-mbpro(locatorX:91075:locator)<v0>:19745 gemfireServer | jblum-mbpro(gemfireServer:91112)<v1>:51461 gfsh>create region --name=ClusterConfigDeclaredRegion --type=PARTITION --key-constraint=java.lang.String --value-constraint=java.lang.Integer --cache-listener=org.spring.data.gemfire.cache.DeclarableReplicatingCacheListener Member | Status ------- | ---------------------------------------------------------- gemfireServer | Region "/ClusterConfigDeclaredRegion" created on "gemfireServer" gfsh>list regions List of regions --------------------------- ClusterConfigDeclaredRegion gfsh>describe region --name=ClusterConfigDeclaredRegion ....................................................................................................... Name : ClusterConfigDeclaredRegion Data Policy : partition Hosting Members : gemfireServer Non-Default Attributes Shared By Hosting Members Type | Name | Value ------ | --------------- | ---------------------------------------------------------------- Region | cache-listeners | org.spring.data.gemfire.cache.DeclarableReplicatingCacheListener | size | 0
Of course, the above should work. But, it is important to remember that this GemFire CacheListener ("DeclarableReplicatingCacheListener") extending LazyWiringDeclarableSupport will not be auto-wired since a Spring ApplicationContext will not be bootstrapped in the Gfsh-configured GemFire Server.
I did this only to build up Cluster Configuration meta-data prior to starting a Spring-configured GemFire Server ("springServer") so that it would pick up the "ClusterConfigDeclaredRegion" with the registered "DeclarableReplicatingCacheListener" and auto-wire it in the presence of a Spring ApplicationContext, which is created in a Spring-configured GemFire Server using either --spring-xml-location or the approach described here.
It is also important to note the distinction between --spring-xml-location and using the 'cache.xml' approach, as described here and introduced in SDG 1.4, basically (which is necessary prior to GemFire 8, such as with GemFire 7)...
<?xml version="1.0"?> <!DOCTYPE cache PUBLIC "-//GemStone Systems, Inc.//GemFire Declarative Caching 7.0//EN" "www.gemstone.com/dtd/cache7_0.dtd"> <cache> <initializer> <class-name>org.springframe...gemfire.support.SpringContextBootstrappingInitializer</class-name> <parameter name="contextConfigLocations"> <string> classpath:org/spring/data/gemfire/app/initializer-gemfire-context.xml </string> </parameter> </initializer> </cache>
Using -spring-xml-location uses Spring to "bootstrap" GemFire where as the cache.xml approach uses GemFire to "bootstrap" a Spring ApplicationContext. Using -spring-xml-location is preferred over using cache.xml and the 'Declarable' <initializer> since it overcomes the limitations described here.
However, things get a bit more complicated when you start mixing configurations, such as using a cache.xml in addition to Spring like so...
<?xml version="1.0"?> <!DOCTYPE cache PUBLIC "-//GemStone Systems, Inc.//GemFire Declarative Caching 7.0//EN" "www.gemstone.com/dtd/cache7_0.dtd"> <cache> <region name="Users" refid="REPLICATE"> <region-attributes initial-capacity="101" load-factor="0.85"> <key-constraint>java.lang.String</key-constraint> <value-constraint>org.spring.data.gemfire.app.beans.User</value-constraint> <cache-loader> <class-name>org.spring.data.gemfire.cache.UserDataStoreCacheLoader</class-name> </cache-loader> </region-attributes> </region> <initializer> <class-name>org.springframe...gemfire.support.SpringContextBootstrappingInitializer</class-name> <parameter name="contextConfigLocations"> <string> classpath:org/spring/data/gemfire/app/initializer-gemfire-context.xml </string> </parameter> </initializer> </cache>
...
<gfe:cache cache-xml-location="cache.xml" ... />
If the "UserDataStoreCacheLoader" on the "Users" cache.xml declared Region requires DI Spring beans (such as a DataSource bean) from the Spring ApplicationContext (declared in "context.xml"), suddenly the CacheLoader is dependent on collaborators that do not exist yet since GemFire parses the cache.xml, initializing the Cache and all GemFire components (e.g. Regions, etc) before any 'Declarable' <initializer> blocks are processed, and thus before the Spring ApplicationContext gets initialized, thus requiring a "lazy" auto-wiring support strategy (i.e. LazyWiringDeclarableSupport).
This is true in the case of using GemFire 8's new Cluster-based Configuration meta-data as well, which will be consumed by a Spring-configured GemFire Server (unless explicitly disabled... --use-cluster-configuration=false) as I have demonstrated above. The "Cluster Configuration meta-data" coming from the Cluster Configuration Service is (which can be seen inside the Servers' log file on startup)...
[info 2014/10/23 14:24:37.923 PDT springServer <main> tid=0x1] *************************************************************** Configuration for 'cluster' Jar files to deployed <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE cache PUBLIC "-//GemStone Systems, Inc.//GemFire Declarative Cache 8.0//EN" "www.gemstone.com/dtd/cache8_0.dtd"> <cache lock-lease="120" lock-timeout="60" search-timeout="300" is-server="false" copy-on-read="false"> <region name="ClusterConfigDeclaredRegion"> <region-attributes data-policy="partition" concurrency-checks-enabled="true"> <key-constraint>java.lang.String</key-constraint> <value-constraint>java.lang.Integer</value-constraint> <cache-listener> <class-name>org.spring.data.gemfire.cache.DeclarableReplicatingCacheListener</class-name> </cache-listener> </region-attributes> </region> <region name="BackingRegion"> <region-attributes scope="distributed-ack" data-policy="replicate" concurrency-checks-enabled="true"/> </region> </cache>
Anyway...
I may need to rethink the logic in LazyWiringDeclarableSupport a bit. Though, GemFire callbacks (e.g. CacheListeners) that extend LazyWiringDeclarableSupport were never intended to be used in Spring config, or rather (and more appropriately) GemFire callbacks declared in Spring config do not need to extend LazyWiringDeclarableSupport (for reasons that I hope are obvious, so I won't explain here).
But, if the "same" GemFire callback (e.g. CacheListener) are used in both places... i.e., a Cluster Configuration meta-data-defined Region created in Gfsh, or attached to a Region declared in cache.xml imported by the Spring config (like the example shown above... <gfe:cache cache-xml-location="cache.xml" ...>), in addition to being registered on a Region declared in Spring config, then of course, the GemFire callback does need to extend LazyWiringDeclarableSupport, especially if it needs to be DI'd with other Spring bean components defined/declared in the Spring ApplicationContext.
Put simply, Spring ApplicationContext needs to be made aware that additional components declared in and coming from GemFire native config need to be auto-wired as well.