Skip to content

Commit f1434ea

Browse files
author
Udo Kohlmeyer
committed
DATAGEODE-236 - Splitting AsCacheListener.java into two annotations to handle region and entry events
1 parent c796eda commit f1434ea

File tree

7 files changed

+167
-125
lines changed

7 files changed

+167
-125
lines changed

src/main/java/org/springframework/data/gemfire/config/annotation/AsCacheListener.java

-8
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,6 @@
4343
*/
4444
CacheListenerEventType[] eventTypes() default {};
4545

46-
/**
47-
* An array of {@link RegionCacheListenerEventType} that control what region events need to be observed
48-
* {@link CacheListenerEventType} and {@link RegionCacheListenerEventType} cannot be set on the same method. As they
49-
* are mutually exclusive and require that the implementing method uses {@link org.apache.geode.cache.RegionEvent} or
50-
* {@link org.apache.geode.cache.EntryEvent}
51-
*/
52-
RegionCacheListenerEventType[] regionEventTypes() default {};
53-
5446
/**
5547
* An array for {@link org.apache.geode.cache.Region} names which this {@link org.apache.geode.cache.CacheListener}
5648
* will be link to. Not declaring any regions will result in the CacheListener to be configured against all defined
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2016-2019 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+
*/
17+
18+
package org.springframework.data.gemfire.config.annotation;
19+
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.data.gemfire.eventing.config.CacheListenerEventType;
26+
import org.springframework.data.gemfire.eventing.config.RegionCacheListenerEventType;
27+
28+
/**
29+
* Used to declare a concrete method as a RegionEventListener event handler, which handles {@link org.apache.geode.cache.Region}
30+
*
31+
* @author Udo Kohlmeyer
32+
* @since 2.3.0
33+
*/
34+
@Retention(RetentionPolicy.RUNTIME)
35+
@Target({ ElementType.METHOD })
36+
public @interface AsRegionEventListener {
37+
38+
/**
39+
* An array of {@link RegionCacheListenerEventType} that control what region events need to be observed
40+
* {@link CacheListenerEventType} and {@link RegionCacheListenerEventType} cannot be set on the same method. As they
41+
* are mutually exclusive and require that the implementing method uses {@link org.apache.geode.cache.RegionEvent} or
42+
* {@link org.apache.geode.cache.EntryEvent}
43+
*/
44+
RegionCacheListenerEventType[] regionEventTypes() default {};
45+
46+
/**
47+
* An array for {@link org.apache.geode.cache.Region} names which this {@link org.apache.geode.cache.CacheListener}
48+
* will be link to. Not declaring any regions will result in the CacheListener to be configured against all defined
49+
* regions.
50+
*/
51+
String[] regions() default {};
52+
}

src/main/java/org/springframework/data/gemfire/config/support/CacheListenerPostProcessor.java

+73-37
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import java.util.List;
2929
import java.util.Optional;
3030

31+
import org.apache.geode.cache.CacheEvent;
32+
import org.apache.geode.cache.CacheListener;
3133
import org.apache.geode.cache.EntryEvent;
3234
import org.apache.geode.cache.Region;
3335
import org.apache.geode.cache.RegionEvent;
@@ -40,85 +42,119 @@
4042
import org.springframework.core.annotation.AnnotationAttributes;
4143
import org.springframework.core.annotation.AnnotationUtils;
4244
import org.springframework.data.gemfire.config.annotation.AsCacheListener;
45+
import org.springframework.data.gemfire.config.annotation.AsRegionEventListener;
4346
import org.springframework.data.gemfire.eventing.EventProcessorUtils;
4447
import org.springframework.data.gemfire.eventing.config.CacheListenerEventType;
48+
import org.springframework.data.gemfire.eventing.config.PojoCacheListenerWrapper;
49+
import org.springframework.data.gemfire.eventing.config.PojoRegionEventCacheListenerWrapper;
4550
import org.springframework.data.gemfire.eventing.config.RegionCacheListenerEventType;
4651
import org.springframework.data.gemfire.util.ArrayUtils;
4752
import org.springframework.util.Assert;
4853
import org.springframework.util.ObjectUtils;
4954
import org.springframework.util.ReflectionUtils;
5055

5156
/**
52-
*
57+
* A {@link BeanPostProcessor} to create and register {@link CacheListener}, annotated with {@link AsCacheListener}
58+
* and {@link AsRegionEventListener} onto the configured {@link Region}s
5359
*/
5460
@Configuration public class CacheListenerPostProcessor implements BeanPostProcessor, BeanFactoryAware {
5561

5662
private ConfigurableListableBeanFactory beanFactory;
5763

5864
@Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
5965

60-
registerAnyDeclaredCacheListenerAnnotatedMethods(bean);
66+
registerAnyDeclaredCacheListenerAnnotatedMethods(bean, AsCacheListener.class);
67+
registerAnyDeclaredCacheListenerAnnotatedMethods(bean, AsRegionEventListener.class);
6168

6269
return bean;
6370
}
6471

65-
private void registerAnyDeclaredCacheListenerAnnotatedMethods(Object bean) {
72+
private <T extends Annotation> void registerAnyDeclaredCacheListenerAnnotatedMethods(Object bean,
73+
Class<T> listenerAnnotationClazz) {
6674

6775
stream(nullSafeArray(ReflectionUtils.getAllDeclaredMethods(bean.getClass()), Method.class)).forEach(method -> {
6876

69-
AsCacheListener cacheListenerAnnotation = AnnotationUtils.getAnnotation(method, AsCacheListener.class);
77+
Optional<T> cacheListenerAnnotation = Optional.ofNullable(AnnotationUtils
78+
.getAnnotation(method, listenerAnnotationClazz));
7079

71-
if (cacheListenerAnnotation != null) {
80+
cacheListenerAnnotation.ifPresent(asCacheListener -> {
7281

7382
Assert.isTrue(Modifier.isPublic(method.getModifiers()), String
7483
.format("The bean [%s] method [%s] annotated with [%s] must be public", bean.getClass().getName(),
75-
method.getName(), AsCacheListener.class.getName()));
76-
77-
AnnotationAttributes cacheListenerAttributes = resolveAnnotationAttributes(cacheListenerAnnotation);
84+
method.getName(), listenerAnnotationClazz.getName()));
7885

79-
CacheListenerEventType[] eventTypes = (CacheListenerEventType[]) cacheListenerAttributes
80-
.get("eventTypes");
81-
RegionCacheListenerEventType[] regionEventTypes = (RegionCacheListenerEventType[]) cacheListenerAttributes
82-
.get("regionEventTypes");
86+
AnnotationAttributes cacheListenerAttributes = resolveAnnotationAttributes(asCacheListener);
8387

84-
if (regionEventTypes.length > 0 && eventTypes.length > 0) {
85-
throw new IllegalArgumentException(String
86-
.format("Populating both event and regionEvent types is not allowed for bean [%s] method [%s]",
87-
bean.getClass().getName(), method.getName()));
88-
}
88+
registerEventHandlers(bean, listenerAnnotationClazz, method, cacheListenerAttributes);
8989

90-
registerEventHandlerToRegion(bean, method, cacheListenerAttributes, eventTypes, regionEventTypes);
91-
}
90+
});
9291
});
9392
}
9493

95-
private void registerEventHandlerToRegion(Object bean, Method method, AnnotationAttributes cacheListenerAttributes,
96-
CacheListenerEventType[] eventTypes, RegionCacheListenerEventType[] regionEventTypes) {
97-
String[] regions = getRegionsForEventRegistration(cacheListenerAttributes.getStringArray("regions"),
98-
getBeanFactory());
99-
100-
if (eventTypes.length > 0) {
101-
EventProcessorUtils.validateCacheListenerMethodParameters(method,EntryEvent.class);
102-
registerCacheListenerEvents(bean, method, regions, eventTypes);
94+
private <T extends Annotation> void registerEventHandlers(Object bean, Class<T> listenerAnnotationClazz,
95+
Method method, AnnotationAttributes cacheListenerAttributes) {
96+
if (listenerAnnotationClazz.isAssignableFrom(AsCacheListener.class)) {
97+
registerCacheListenerEventHandler(bean, method, cacheListenerAttributes);
10398
}
104-
if (regionEventTypes.length > 0) {
105-
EventProcessorUtils.validateCacheListenerMethodParameters(method, RegionEvent.class);
106-
registerRegionCacheListenerEvents(bean, method, regions, regionEventTypes);
99+
else if (listenerAnnotationClazz.isAssignableFrom(AsRegionEventListener.class)) {
100+
registerRegionEventHandler(bean, method, cacheListenerAttributes);
107101
}
108102
}
109103

110-
private void registerCacheListenerEvents(Object bean, Method method, String[] regions,
111-
CacheListenerEventType[] eventTypes) {
104+
/**
105+
* Lookup {@link CacheListenerEventType} from the {@link AsCacheListener} annotation and create a {@link PojoCacheListenerWrapper}
106+
* of type {@link CacheListener} that would register itself onto a {@link Region} for the configured events
107+
*/
108+
private void registerCacheListenerEventHandler(Object bean, Method method,
109+
AnnotationAttributes cacheListenerAttributes) {
110+
CacheListenerEventType[] eventTypes = (CacheListenerEventType[]) cacheListenerAttributes
111+
.get("eventTypes");
112+
registerEventHandlerToRegion(method, cacheListenerAttributes,
113+
new PojoCacheListenerWrapper(method, bean, eventTypes), EntryEvent.class);
114+
}
112115

113-
EventProcessorUtils.registerCacheListenerOntoRegions(regions, method, bean, eventTypes, getBeanFactory());
116+
/**
117+
* Lookup {@link RegionCacheListenerEventType} from the {@link AsRegionEventListener} annotation and
118+
* create a {@link PojoRegionEventCacheListenerWrapper}
119+
* of type {@link CacheListener} that would register itself onto a {@link Region} for the configured
120+
* {@link Region} specific events
121+
*/
122+
private void registerRegionEventHandler(Object bean, Method method,
123+
AnnotationAttributes cacheListenerAttributes) {
124+
RegionCacheListenerEventType[] eventTypes = (RegionCacheListenerEventType[]) cacheListenerAttributes
125+
.get("regionEventTypes");
126+
registerEventHandlerToRegion(method, cacheListenerAttributes,
127+
new PojoRegionEventCacheListenerWrapper(method, bean, eventTypes), RegionEvent.class);
114128
}
115129

116-
private void registerRegionCacheListenerEvents(Object bean, Method method, String[] regions,
117-
RegionCacheListenerEventType[] regionEventTypes) {
118-
EventProcessorUtils
119-
.registerCacheListenerForRegionEventsOntoRegions(regions, method, bean, regionEventTypes, getBeanFactory());
130+
/**
131+
* Validates the method parameters to be of the correct type dependent on the eventing Annotation. It then registers
132+
* the defined {@link CacheListener} onto the defined set of {@link Region}.
133+
*
134+
* @param method - The event handler callback method for event handling type
135+
* @param cacheListenerAttributes - A set of {@link Annotation} attributes used to get the region names configured
136+
* on the annotation
137+
* @param cacheListener - The {@link CacheListener} to be registered onto the {@link Region}
138+
* @param eventClass - The expected method parameter type. Can be either {@link EntryEvent} or {@link RegionEvent}
139+
*/
140+
private <T extends CacheEvent> void registerEventHandlerToRegion(Method method,
141+
AnnotationAttributes cacheListenerAttributes, CacheListener cacheListener, Class<T> eventClass) {
142+
String[] regions = getRegionsForEventRegistration(cacheListenerAttributes.getStringArray("regions"),
143+
getBeanFactory());
144+
145+
EventProcessorUtils.validateCacheListenerMethodParameters(method, eventClass);
146+
EventProcessorUtils.registerCacheListenerToRegions(regions, beanFactory, cacheListener);
120147
}
121148

149+
/**
150+
* Takes an array of Region names. If empty, returns all configured {@link Region} names, otherwise returns the input
151+
* region name array
152+
*
153+
* @param regions - An Array of {@link Region} names. This can be empty and thus defaults to all configured {@link Region}
154+
* @param beanFactory - A {@link org.springframework.data.gemfire.ConfigurableRegionFactoryBean}
155+
* @return An array of {@link Region} names. If the input regions array is empty, the result will be an array with all
156+
* configured {@link Region} names
157+
*/
122158
private String[] getRegionsForEventRegistration(String[] regions, ConfigurableListableBeanFactory beanFactory) {
123159
if (ArrayUtils.isEmpty(regions)) {
124160
List<String> regionNames = new ArrayList<>();

src/main/java/org/springframework/data/gemfire/eventing/EventProcessorUtils.java

+3-13
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,22 @@
2323
import java.util.Optional;
2424

2525
import org.apache.geode.cache.CacheListener;
26-
import org.apache.geode.cache.EntryEvent;
2726
import org.apache.geode.cache.Region;
2827
import org.springframework.beans.factory.BeanFactory;
29-
import org.springframework.data.gemfire.eventing.config.CacheListenerEventType;
30-
import org.springframework.data.gemfire.eventing.config.PojoCacheListenerWrapper;
3128
import org.springframework.data.gemfire.eventing.config.PojoRegionEventCacheListenerWrapper;
3229
import org.springframework.data.gemfire.eventing.config.RegionCacheListenerEventType;
3330

3431
public class EventProcessorUtils {
3532

36-
public static void registerCacheListenerOntoRegions(String[] regions, Method method, Object bean,
37-
CacheListenerEventType[] eventTypes, BeanFactory beanFactory) {
38-
PojoCacheListenerWrapper cacheListenerWrapper = new PojoCacheListenerWrapper(method, bean, eventTypes);
39-
40-
registerCacheListenerToRegions(regions, beanFactory, cacheListenerWrapper);
41-
}
42-
43-
public static void registerCacheListenerForRegionEventsOntoRegions(String[] regions, Method method, Object bean,
33+
public static void registerRegionEvents(String[] regions, Method method, Object bean,
4434
RegionCacheListenerEventType[] regionEventTypes, BeanFactory beanFactory) {
4535
PojoRegionEventCacheListenerWrapper regionEventCacheListenerWrapper = new PojoRegionEventCacheListenerWrapper(
4636
method, bean, regionEventTypes);
4737

4838
registerCacheListenerToRegions(regions, beanFactory, regionEventCacheListenerWrapper);
4939
}
5040

51-
private static void registerCacheListenerToRegions(String[] regions, BeanFactory beanFactory,
41+
public static void registerCacheListenerToRegions(String[] regions, BeanFactory beanFactory,
5242
CacheListener cacheListener) {
5343
stream(regions).forEach(regionName -> {
5444
Optional<Region> regionBeanOptional = Optional.of(beanFactory.getBean(regionName, Region.class));
@@ -64,7 +54,7 @@ public static void validateCacheListenerMethodParameters(Method method, Class re
6454
.format("CacheListener method: %s does not currently support more than one parameter",
6555
method.getName()));
6656
}
67-
if (!parameterTypes[0].isAssignableFrom(EntryEvent.class)) {
57+
if (!parameterTypes[0].isAssignableFrom(requireParameterType)) {
6858
throw new IllegalArgumentException(String
6959
.format("CacheListener method: %s requires an %s parameter type", method.getName(),
7060
requireParameterType.getName()));

src/test/java/org/springframework/data/gemfire/config/annotation/AsCacheListenerCacheServerConfigurationTests.java

+13-31
Original file line numberDiff line numberDiff line change
@@ -52,35 +52,31 @@ protected static ReplicatedRegionFactoryBean<String, String> createRegionFactory
5252
return TestConfigurationWithIncorrectRegionEventParameter.class;
5353
}
5454

55-
protected Class<TestConfigurationWithSimpleCacheListener> getCacheListenerAnnotationSingleDefaultRegionsConfiguration() {
55+
@Override protected Class<TestConfigurationWithSimpleCacheListener> getCacheListenerAnnotationSingleDefaultRegionsConfiguration() {
5656
return TestConfigurationWithSimpleCacheListener.class;
5757
}
5858

59-
protected Class<TestConfigurationWithBothCacheListenerEventTypes> getCacheListenerAnnotationWithBothEventAndRegionEvents() {
60-
return TestConfigurationWithBothCacheListenerEventTypes.class;
61-
}
62-
63-
protected Class<TestConfigurationWithInvalidRegion> getCacheListenerAnnotationWithInvalidRegion() {
59+
@Override protected Class<TestConfigurationWithInvalidRegion> getCacheListenerAnnotationWithInvalidRegion() {
6460
return TestConfigurationWithInvalidRegion.class;
6561
}
6662

67-
protected Class<TestConfigurationWith2RegionsAnd2CacheListenersDefaulted> getCacheListenerAnnotationMultipleRegionsDefault() {
63+
@Override protected Class<TestConfigurationWith2RegionsAnd2CacheListenersDefaulted> getCacheListenerAnnotationMultipleRegionsDefault() {
6864
return TestConfigurationWith2RegionsAnd2CacheListenersDefaulted.class;
6965
}
7066

71-
protected Class<TestConfigurationWithSimpleCacheListenerAllEvents> getCacheListenerAnnotationSingleRegionAllEvents() {
67+
@Override protected Class<TestConfigurationWithSimpleCacheListenerAllEvents> getCacheListenerAnnotationSingleRegionAllEvents() {
7268
return TestConfigurationWithSimpleCacheListenerAllEvents.class;
7369
}
7470

75-
protected Class<TestConfigurationWithSimpleCacheListenerWith2Regions> getCacheListenerAnnotationAgainst2NamedRegions() {
71+
@Override protected Class<TestConfigurationWithSimpleCacheListenerWith2Regions> getCacheListenerAnnotationAgainst2NamedRegions() {
7672
return TestConfigurationWithSimpleCacheListenerWith2Regions.class;
7773
}
7874

7975
@Configuration @CacheServerApplication @EnableEventProcessing
8076
public static class TestConfigurationWithIncorrectRegionEventParameter {
8177

82-
@Bean("TestRegion") ReplicatedRegionFactoryBean getTestRegion(GemFireCache cache) {
83-
return createRegionFactoryBean(cache, "TestRegion");
78+
@Bean("TestRegion1") ReplicatedRegionFactoryBean getTestRegion(GemFireCache cache) {
79+
return createRegionFactoryBean(cache, "TestRegion1");
8480
}
8581

8682
@AsCacheListener(eventTypes = CacheListenerEventType.AFTER_CREATE)
@@ -91,8 +87,8 @@ public void afterCreateListener(RegionEvent event) {
9187
@Configuration @CacheServerApplication @EnableEventProcessing
9288
public static class TestConfigurationWithSimpleCacheListener {
9389

94-
@Bean("TestRegion") ReplicatedRegionFactoryBean getTestRegion(GemFireCache cache) {
95-
return createRegionFactoryBean(cache, "TestRegion");
90+
@Bean("TestRegion1") ReplicatedRegionFactoryBean getTestRegion(GemFireCache cache) {
91+
return createRegionFactoryBean(cache, "TestRegion1");
9692
}
9793

9894
@AsCacheListener(eventTypes = CacheListenerEventType.AFTER_CREATE)
@@ -107,25 +103,11 @@ public void afterUpdateListener(EntryEvent event) {
107103
}
108104
}
109105

110-
@Configuration @CacheServerApplication @EnableEventProcessing
111-
public static class TestConfigurationWithBothCacheListenerEventTypes {
112-
113-
@Bean("TestRegion") ReplicatedRegionFactoryBean getTestRegion(GemFireCache cache) {
114-
return createRegionFactoryBean(cache, "TestRegion");
115-
}
116-
117-
@AsCacheListener(eventTypes = CacheListenerEventType.AFTER_CREATE, regionEventTypes = RegionCacheListenerEventType.AFTER_REGION_CLEAR)
118-
public void afterCreateListener(EntryEvent event) {
119-
recordEvent(event);
120-
}
121-
122-
}
123-
124106
@Configuration @CacheServerApplication @EnableEventProcessing
125107
public static class TestConfigurationWithInvalidRegion {
126108

127-
@Bean("TestRegion") ReplicatedRegionFactoryBean getTestRegion(GemFireCache cache) {
128-
return createRegionFactoryBean(cache, "TestRegion");
109+
@Bean("TestRegion1") ReplicatedRegionFactoryBean getTestRegion(GemFireCache cache) {
110+
return createRegionFactoryBean(cache, "TestRegion1");
129111
}
130112

131113
@AsCacheListener(eventTypes = CacheListenerEventType.AFTER_CREATE, regions = "TestRegion2")
@@ -138,8 +120,8 @@ public void afterCreateListener(EntryEvent event) {
138120
@Configuration @CacheServerApplication @EnableEventProcessing
139121
public static class TestConfigurationWithSimpleCacheListenerAllEvents {
140122

141-
@Bean("TestRegion") ReplicatedRegionFactoryBean getTestRegion(GemFireCache cache) {
142-
return createRegionFactoryBean(cache, "TestRegion");
123+
@Bean("TestRegion1") ReplicatedRegionFactoryBean getTestRegion(GemFireCache cache) {
124+
return createRegionFactoryBean(cache, "TestRegion1");
143125
}
144126

145127
@AsCacheListener(eventTypes = CacheListenerEventType.ALL) public void afterCreateListener(EntryEvent event) {

0 commit comments

Comments
 (0)