From fcf1842e02271ba1b229bd6bac7a3ce6d83d7992 Mon Sep 17 00:00:00 2001 From: John Blum Date: Fri, 24 Sep 2021 16:41:53 -0700 Subject: [PATCH] Add Spring BeanPostProcessor to initialize an Apache Geode Region (bean) with data on Spring container initialization. --- .../RegionDataInitializingPostProcessor.java | 118 ++++++++++++++++++ ...ializingPostProcessorIntegrationTests.java | 103 +++++++++++++++ 2 files changed, 221 insertions(+) create mode 100644 spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/objects/geode/cache/RegionDataInitializingPostProcessor.java create mode 100644 spring-data-geode-test/src/test/java/org/springframework/data/gemfire/tests/objects/geode/cache/RegionDataInitializingPostProcessorIntegrationTests.java diff --git a/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/objects/geode/cache/RegionDataInitializingPostProcessor.java b/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/objects/geode/cache/RegionDataInitializingPostProcessor.java new file mode 100644 index 0000000..df74956 --- /dev/null +++ b/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/objects/geode/cache/RegionDataInitializingPostProcessor.java @@ -0,0 +1,118 @@ +/* + * Copyright 2019 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 + * + * https://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.tests.objects.geode.cache; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +import org.apache.geode.cache.Region; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.lang.NonNull; +import org.springframework.util.Assert; + +/** + * Spring {@link BeanPostProcessor} used to initialize an Apache Geode {@link Region} with data. + * + * @author John Blum + * @see java.util.Map + * @see java.util.function.Function + * @see org.apache.geode.cache.Region + * @see org.springframework.beans.factory.config.BeanPostProcessor + * @since 0.0.26 + */ +@SuppressWarnings("unused") +public class RegionDataInitializingPostProcessor implements BeanPostProcessor { + + public static EntityIdentifierBuilder withRegion(@NonNull String regionBeanName) { + return new EntityIdentifierBuilder<>(new RegionDataInitializingPostProcessor<>(regionBeanName)); + } + + private Function entityIdentifierFunction; + + private final Map regionData = new ConcurrentHashMap<>(); + + private final String regionBeanName; + + protected RegionDataInitializingPostProcessor(@NonNull String regionBeanName) { + + Assert.hasText(regionBeanName, String.format("Region bean name [%s] must be specified", regionBeanName)); + + this.regionBeanName = regionBeanName; + } + + public @NonNull String getRegionBeanName() { + return this.regionBeanName; + } + + protected @NonNull Function getEntityIdentifierFunction() { + return this.entityIdentifierFunction; + } + + public @NonNull Map getRegionData() { + return this.regionData; + } + + protected boolean isTargetRegion(Object bean, String beanName) { + return bean instanceof Region && getRegionBeanName().equals(beanName); + } + + @Override + @SuppressWarnings("unchecked") + public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException { + + if (isTargetRegion(bean, beanName)) { + ((Region) bean).putAll(getRegionData()); + } + + return bean; + } + + public RegionDataInitializingPostProcessor store(T entity) { + getRegionData().put(getEntityIdentifierFunction().apply(entity), entity); + return this; + } + + protected RegionDataInitializingPostProcessor useEntityIdentifierFunction( + @NonNull Function entityIdentifierFunction) { + + Assert.notNull(entityIdentifierFunction, + "The Function used to resolve the entity's identify (identifier) must not be null"); + + this.entityIdentifierFunction = entityIdentifierFunction; + + return this; + } + + @SuppressWarnings("unused") + public static class EntityIdentifierBuilder { + + private final RegionDataInitializingPostProcessor postProcessor; + + private EntityIdentifierBuilder(@NonNull RegionDataInitializingPostProcessor postProcessor) { + Assert.notNull(postProcessor, "RegionDataInitializingPostProcess must not be null"); + this.postProcessor = postProcessor; + } + + public RegionDataInitializingPostProcessor useAsEntityIdentifier( + @NonNull Function entityIdentifierFunction) { + + return this.postProcessor.useEntityIdentifierFunction(entityIdentifierFunction); + } + } +} diff --git a/spring-data-geode-test/src/test/java/org/springframework/data/gemfire/tests/objects/geode/cache/RegionDataInitializingPostProcessorIntegrationTests.java b/spring-data-geode-test/src/test/java/org/springframework/data/gemfire/tests/objects/geode/cache/RegionDataInitializingPostProcessorIntegrationTests.java new file mode 100644 index 0000000..0ce1f47 --- /dev/null +++ b/spring-data-geode-test/src/test/java/org/springframework/data/gemfire/tests/objects/geode/cache/RegionDataInitializingPostProcessorIntegrationTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2019 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 + * + * https://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.tests.objects.geode.cache; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.apache.geode.cache.DataPolicy; +import org.apache.geode.cache.client.ClientRegionShortcut; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.config.annotation.ClientCacheApplication; +import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions; +import org.springframework.data.gemfire.mapping.annotation.Region; +import org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport; +import org.springframework.data.gemfire.tests.unit.annotation.GemFireUnitTest; +import org.springframework.test.context.junit4.SpringRunner; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +/** + * Integration Tests for {@link RegionDataInitializingPostProcessor}. + * + * @author John Blum + * @see org.junit.Test + * @see org.apache.geode.cache.Region + * @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication + * @see org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport + * @see org.springframework.data.gemfire.tests.unit.annotation.GemFireUnitTest + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @since 0.0.26 + */ +@RunWith(SpringRunner.class) +@GemFireUnitTest +@SuppressWarnings("unused") +public class RegionDataInitializingPostProcessorIntegrationTests extends IntegrationTestsSupport { + + @Autowired + @Qualifier("Users") + private org.apache.geode.cache.Region users; + + @Test + public void assertUsersRegionMetadata() { + + assertThat(this.users).isNotNull(); + assertThat(this.users.getName()).isEqualTo("Users"); + assertThat(this.users.getAttributes()).isNotNull(); + assertThat(this.users.getAttributes().getDataPolicy()).isEqualTo(DataPolicy.NORMAL); + } + + @Test + public void assetUsersRegionData() { + + assertThat(this.users).hasSize(2); + assertThat(this.users).containsKeys("jonDoe", "janeDoe"); + assertThat(this.users).containsValues(User.with("jonDoe"), User.with("janeDoe")); + } + + @ClientCacheApplication + @EnableEntityDefinedRegions(basePackageClasses = User.class, clientRegionShortcut = ClientRegionShortcut.LOCAL) + static class TestConfiguration { + + @Bean + RegionDataInitializingPostProcessor usersRegionDataInitializer() { + + return RegionDataInitializingPostProcessor.withRegion("Users") + .useAsEntityIdentifier(User::getName) + .store(User.with("jonDoe")) + .store(User.with("janeDoe")); + } + } + + @Getter + @Region("Users") + @ToString(of = "name") + @EqualsAndHashCode(of = "name") + @RequiredArgsConstructor(staticName = "with") + static class User { + @lombok.NonNull + private final String name; + } +}