Skip to content

Commit db94d3c

Browse files
committed
Add support to protect against Apache Geode Initialization Safety Issues in a DI context.
Resolves spring-projectsgh-554.
1 parent 77ba57c commit db94d3c

22 files changed

+2305
-284
lines changed
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,206 @@
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.wan.GatewayReceiver;
26+
27+
import org.springframework.beans.BeansException;
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.beans.factory.config.BeanPostProcessor;
30+
import org.springframework.context.ApplicationListener;
31+
import org.springframework.context.annotation.Bean;
32+
import org.springframework.context.annotation.Configuration;
33+
import org.springframework.context.annotation.ImportAware;
34+
import org.springframework.context.event.ContextRefreshedEvent;
35+
import org.springframework.core.annotation.AnnotationAttributes;
36+
import org.springframework.core.type.AnnotationMetadata;
37+
import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport;
38+
import org.springframework.data.gemfire.event.GemFireClusterReadyApplicationListener;
39+
import org.springframework.data.gemfire.event.GemFireClusterReadyStrategy;
40+
import org.springframework.data.gemfire.event.support.InitializationSafeCacheListener;
41+
import org.springframework.data.gemfire.event.support.InitializationSafeCacheLoader;
42+
import org.springframework.data.gemfire.event.support.InitializationSafeCacheWriter;
43+
import org.springframework.data.gemfire.init.WaitStrategy;
44+
import org.springframework.data.gemfire.util.CollectionUtils;
45+
import org.springframework.data.gemfire.wan.GatewayReceiverFactoryBean;
46+
import org.springframework.data.gemfire.wan.support.InitializationSafeGatewayReceiver;
47+
import org.springframework.lang.NonNull;
48+
import org.springframework.lang.Nullable;
49+
50+
/**
51+
* Spring {@link Configuration} class used to enforce {@literal initialization safety} between Apache Geode
52+
* and application components used to extend the functionality and behavior of an Apache Geode {@link GemFireCache}.
53+
*
54+
* @author John Blum
55+
* @see java.lang.annotation.Annotation
56+
* @see org.apache.geode.cache.GemFireCache
57+
* @see org.apache.geode.cache.wan.GatewayReceiver
58+
* @see org.springframework.beans.factory.config.BeanPostProcessor
59+
* @see org.springframework.context.ApplicationContext
60+
* @see org.springframework.context.ApplicationListener
61+
* @see org.springframework.context.annotation.Bean
62+
* @see org.springframework.context.annotation.Configuration
63+
* @see org.springframework.context.annotation.ImportAware
64+
* @see org.springframework.context.event.ContextRefreshedEvent
65+
* @see org.springframework.core.annotation.AnnotationAttributes
66+
* @see org.springframework.core.type.AnnotationMetadata
67+
* @see org.springframework.data.gemfire.config.annotation.EnableGemFireInitializationSafety
68+
* @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport
69+
* @see org.springframework.data.gemfire.event.GemFireClusterReadyApplicationListener
70+
* @see org.springframework.data.gemfire.event.GemFireClusterReadyStrategy
71+
* @see InitializationSafeGatewayReceiver
72+
* @since 2.7.0
73+
*/
74+
@Configuration
75+
@SuppressWarnings("unused")
76+
public class GemFireInitializationSafetyConfiguration extends AbstractAnnotationConfigSupport implements ImportAware {
77+
78+
protected static final boolean DEFAULT_ENABLE_CACHE_CALLBACK_SAFETY = true;
79+
protected static final boolean DEFAULT_ENABLE_GATEWAY_SAFETY = true;
80+
81+
private Boolean enableCacheCallbackSafety = DEFAULT_ENABLE_CACHE_CALLBACK_SAFETY;
82+
private Boolean enableGatewaySafety = DEFAULT_ENABLE_GATEWAY_SAFETY;
83+
84+
/**
85+
* @inheritDoc
86+
*/
87+
@Override
88+
protected @NonNull Class<? extends Annotation> getAnnotationType() {
89+
return EnableGemFireInitializationSafety.class;
90+
}
91+
92+
/**
93+
* @inheritDoc
94+
*/
95+
@Override
96+
public void setImportMetadata(@NonNull AnnotationMetadata importMetadata) {
97+
98+
if (isAnnotationPresent(importMetadata)) {
99+
100+
AnnotationAttributes enableGemFireInitializationSafetyAttributes = getAnnotationAttributes(importMetadata);
101+
102+
setEnableCacheCallbackSafety(resolveProperty(cacheProperty("initialization-safety.callback.enabled"),
103+
Boolean.class, enableGemFireInitializationSafetyAttributes.getBoolean("enableCacheCallbackSafety")));
104+
105+
setEnableGatewaySafety(resolveProperty(cacheProperty("initialization-safety.gateway.enabled"),
106+
Boolean.class, enableGemFireInitializationSafetyAttributes.getBoolean("enableGatewaySafety")));
107+
}
108+
}
109+
110+
@Bean
111+
public ApplicationListener<ContextRefreshedEvent> gemfireClusterReadyApplicationListener(
112+
@Autowired(required = false) List<GemFireClusterReadyStrategy> clusterReadyStrategies) {
113+
114+
GemFireClusterReadyApplicationListener listener = new GemFireClusterReadyApplicationListener();
115+
116+
for (GemFireClusterReadyStrategy strategy : CollectionUtils.nullSafeList(clusterReadyStrategies)) {
117+
if (strategy != null) {
118+
listener = listener.thenWaitUntil(strategy);
119+
}
120+
}
121+
122+
return listener;
123+
}
124+
125+
@Bean
126+
public BeanPostProcessor cacheCallbackGemFireClusterReadyAwareBeanPostProcessor(
127+
@Autowired(required = false) WaitStrategy waitStrategy) {
128+
129+
return new BeanPostProcessor() {
130+
131+
@Override
132+
public @Nullable Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
133+
134+
if (isEnableCacheCallbackSafety()) {
135+
if (bean instanceof CacheListener) {
136+
CacheListener<?, ?> cacheListener = (CacheListener<?, ?>) bean;
137+
bean = InitializationSafeCacheListener.from(cacheListener)
138+
.usingWaitStrategy(waitStrategy);
139+
}
140+
else if (bean instanceof CacheLoader) {
141+
CacheLoader<?, ?> cacheLoader = (CacheLoader<?, ?>) bean;
142+
bean = InitializationSafeCacheLoader.from(cacheLoader)
143+
.usingWaitStrategy(waitStrategy);
144+
}
145+
else if (bean instanceof CacheWriter) {
146+
CacheWriter<?, ?> cacheWriter = (CacheWriter<?, ?>) bean;
147+
bean = InitializationSafeCacheWriter.from(cacheWriter)
148+
.usingWaitStrategy(waitStrategy);
149+
}
150+
}
151+
152+
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
153+
}
154+
};
155+
}
156+
157+
@Bean
158+
public BeanPostProcessor wanGatewayGemFireClusterReadyAwareBeanPostProcessor(
159+
@Autowired(required = false) WaitStrategy waitStrategy) {
160+
161+
return new BeanPostProcessor() {
162+
163+
@Override
164+
public @Nullable Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
165+
166+
if (isEnableGatewaySafety()) {
167+
if (bean instanceof GatewayReceiverFactoryBean) {
168+
((GatewayReceiverFactoryBean) bean).setManualStart(true);
169+
}
170+
}
171+
172+
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
173+
}
174+
175+
@Override
176+
public @Nullable Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
177+
178+
if (isEnableGatewaySafety()) {
179+
if (bean instanceof GatewayReceiver) {
180+
GatewayReceiver gatewayReceiver = (GatewayReceiver) bean;
181+
bean = InitializationSafeGatewayReceiver.from(gatewayReceiver)
182+
.usingWaitStrategy(waitStrategy);
183+
}
184+
}
185+
186+
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
187+
}
188+
};
189+
}
190+
191+
protected void setEnableCacheCallbackSafety(@Nullable Boolean enableCacheCallbackSafety) {
192+
this.enableCacheCallbackSafety = enableCacheCallbackSafety;
193+
}
194+
195+
public boolean isEnableCacheCallbackSafety() {
196+
return Boolean.TRUE.equals(this.enableCacheCallbackSafety);
197+
}
198+
199+
protected void setEnableGatewaySafety(@Nullable Boolean enableGatewaySafety) {
200+
this.enableGatewaySafety = enableGatewaySafety;
201+
}
202+
203+
public boolean isEnableGatewaySafety() {
204+
return Boolean.TRUE.equals(this.enableGatewaySafety);
205+
}
206+
}
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)