Skip to content

Commit b00c205

Browse files
committed
Add support to protect against Apache Geode initialization safety issues in a DI context.
Resolves spring-projectsgh-554.
1 parent b65dab1 commit b00c205

29 files changed

+2936
-289
lines changed

spring-data-geode/src/main/java/org/springframework/data/gemfire/config/annotation/ClusterDefinedRegionsConfiguration.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
import org.apache.geode.cache.client.ClientCache;
2424
import org.apache.geode.cache.client.ClientRegionShortcut;
2525

26-
import org.apache.shiro.util.Assert;
27-
2826
import org.springframework.context.annotation.Bean;
2927
import org.springframework.context.annotation.Configuration;
3028
import org.springframework.context.annotation.ImportAware;
@@ -35,6 +33,7 @@
3533
import org.springframework.data.gemfire.client.GemfireDataSourcePostProcessor;
3634
import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport;
3735
import org.springframework.data.gemfire.util.CacheUtils;
36+
import org.springframework.util.Assert;
3837
import org.springframework.util.ObjectUtils;
3938

4039
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.gemfire.config.annotation;
17+
18+
import java.lang.annotation.Annotation;
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Inherited;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
import org.apache.geode.cache.GemFireCache;
27+
28+
import org.springframework.context.annotation.Import;
29+
30+
/**
31+
* Spring Java {@link Annotation} used to enforce {@literal initialization safety} between Apache Geode
32+
* and application components used to extend the functionality and behavior of an Apache Geode {@link GemFireCache}.
33+
*
34+
* @author John Blum
35+
* @see java.lang.annotation.Annotation
36+
* @see java.lang.annotation.Documented
37+
* @see java.lang.annotation.Inherited
38+
* @see java.lang.annotation.Retention
39+
* @see java.lang.annotation.Target
40+
* @see org.apache.geode.cache.GemFireCache
41+
* @see org.springframework.context.annotation.Import
42+
* @see org.springframework.data.gemfire.config.annotation.GemFireInitializationSafetyConfiguration
43+
* @since 2.7.0
44+
*/
45+
@Target(ElementType.TYPE)
46+
@Retention(RetentionPolicy.RUNTIME)
47+
@Inherited
48+
@Documented
49+
@Import(GemFireInitializationSafetyConfiguration.class)
50+
@SuppressWarnings("unused")
51+
public @interface EnableGemFireInitializationSafety {
52+
53+
/**
54+
* Configures whether initialization safety is enabled or disabled for {@link GemFireCache} callbacks.
55+
*
56+
* Defaults to {@literal true}
57+
*
58+
* Use {@literal spring.data.gemfire.cache.initialization-safety.callback.enabled} property
59+
* in {@literal application.properties}.
60+
*
61+
* @return a boolean value indicating whether initialization safety is enabled or disabled for {@link GemFireCache} callbacks.
62+
*/
63+
boolean enableCacheCallbackSafety() default GemFireInitializationSafetyConfiguration.DEFAULT_ENABLE_CACHE_CALLBACK_SAFETY;
64+
65+
/**
66+
* Configures whether initialization safety is enabled or disabled for {@link GemFireCache} WAN Gateway components.
67+
*
68+
* Defaults to {@literal true}
69+
*
70+
* Use {@literal spring.data.gemfire.cache.initialization-safety.gateway.enabled} property
71+
* in {@literal application.properties}.
72+
*
73+
* @return a boolean value indicating whether initialization safety is enabled or disabled for {@link GemFireCache}
74+
* WAN Gateway components.
75+
*/
76+
boolean enableGatewaySafety() default GemFireInitializationSafetyConfiguration.DEFAULT_ENABLE_GATEWAY_SAFETY;
77+
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.gemfire.config.annotation;
17+
18+
import java.lang.annotation.Annotation;
19+
import java.util.List;
20+
21+
import org.apache.geode.cache.CacheListener;
22+
import org.apache.geode.cache.CacheLoader;
23+
import org.apache.geode.cache.CacheWriter;
24+
import org.apache.geode.cache.GemFireCache;
25+
import org.apache.geode.cache.TransactionListener;
26+
import org.apache.geode.cache.TransactionWriter;
27+
import org.apache.geode.cache.asyncqueue.AsyncEventListener;
28+
import org.apache.geode.cache.wan.GatewayReceiver;
29+
30+
import org.springframework.beans.BeansException;
31+
import org.springframework.beans.factory.annotation.Autowired;
32+
import org.springframework.beans.factory.config.BeanPostProcessor;
33+
import org.springframework.context.ApplicationListener;
34+
import org.springframework.context.annotation.Bean;
35+
import org.springframework.context.annotation.Configuration;
36+
import org.springframework.context.annotation.ImportAware;
37+
import org.springframework.context.event.ContextRefreshedEvent;
38+
import org.springframework.core.annotation.AnnotationAttributes;
39+
import org.springframework.core.type.AnnotationMetadata;
40+
import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport;
41+
import org.springframework.data.gemfire.event.GemFireClusterReadyApplicationListener;
42+
import org.springframework.data.gemfire.event.GemFireClusterReadyStrategy;
43+
import org.springframework.data.gemfire.event.support.InitializationSafeAsyncEventListener;
44+
import org.springframework.data.gemfire.event.support.InitializationSafeCacheListener;
45+
import org.springframework.data.gemfire.event.support.InitializationSafeCacheLoader;
46+
import org.springframework.data.gemfire.event.support.InitializationSafeCacheWriter;
47+
import org.springframework.data.gemfire.event.support.InitializationSafeTransactionListener;
48+
import org.springframework.data.gemfire.event.support.InitializationSafeTransactionWriter;
49+
import org.springframework.data.gemfire.init.WaitStrategy;
50+
import org.springframework.data.gemfire.util.CollectionUtils;
51+
import org.springframework.data.gemfire.wan.GatewayReceiverFactoryBean;
52+
import org.springframework.data.gemfire.wan.support.InitializationSafeGatewayReceiver;
53+
import org.springframework.lang.NonNull;
54+
import org.springframework.lang.Nullable;
55+
56+
/**
57+
* Spring {@link Configuration} class used to enforce {@literal initialization safety} between Apache Geode
58+
* and application components used to extend the functionality and behavior of an Apache Geode {@link GemFireCache}.
59+
*
60+
* @author John Blum
61+
* @see java.lang.annotation.Annotation
62+
* @see org.apache.geode.cache.GemFireCache
63+
* @see org.apache.geode.cache.wan.GatewayReceiver
64+
* @see org.springframework.beans.factory.config.BeanPostProcessor
65+
* @see org.springframework.context.ApplicationContext
66+
* @see org.springframework.context.ApplicationListener
67+
* @see org.springframework.context.annotation.Bean
68+
* @see org.springframework.context.annotation.Configuration
69+
* @see org.springframework.context.annotation.ImportAware
70+
* @see org.springframework.context.event.ContextRefreshedEvent
71+
* @see org.springframework.core.annotation.AnnotationAttributes
72+
* @see org.springframework.core.type.AnnotationMetadata
73+
* @see org.springframework.data.gemfire.config.annotation.EnableGemFireInitializationSafety
74+
* @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport
75+
* @see org.springframework.data.gemfire.event.GemFireClusterReadyApplicationListener
76+
* @see org.springframework.data.gemfire.event.GemFireClusterReadyStrategy
77+
* @see InitializationSafeGatewayReceiver
78+
* @since 2.7.0
79+
*/
80+
@Configuration
81+
@SuppressWarnings("unused")
82+
public class GemFireInitializationSafetyConfiguration extends AbstractAnnotationConfigSupport implements ImportAware {
83+
84+
protected static final boolean DEFAULT_ENABLE_CACHE_CALLBACK_SAFETY = true;
85+
protected static final boolean DEFAULT_ENABLE_GATEWAY_SAFETY = true;
86+
87+
private Boolean enableCacheCallbackSafety = DEFAULT_ENABLE_CACHE_CALLBACK_SAFETY;
88+
private Boolean enableGatewaySafety = DEFAULT_ENABLE_GATEWAY_SAFETY;
89+
90+
/**
91+
* @inheritDoc
92+
*/
93+
@Override
94+
protected @NonNull Class<? extends Annotation> getAnnotationType() {
95+
return EnableGemFireInitializationSafety.class;
96+
}
97+
98+
/**
99+
* @inheritDoc
100+
*/
101+
@Override
102+
public void setImportMetadata(@NonNull AnnotationMetadata importMetadata) {
103+
104+
if (isAnnotationPresent(importMetadata)) {
105+
106+
AnnotationAttributes enableGemFireInitializationSafetyAttributes = getAnnotationAttributes(importMetadata);
107+
108+
setEnableCacheCallbackSafety(resolveProperty(cacheProperty("initialization-safety.callback.enabled"),
109+
Boolean.class, enableGemFireInitializationSafetyAttributes.getBoolean("enableCacheCallbackSafety")));
110+
111+
setEnableGatewaySafety(resolveProperty(cacheProperty("initialization-safety.gateway.enabled"),
112+
Boolean.class, enableGemFireInitializationSafetyAttributes.getBoolean("enableGatewaySafety")));
113+
}
114+
}
115+
116+
@Bean
117+
public ApplicationListener<ContextRefreshedEvent> gemfireClusterReadyApplicationListener(
118+
@Autowired(required = false) List<GemFireClusterReadyStrategy> clusterReadyStrategies) {
119+
120+
GemFireClusterReadyApplicationListener listener = new GemFireClusterReadyApplicationListener();
121+
122+
for (GemFireClusterReadyStrategy strategy : CollectionUtils.nullSafeList(clusterReadyStrategies)) {
123+
if (strategy != null) {
124+
listener = listener.thenWaitUntil(strategy);
125+
}
126+
}
127+
128+
return listener;
129+
}
130+
131+
@Bean
132+
public BeanPostProcessor cacheCallbackGemFireClusterReadyAwareBeanPostProcessor(
133+
@Autowired(required = false) WaitStrategy waitStrategy) {
134+
135+
return new BeanPostProcessor() {
136+
137+
@Override
138+
public @Nullable Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
139+
140+
if (isEnableCacheCallbackSafety()) {
141+
if (bean instanceof AsyncEventListener) {
142+
AsyncEventListener eventListener = (AsyncEventListener) bean;
143+
bean = InitializationSafeAsyncEventListener.from(eventListener)
144+
.usingWaitStrategy(waitStrategy);
145+
}
146+
else if (bean instanceof CacheListener) {
147+
CacheListener<?, ?> cacheListener = (CacheListener<?, ?>) bean;
148+
bean = InitializationSafeCacheListener.from(cacheListener)
149+
.usingWaitStrategy(waitStrategy);
150+
}
151+
else if (bean instanceof CacheLoader) {
152+
CacheLoader<?, ?> cacheLoader = (CacheLoader<?, ?>) bean;
153+
bean = InitializationSafeCacheLoader.from(cacheLoader)
154+
.usingWaitStrategy(waitStrategy);
155+
}
156+
else if (bean instanceof CacheWriter) {
157+
CacheWriter<?, ?> cacheWriter = (CacheWriter<?, ?>) bean;
158+
bean = InitializationSafeCacheWriter.from(cacheWriter)
159+
.usingWaitStrategy(waitStrategy);
160+
}
161+
else if (bean instanceof TransactionListener) {
162+
TransactionListener transactionListener = (TransactionListener) bean;
163+
bean = InitializationSafeTransactionListener.from(transactionListener)
164+
.usingWaitStrategy(waitStrategy);
165+
}
166+
else if (bean instanceof TransactionWriter) {
167+
TransactionWriter transactionWriter = (TransactionWriter) bean;
168+
bean = InitializationSafeTransactionWriter.from(transactionWriter)
169+
.usingWaitStrategy(waitStrategy);
170+
}
171+
}
172+
173+
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
174+
}
175+
};
176+
}
177+
178+
@Bean
179+
public BeanPostProcessor wanGatewayGemFireClusterReadyAwareBeanPostProcessor(
180+
@Autowired(required = false) WaitStrategy waitStrategy) {
181+
182+
return new BeanPostProcessor() {
183+
184+
@Override
185+
public @Nullable Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
186+
187+
if (isEnableGatewaySafety()) {
188+
if (bean instanceof GatewayReceiverFactoryBean) {
189+
((GatewayReceiverFactoryBean) bean).setManualStart(true);
190+
}
191+
}
192+
193+
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
194+
}
195+
196+
@Override
197+
public @Nullable Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
198+
199+
if (isEnableGatewaySafety()) {
200+
if (bean instanceof GatewayReceiver) {
201+
GatewayReceiver gatewayReceiver = (GatewayReceiver) bean;
202+
bean = InitializationSafeGatewayReceiver.from(gatewayReceiver)
203+
.usingWaitStrategy(waitStrategy);
204+
}
205+
}
206+
207+
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
208+
}
209+
};
210+
}
211+
212+
protected void setEnableCacheCallbackSafety(@Nullable Boolean enableCacheCallbackSafety) {
213+
this.enableCacheCallbackSafety = enableCacheCallbackSafety;
214+
}
215+
216+
public boolean isEnableCacheCallbackSafety() {
217+
return Boolean.TRUE.equals(this.enableCacheCallbackSafety);
218+
}
219+
220+
protected void setEnableGatewaySafety(@Nullable Boolean enableGatewaySafety) {
221+
this.enableGatewaySafety = enableGatewaySafety;
222+
}
223+
224+
public boolean isEnableGatewaySafety() {
225+
return Boolean.TRUE.equals(this.enableGatewaySafety);
226+
}
227+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.gemfire.event;
17+
18+
import org.apache.geode.distributed.DistributedSystem;
19+
20+
import org.springframework.context.ApplicationEvent;
21+
import org.springframework.lang.NonNull;
22+
23+
/**
24+
* Spring {@link ApplicationEvent} to indicate that the Apache Geode {@link DistributedSystem cluster}
25+
* is available (up & running) and ready for service.
26+
*
27+
* @author John Blum
28+
* @see org.apache.geode.distributed.DistributedSystem
29+
* @see org.springframework.context.ApplicationEvent
30+
* @since 2.7.0
31+
*/
32+
public class GemFireClusterReadyApplicationEvent extends ApplicationEvent {
33+
34+
/**
35+
* Constructs an new instance of {@link GemFireClusterReadyApplicationEvent} initialized with
36+
* the given {@link Object} as the source of this event.
37+
*
38+
* @param source {@link Object} representing the source of this event.
39+
* @throws IllegalArgumentException if the source {@link Object} is {@literal null}.
40+
*/
41+
public GemFireClusterReadyApplicationEvent(@NonNull Object source) {
42+
super(source);
43+
}
44+
}

0 commit comments

Comments
 (0)