Enables Spring Locator-based applications configured and bootstrapped with SDG to use the GemfireBeanFactoryLocator to autowire Apache Geode components.

Closes #620.
This commit is contained in:
John Blum
2022-08-19 16:34:41 -07:00
parent fd7d00d895
commit d5c4fb041c
7 changed files with 198 additions and 55 deletions

View File

@@ -15,9 +15,6 @@
*/
package org.springframework.data.gemfire;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -32,8 +29,10 @@ import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.gemfire.config.annotation.LocatorConfigurer;
import org.springframework.data.gemfire.support.AbstractFactoryBeanSupport;
import org.springframework.data.gemfire.support.GemfireBeanFactoryLocator;
import org.springframework.data.gemfire.util.ArrayUtils;
import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.lang.Nullable;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -57,8 +56,10 @@ public class LocatorFactoryBean extends AbstractFactoryBeanSupport<Locator> impl
public static final int DEFAULT_PORT = 10334;
public static final String DEFAULT_LOG_LEVEL = GemFireProperties.LOG_LEVEL.getDefaultValueAsString();
private static final String LOCATORS_PROPERTY = GemFireProperties.LOCATORS.toString();
public static final String LOG_LEVEL_PROPERTY = GemFireProperties.LOG_LEVEL.toString();
private static final String LOCATORS_PROPERTY = GemFireProperties.LOCATORS.getName();
public static final String LOG_LEVEL_PROPERTY = GemFireProperties.LOG_LEVEL.getName();
private boolean useBeanFactoryLocator = false;
private Integer port = DEFAULT_PORT;
@@ -82,16 +83,22 @@ public class LocatorFactoryBean extends AbstractFactoryBeanSupport<Locator> impl
@Override
public void afterPropertiesSet() throws Exception {
applyLocatorConfigurers(getCompositeLocatorConfigurer());
applyLocatorConfigurers();
initializeBeanFactoryLocator();
init();
}
protected void applyLocatorConfigurers() {
applyLocatorConfigurers(getCompositeLocatorConfigurer());
}
protected void applyLocatorConfigurers(LocatorConfigurer... locatorConfigurers) {
applyLocatorConfigurers(Arrays.asList(nullSafeArray(locatorConfigurers, LocatorConfigurer.class)));
applyLocatorConfigurers(Arrays.asList(ArrayUtils.nullSafeArray(locatorConfigurers, LocatorConfigurer.class)));
}
protected void applyLocatorConfigurers(Iterable<LocatorConfigurer> locatorConfigurers) {
StreamSupport.stream(nullSafeIterable(locatorConfigurers).spliterator(), false)
StreamSupport.stream(CollectionUtils.nullSafeIterable(locatorConfigurers).spliterator(), false)
.forEach(locatorConfigurer -> locatorConfigurer.configure(getBeanName(), this));
}
@@ -142,6 +149,13 @@ public class LocatorFactoryBean extends AbstractFactoryBeanSupport<Locator> impl
return locatorBuilder;
}
protected void initializeBeanFactoryLocator() {
if (isUseBeanFactoryLocator()) {
GemfireBeanFactoryLocator.newBeanFactoryLocator(getBeanFactory(), getBeanName());
}
}
protected LocatorLauncher.Builder newLocatorLauncherBuilder() {
return new LocatorLauncher.Builder();
}
@@ -162,8 +176,8 @@ public class LocatorFactoryBean extends AbstractFactoryBeanSupport<Locator> impl
return this.locatorLauncher;
}
@Nullable @Override
public Locator getObject() throws Exception {
@Override
public @NonNull Locator getObject() throws Exception {
Locator locator = getLocator();
@@ -172,8 +186,8 @@ public class LocatorFactoryBean extends AbstractFactoryBeanSupport<Locator> impl
return locator;
}
@Nullable @Override
public Class<?> getObjectType() {
@Override
public @NonNull Class<?> getObjectType() {
Locator locator = getLocator();
@@ -190,7 +204,7 @@ public class LocatorFactoryBean extends AbstractFactoryBeanSupport<Locator> impl
.filter(StringUtils::hasText);
}
public LocatorConfigurer getCompositeLocatorConfigurer() {
public @NonNull LocatorConfigurer getCompositeLocatorConfigurer() {
return this.compositeLocatorConfigurer;
}
@@ -198,7 +212,7 @@ public class LocatorFactoryBean extends AbstractFactoryBeanSupport<Locator> impl
this.gemfireProperties = gemfireProperties;
}
public Properties getGemFireProperties() {
public @NonNull Properties getGemFireProperties() {
if (this.gemfireProperties == null) {
this.gemfireProperties = new Properties();
@@ -218,7 +232,7 @@ public class LocatorFactoryBean extends AbstractFactoryBeanSupport<Locator> impl
}
public void setLocatorConfigurers(LocatorConfigurer... locatorConfigurers) {
setLocatorConfigurers(Arrays.asList(nullSafeArray(locatorConfigurers, LocatorConfigurer.class)));
setLocatorConfigurers(Arrays.asList(ArrayUtils.nullSafeArray(locatorConfigurers, LocatorConfigurer.class)));
}
public void setLocatorConfigurers(List<LocatorConfigurer> locatorConfigurers) {
@@ -263,4 +277,12 @@ public class LocatorFactoryBean extends AbstractFactoryBeanSupport<Locator> impl
public Integer getPort() {
return this.port;
}
public void setUseBeanFactoryLocator(boolean useBeanFactoryLocator) {
this.useBeanFactoryLocator = useBeanFactoryLocator;
}
public boolean isUseBeanFactoryLocator() {
return this.useBeanFactoryLocator;
}
}

View File

@@ -20,8 +20,10 @@ import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.data.gemfire.LocatorFactoryBean;
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
import org.springframework.data.gemfire.support.GemfireBeanFactoryLocator;
import org.springframework.lang.NonNull;
/**
* The {@link BeanFactoryLocatorConfiguration} class extends the Spring application configuration by enabling
@@ -33,8 +35,10 @@ import org.springframework.data.gemfire.support.GemfireBeanFactoryLocator;
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.data.gemfire.CacheFactoryBean
* @see org.springframework.data.gemfire.LocatorFactoryBean
* @see org.springframework.data.gemfire.client.ClientCacheFactoryBean
* @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer
* @see org.springframework.data.gemfire.config.annotation.LocatorConfigurer
* @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer
* @see org.springframework.data.gemfire.config.annotation.EnableBeanFactoryLocator
* @see org.springframework.data.gemfire.support.GemfireBeanFactoryLocator
@@ -64,6 +68,10 @@ public class BeanFactoryLocatorConfiguration {
((CacheFactoryBean) bean).setUseBeanFactoryLocator(true);
}
if (bean instanceof LocatorFactoryBean) {
((LocatorFactoryBean) bean).setUseBeanFactoryLocator(true);
}
return bean;
}
};
@@ -73,12 +81,25 @@ public class BeanFactoryLocatorConfiguration {
* Declares and registers a {@link ClientCacheConfigurer} bean to configure a {@link ClientCacheFactoryBean}
* by setting the {@literal useBeanFactoryLocator} property to {@literal true}.
*
* @return a {@link ClientCacheConfigurer} used to configure and set the SDG {@link ClientCacheFactoryBean}'s
* @return a {@link ClientCacheConfigurer} used to configure and set the SDG {@link ClientCacheFactoryBean}
* {@literal useBeanFactoryLocator} property to {@literal true}.
* @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer
*/
@Bean
public ClientCacheConfigurer useBeanFactoryLocatorClientCacheConfigurer() {
public @NonNull ClientCacheConfigurer useBeanFactoryLocatorClientCacheConfigurer() {
return (beanName, bean) -> bean.setUseBeanFactoryLocator(true);
}
/**
* Declares and registers a {@link LocatorConfigurer} bean to configure a {@link LocatorFactoryBean}
* by setting the {@literal useBeanFactoryLocator} property to {@literal true}.
*
* @return a {@link LocatorConfigurer} used to configure and set the SDG {@link LocatorFactoryBean}
* {@literal useBeanFactoryLocator} property to {@literal true}.
* @see org.springframework.data.gemfire.config.annotation.LocatorConfigurer
*/
@Bean
public @NonNull LocatorConfigurer useBeanFactoryLocatorLocatorConfigurer() {
return (beanName, bean) -> bean.setUseBeanFactoryLocator(true);
}
@@ -86,12 +107,12 @@ public class BeanFactoryLocatorConfiguration {
* Declares and registers a {@link PeerCacheConfigurer} bean to configure a {@link CacheFactoryBean}
* by setting the {@literal useBeanFactoryLocator} property to {@literal true}.
*
* @return a {@link PeerCacheConfigurer} used to configure and set the SDG {@link CacheFactoryBean}'s
* @return a {@link PeerCacheConfigurer} used to configure and set the SDG {@link CacheFactoryBean}
* {@literal useBeanFactoryLocator} property to {@literal true}.
* @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer
*/
@Bean
public PeerCacheConfigurer useBeanFactoryLocatorPeerCacheConfigurer() {
public @NonNull PeerCacheConfigurer useBeanFactoryLocatorPeerCacheConfigurer() {
return (beanName, bean) -> bean.setUseBeanFactoryLocator(true);
}
}

View File

@@ -25,8 +25,10 @@ import java.lang.annotation.Target;
import org.apache.geode.distributed.Locator;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.gemfire.support.GemfireBeanFactoryLocator;
/**
* The {@link LocatorApplication} {@link Annotation} enables a Spring Data for Apache Geode & Pivotal GemFire
@@ -90,7 +92,7 @@ public @interface LocatorApplication {
String logLevel() default LocatorApplicationConfiguration.DEFAULT_LOG_LEVEL;
/**
* Configures the name of the {@link Locator} application.
* Configures the {@link String name} of the {@link Locator} application.
*
* Defaults to {@literal SpringBasedLocatorApplication}.
*
@@ -110,4 +112,15 @@ public @interface LocatorApplication {
*/
int port() default LocatorApplicationConfiguration.DEFAULT_PORT;
/**
* Configures the {@link Locator} application with the {@link GemfireBeanFactoryLocator} in order to look up
* the Spring {@link BeanFactory} used to auto-wire, configure and initialize Apache Geode components
* created in a non-Spring managed, Apache Geode context (for example: {@literal cache.xml}).
*
* Defaults to {@literal false}.
*
* Use {@literal spring.data.gemfire.use-bean-factory-locator} property in {@literal application.properties}.
*/
boolean useBeanFactoryLocator() default LocatorApplicationConfiguration.DEFAULT_USE_BEAN_FACTORY_LOCATOR;
}

View File

@@ -38,6 +38,8 @@ import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.data.gemfire.LocatorFactoryBean;
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
@@ -64,12 +66,14 @@ import org.springframework.util.ClassUtils;
@SuppressWarnings("unused")
public class LocatorApplicationConfiguration extends AbstractAnnotationConfigSupport implements ImportAware {
public static final boolean DEFAULT_USE_BEAN_FACTORY_LOCATOR = false;
public static final int DEFAULT_PORT = 10334;
public static final String DEFAULT_LOG_LEVEL = "warn";
public static final String DEFAULT_NAME = "SpringBasedLocatorApplication";
protected static final String EXCLUSIVE_LOCATOR_APPLICATION_ERROR_MESSAGE =
protected static final String LOCATOR_APPLICATION_MUTEX_ERROR_MESSAGE =
"A Spring application cannot be both a Cache and a Locator application;"
+ " You may annotate your Spring application main class with 1 of"
+ " [@ClientCacheApplication, @CacheServerApplication, @PeerCacheApplication] or @LocatorApplication;"
@@ -81,6 +85,8 @@ public class LocatorApplicationConfiguration extends AbstractAnnotationConfigSup
private static final List<String> CACHE_FACTORY_BEAN_CLASS_NAMES =
Arrays.asList(CacheFactoryBean.class.getName(), ClientCacheFactoryBean.class.getName());
private boolean useBeanFactoryLocator = DEFAULT_USE_BEAN_FACTORY_LOCATOR;
private int port = DEFAULT_PORT;
@SuppressWarnings("all")
@@ -93,13 +99,28 @@ public class LocatorApplicationConfiguration extends AbstractAnnotationConfigSup
private String logLevel;
private String name;
/**
* Returns the {@link LocatorApplication} annotation used to configure and bootstrap a {@link Locator}-based,
* Spring application.
*
* @return the {@link LocatorApplication} annotation {@link Class type}.
* @see org.springframework.data.gemfire.config.annotation.LocatorApplication
*/
@Override
protected Class<? extends Annotation> getAnnotationType() {
protected @NonNull Class<? extends Annotation> getAnnotationType() {
return LocatorApplication.class;
}
/**
* Returns a {@link BeanFactoryPostProcessor} used to enforce that the Spring application can only be
* an Apache Geode {@link GemFireCache cache} application or an Apache Geode {@link Locator} application,
* but not both.
*
* @return a {@link BeanFactoryPostProcessor} used to enforce the Spring, Apache Geode application type.
* @see org.springframework.beans.factory.config.BeanFactoryPostProcessor
*/
@Bean
BeanFactoryPostProcessor exclusiveLocatorApplicationBeanFactoryPostProcessor() {
@NonNull BeanFactoryPostProcessor locatorApplicationMutexBeanFactoryPostProcessor() {
return configurableListableBeanFactory -> {
@@ -107,25 +128,23 @@ public class LocatorApplicationConfiguration extends AbstractAnnotationConfigSup
boolean match = Arrays.stream(nullSafeArray(beanDefinitionNames, String.class))
.map(configurableListableBeanFactory::getBeanDefinition)
.anyMatch(beanDefinition ->
.anyMatch(beanDefinition -> resolveBeanClassName(beanDefinition)
.map(beanClassName -> {
try {
resolveBeanClassName(beanDefinition)
.map(beanClassName -> {
try {
Class<?> possibleCacheType =
ClassUtils.resolveClassName(beanClassName, getBeanClassLoader());
Class<?> possibleCacheType =
ClassUtils.resolveClassName(beanClassName, getBeanClassLoader());
return isCacheType(possibleCacheType);
}
catch (Throwable ignore) {
return CACHE_FACTORY_BEAN_CLASS_NAMES.contains(beanClassName);
}
})
.orElse(false));
return isCacheType(possibleCacheType);
}
catch (Throwable ignore) {
return CACHE_FACTORY_BEAN_CLASS_NAMES.contains(beanClassName);
}
})
.orElse(false));
if (match) {
throw new BeanDefinitionStoreException(EXCLUSIVE_LOCATOR_APPLICATION_ERROR_MESSAGE);
throw new BeanDefinitionStoreException(LOCATOR_APPLICATION_MUTEX_ERROR_MESSAGE);
}
};
}
@@ -136,8 +155,15 @@ public class LocatorApplicationConfiguration extends AbstractAnnotationConfigSup
&& (CacheFactoryBean.class.isAssignableFrom(type) || GemFireCache.class.isAssignableFrom(type));
}
/**
* Process the {@link Annotation} metadata from the {@link LocatorApplication} annotation.
*
* @param importMetadata {@link AnnotationMetadata} containing metadata from the {@link LocatorApplication}
* annotation annotated on the Spring application {@link Configuration} {@link Class}.
* @see org.springframework.core.type.AnnotationMetadata
*/
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
public void setImportMetadata(@NonNull AnnotationMetadata importMetadata) {
if (isAnnotationPresent(importMetadata)) {
@@ -160,6 +186,9 @@ public class LocatorApplicationConfiguration extends AbstractAnnotationConfigSup
setPort(resolveProperty(locatorProperty("port"),
locatorApplicationAnnotationAttributes.<Integer>getNumber("port")));
setUseBeanFactoryLocator(resolveProperty("use-bean-factory-locator", Boolean.class,
locatorApplicationAnnotationAttributes.getBoolean("useBeanFactoryLocator")));
}
}
@@ -175,6 +204,7 @@ public class LocatorApplicationConfiguration extends AbstractAnnotationConfigSup
locatorFactoryBean.setLogLevel(getLogLevel());
locatorFactoryBean.setName(getName());
locatorFactoryBean.setPort(getPort());
locatorFactoryBean.setUseBeanFactoryLocator(isUseBeanFactoryLocator());
return locatorFactoryBean;
}
@@ -183,47 +213,47 @@ public class LocatorApplicationConfiguration extends AbstractAnnotationConfigSup
return Optional.ofNullable(this.locatorConfigurers)
.filter(locatorConfigurers -> !locatorConfigurers.isEmpty())
.orElseGet(() ->
Collections.singletonList(LazyResolvingComposableLocatorConfigurer.create(getBeanFactory())));
.orElseGet(() -> Collections.singletonList(LazyResolvingComposableLocatorConfigurer
.create(getBeanFactory())));
}
public void setBindAddress(String bindAddress) {
public void setBindAddress(@Nullable String bindAddress) {
this.bindAddress = bindAddress;
}
public String getBindAddress() {
public @Nullable String getBindAddress() {
return this.bindAddress;
}
public void setHostnameForClients(String hostnameForClients) {
public void setHostnameForClients(@Nullable String hostnameForClients) {
this.hostnameForClients = hostnameForClients;
}
public String getHostnameForClients() {
public @Nullable String getHostnameForClients() {
return this.hostnameForClients;
}
public void setLocators(String locators) {
public void setLocators(@Nullable String locators) {
this.locators = locators;
}
public String getLocators() {
public @Nullable String getLocators() {
return this.locators;
}
public void setLogLevel(String logLevel) {
public void setLogLevel(@Nullable String logLevel) {
this.logLevel = logLevel;
}
public String getLogLevel() {
public @Nullable String getLogLevel() {
return this.logLevel;
}
public void setName(String name) {
public void setName(@Nullable String name) {
this.name = name;
}
public String getName() {
public @Nullable String getName() {
return this.name;
}
@@ -234,4 +264,12 @@ public class LocatorApplicationConfiguration extends AbstractAnnotationConfigSup
public int getPort() {
return this.port;
}
public boolean isUseBeanFactoryLocator() {
return this.useBeanFactoryLocator;
}
public void setUseBeanFactoryLocator(boolean useBeanFactoryLocator) {
this.useBeanFactoryLocator = useBeanFactoryLocator;
}
}

View File

@@ -16,11 +16,19 @@
package org.springframework.data.gemfire.config.annotation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.data.gemfire.LocatorFactoryBean;
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
/**
@@ -36,7 +44,7 @@ import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
*/
public class EnableBeanFactoryLocatorConfigurationUnitTests {
private BeanFactoryLocatorConfiguration configuration = new BeanFactoryLocatorConfiguration();
private final BeanFactoryLocatorConfiguration configuration = new BeanFactoryLocatorConfiguration();
private void testUseBeanFactoryLocatorBeanPostProcessorProcessesBean(Object cacheBean) {
@@ -71,6 +79,28 @@ public class EnableBeanFactoryLocatorConfigurationUnitTests {
assertThat(clientCacheFactoryBean.isUseBeanFactoryLocator()).isTrue();
}
@Test
public void useBeanFactoryLocatorBeanPostProcessorProcessesLocatorFactoryBean() {
LocatorFactoryBean locatorFactoryBean = new LocatorFactoryBean();
assertThat(locatorFactoryBean.isUseBeanFactoryLocator()).isFalse();
testUseBeanFactoryLocatorBeanPostProcessorProcessesBean(locatorFactoryBean);
assertThat(locatorFactoryBean.isUseBeanFactoryLocator()).isTrue();
}
@Test
public void useBeanFactoryLocatorBeanPostProcessorWillNotProcessObject() {
Object mockObject = mock(Object.class);
testUseBeanFactoryLocatorBeanPostProcessorProcessesBean(mockObject);
verifyNoInteractions(mockObject);
}
@Test
public void useBeanFactoryLocatorClientCacheConfigurerIsCorrect() throws Exception {
@@ -84,6 +114,23 @@ public class EnableBeanFactoryLocatorConfigurationUnitTests {
assertThat(factoryBean.isUseBeanFactoryLocator()).isTrue();
}
@Test
public void useBeanFactoryLocatorLocatorConfigurerIsCorrect() throws Exception {
LocatorFactoryBean factoryBean = spy(new LocatorFactoryBean());
doNothing().when(factoryBean).init();
assertThat(factoryBean.isUseBeanFactoryLocator()).isFalse();
factoryBean.setLocatorConfigurers(this.configuration.useBeanFactoryLocatorLocatorConfigurer());
factoryBean.afterPropertiesSet();
assertThat(factoryBean.isUseBeanFactoryLocator()).isTrue();
verify(factoryBean, times(1)).setUseBeanFactoryLocator(eq(true));
}
@Test
public void useBeanFactoryLocatorPeerCacheConfigurerIsCorrect() throws Exception {

View File

@@ -69,7 +69,7 @@ public class LocatorApplicationCannotCoexistWithCacheApplicationIntegrationTests
catch (BeanDefinitionStoreException expected) {
assertThat(expected)
.hasMessage(LocatorApplicationConfiguration.EXCLUSIVE_LOCATOR_APPLICATION_ERROR_MESSAGE);
.hasMessage(LocatorApplicationConfiguration.LOCATOR_APPLICATION_MUTEX_ERROR_MESSAGE);
assertThat(expected).hasNoCause();

View File

@@ -66,6 +66,7 @@ public class LocatorApplicationConfigurationIntegrationTests extends Integration
assertThat(this.locatorFactoryBean.getLogLevel()).isEqualTo("WARN");
assertThat(this.locatorFactoryBean.getName().orElse(null)).isEqualTo("MockLocator");
assertThat(this.locatorFactoryBean.getPort()).isEqualTo(9876);
assertThat(this.locatorFactoryBean.isUseBeanFactoryLocator()).isTrue();
}
@EnableGemFireMockObjects
@@ -75,7 +76,8 @@ public class LocatorApplicationConfigurationIntegrationTests extends Integration
locators = "host1[1234],host2[6789]",
logLevel = "WARN",
name = "MockLocator",
port = 9876
port = 9876,
useBeanFactoryLocator = true
)
static class TestConfiguration {