From ad0ad251a19281150a6695c79b8cf254b7338c5a Mon Sep 17 00:00:00 2001 From: John Blum Date: Sun, 12 May 2019 23:18:31 -0700 Subject: [PATCH] DATAGEODE-187 - Configure and start GemFire Locators with SDG config. --- .../data/gemfire/LocatorFactoryBean.java | 242 +++++++++++ .../config/annotation/EnableLocator.java | 14 +- .../config/annotation/LocatorApplication.java | 104 +++++ .../LocatorApplicationConfiguration.java | 239 +++++++++++ .../config/annotation/LocatorConfigurer.java | 50 +++ .../gemfire/LocatorFactoryBeanUnitTests.java | 389 ++++++++++++++++++ ...tWithCacheApplicationIntegrationTests.java | 122 ++++++ ...ApplicationConfigurerIntegrationTests.java | 122 ++++++ .../LocatorApplicationIntegrationTests.java | 106 +++++ ...ApplicationPropertiesIntegrationTests.java | 139 +++++++ 10 files changed, 1521 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/springframework/data/gemfire/LocatorFactoryBean.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/LocatorApplication.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationConfiguration.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/LocatorConfigurer.java create mode 100644 src/test/java/org/springframework/data/gemfire/LocatorFactoryBeanUnitTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationCannotCoexistWithCacheApplicationIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationConfigurerIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationPropertiesIntegrationTests.java diff --git a/src/main/java/org/springframework/data/gemfire/LocatorFactoryBean.java b/src/main/java/org/springframework/data/gemfire/LocatorFactoryBean.java new file mode 100644 index 00000000..49a04dd8 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/LocatorFactoryBean.java @@ -0,0 +1,242 @@ +/* + * Copyright 2018 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 + * + * http://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; + +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeList; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Properties; +import java.util.stream.StreamSupport; + +import org.apache.geode.distributed.Locator; +import org.apache.geode.distributed.LocatorLauncher; +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.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Spring {@link FactoryBean} used to configure and initialize (bootstrap) an Apache Geode or Pivotal GemFire + * {@link Locator} using the {@link LocatorLauncher} class. + * + * @author John Blum + * @see java.util.Properties + * @see org.apache.geode.distributed.Locator + * @see org.apache.geode.distributed.LocatorLauncher + * @see org.springframework.beans.factory.FactoryBean + * @see org.springframework.beans.factory.InitializingBean + * @see org.springframework.data.gemfire.config.annotation.LocatorConfigurer + * @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport + * @since 2.2.0 + */ +@SuppressWarnings("unused") +public class LocatorFactoryBean extends AbstractFactoryBeanSupport implements InitializingBean { + + public static final int DEFAULT_PORT = 10334; + + public static final String DEFAULT_LOG_LEVEL = "config"; + public static final String LOG_LEVEL_PROPERTY = "log-level"; + + private Integer port = DEFAULT_PORT; + + private List locatorConfigurers = new ArrayList<>(); + + private Locator locator; + + private LocatorConfigurer compositeLocatorConfigurer = (beanName, bean) -> + nullSafeList(this.locatorConfigurers).forEach(locatorConfigurer -> + locatorConfigurer.configure(beanName, bean)); + + private LocatorLauncher locatorLauncher; + + private Properties gemfireProperties; + + private String bindAddress; + private String hostnameForClients; + private String logLevel; + private String name; + + @Override + public void afterPropertiesSet() throws Exception { + applyLocatorConfigurers(getCompositeLocatorConfigurer()); + init(); + } + + protected void applyLocatorConfigurers(LocatorConfigurer... locatorConfigurers) { + applyLocatorConfigurers(Arrays.asList(nullSafeArray(locatorConfigurers, LocatorConfigurer.class))); + } + + protected void applyLocatorConfigurers(Iterable locatorConfigurers) { + StreamSupport.stream(nullSafeIterable(locatorConfigurers).spliterator(), false) + .forEach(locatorConfigurer -> locatorConfigurer.configure(getBeanName(), this)); + } + + public void init() { + + LocatorLauncher.Builder locatorBuilder = configureGemfireProperties(newLocatorLauncherBuilder()); + + getBindAddress().ifPresent(locatorBuilder::setBindAddress); + getHostnameForClients().ifPresent(locatorBuilder::setHostnameForClients); + getName().ifPresent(locatorBuilder::setMemberName); + + locatorBuilder.set(LOG_LEVEL_PROPERTY, getLogLevel()); + locatorBuilder.setPort(getPort()); + + locatorBuilder = postProcess(locatorBuilder); + + this.locatorLauncher = postProcess(locatorBuilder.build()); + + LocatorLauncher.LocatorState locatorState = this.locatorLauncher.start(); + + /* + if (LocatorLauncher.Status.ONLINE.equals(locatorState.getStatus())) { + // log warning + } + */ + + this.locator = this.locatorLauncher.getLocator(); + } + + protected LocatorLauncher.Builder configureGemfireProperties(LocatorLauncher.Builder locatorBuilder) { + + Properties gemfireProperties = getGemFireProperties(); + + gemfireProperties.stringPropertyNames().stream() + .forEach(propertyName -> locatorBuilder.set(propertyName, gemfireProperties.getProperty(propertyName))); + + return locatorBuilder; + } + + protected LocatorLauncher.Builder newLocatorLauncherBuilder() { + return new LocatorLauncher.Builder(); + } + + protected LocatorLauncher.Builder postProcess(LocatorLauncher.Builder locatorBuilder) { + return locatorBuilder; + } + + protected LocatorLauncher postProcess(LocatorLauncher locatorLauncher) { + return locatorLauncher; + } + + public Locator getLocator() { + return this.locator; + } + + public LocatorLauncher getLocatorLauncher() { + return this.locatorLauncher; + } + + @Nullable @Override + public Locator getObject() throws Exception { + + Locator locator = getLocator(); + + Assert.state(locator != null, "Locator was not configured and initialized"); + + return locator; + } + + @Nullable @Override + public Class getObjectType() { + + Locator locator = getLocator(); + + return locator != null ? locator.getClass() : Locator.class; + } + + public void setBindAddress(String bindAddress) { + this.bindAddress = bindAddress; + } + + public Optional getBindAddress() { + + return Optional.ofNullable(this.bindAddress) + .filter(StringUtils::hasText); + } + + public LocatorConfigurer getCompositeLocatorConfigurer() { + return this.compositeLocatorConfigurer; + } + + public void setGemFireProperties(Properties gemfireProperties) { + this.gemfireProperties = gemfireProperties; + } + + public Properties getGemFireProperties() { + + if (this.gemfireProperties == null) { + this.gemfireProperties = new Properties(); + } + + return this.gemfireProperties; + } + + public void setHostnameForClients(String hostnameForClients) { + this.hostnameForClients = hostnameForClients; + } + + public Optional getHostnameForClients() { + + return Optional.ofNullable(this.hostnameForClients) + .filter(StringUtils::hasText); + } + + public void setLocatorConfigurers(LocatorConfigurer... locatorConfigurers) { + setLocatorConfigurers(Arrays.asList(nullSafeArray(locatorConfigurers, LocatorConfigurer.class))); + } + + public void setLocatorConfigurers(List locatorConfigurers) { + Optional.ofNullable(locatorConfigurers).ifPresent(this.locatorConfigurers::addAll); + } + + public void setLogLevel(String logLevel) { + this.logLevel = logLevel; + } + + public String getLogLevel() { + return StringUtils.hasText(this.logLevel) ? this.logLevel : DEFAULT_LOG_LEVEL; + } + + public void setName(String name) { + this.name = name; + } + + public Optional getName() { + + return Optional.ofNullable(this.name) + .filter(StringUtils::hasText); + } + + public void setPort(Integer port) { + + Assert.isTrue(port >= 0 && port < 65536, String.format("Network port [%d] is not valid", port)); + + this.port = port; + } + + public Integer getPort() { + return this.port; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableLocator.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableLocator.java index ef34e1df..342f40dc 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableLocator.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableLocator.java @@ -53,22 +53,24 @@ import org.springframework.context.annotation.Import; public @interface EnableLocator { /** - * Configures the host/IP address on which the embedded Locator service will bind to for accepting connections - * from clients sending Locator requests. + * Configures the host/IP address on which the embedded {@link Locator} service will bind to + * for accepting connections from clients sending {@link Locator} requests. * * Defaults to {@literal localhost}. * - * Use the {@literal spring.data.gemfire.locator.host} property in {@literal application.properties}. + * Use the {@literal spring.data.gemfire.locator.host} property + * in Spring Boot {@literal application.properties}. */ String host() default LocatorConfiguration.DEFAULT_HOST; /** - * Configures the port on which the embedded Locator service will bind to listening for client connections - * sending Locator requests. + * Configures the port on which the embedded {@link Locator} service will bind to + * listening for client connections sending {@link Locator} requests. * * Defaults to {@literal 10334}. * - * Use the {@literal spring.data.gemfire.locator.port} property in {@literal application.properties}. + * Use the {@literal spring.data.gemfire.locator.port} property + * in Spring Boot {@literal application.properties}. */ int port() default LocatorConfiguration.DEFAULT_LOCATOR_PORT; diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/LocatorApplication.java b/src/main/java/org/springframework/data/gemfire/config/annotation/LocatorApplication.java new file mode 100644 index 00000000..496a14d3 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/LocatorApplication.java @@ -0,0 +1,104 @@ +/* + * Copyright 2018 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 + * + * http://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.config.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apache.geode.distributed.Locator; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * The {@link LocatorApplication} {@link Annotation} enables a Spring Data for Apache Geode & Pivotal GemFire + * application to become a {@link Locator} based application. + * + * @author John Blum + * @see java.lang.annotation.Documented + * @see java.lang.annotation.Inherited + * @see java.lang.annotation.Retention + * @see java.lang.annotation.Target + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.context.annotation.Import + * @see org.springframework.data.gemfire.config.annotation.LocatorApplicationConfiguration + * @since 2.2.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Configuration +@Import(LocatorApplicationConfiguration.class) +@SuppressWarnings("unused") +public @interface LocatorApplication { + + /** + * Configures the hostname or IP address on which the {@link Locator} will bind to for accepting connections + * from clients sending {@link Locator} requests. + * + * Defaults to {@literal localhost}. + * + * Use the {@literal spring.data.gemfire.locator.bind-address} property + * in Spring Boot {@literal application.properties}. + */ + String bindAddress() default ""; + + /** + * Configures the {@link String hostname} used by clients connecting to this {@link Locator}. + * + * Defaults to {@literal localhost}. + * + * Use {@literal spring.data.gemfire.locator.hostname-for-clients} + * in Spring Boot {@literal application.properties}. + */ + String hostnameForClients() default ""; + + /** + * Configures the log level used to output log messages at Apache Geode / Pivotal GemFire {@link Locator} runtime. + * + * Defaults to {@literal config}. + * + * Use {@literal spring.data.gemfire.locator.log-level} property in {@literal application.properties}. + */ + String logLevel() default LocatorApplicationConfiguration.DEFAULT_LOG_LEVEL; + + /** + * Configures the name of the {@link Locator} application. + * + * Defaults to {@literal SpringBasedLocatorApplication}. + * + * Use the {@literal spring.data.gemfire.locator.name} property + * in Spring Boot {@literal application.properties}. + */ + String name() default LocatorApplicationConfiguration.DEFAULT_NAME; + + /** + * Configures the port on which the embedded {@link Locator} service will bind to + * listening for client connections sending {@link Locator} requests. + * + * Defaults to {@literal 10334}. + * + * Use the {@literal spring.data.gemfire.locator.port} property + * in Spring Boot {@literal application.properties}. + */ + int port() default LocatorApplicationConfiguration.DEFAULT_PORT; + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationConfiguration.java new file mode 100644 index 00000000..7f264a43 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationConfiguration.java @@ -0,0 +1,239 @@ +/* + * Copyright 2018 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 + * + * http://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.config.annotation; + +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.distributed.Locator; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportAware; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +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.util.ClassUtils; + +/** + * Spring {@link Configuration @Configuration} class used to configure and bootstrap an Apache Geode + * or Pivotal GemFire {@link Locator}. + * + * @author John Blum + * @see java.lang.annotation.Annotation + * @see org.apache.geode.distributed.Locator + * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor + * @see org.springframework.context.annotation.Bean + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.context.annotation.ImportAware + * @see org.springframework.core.annotation.AnnotationAttributes + * @see org.springframework.core.type.AnnotationMetadata + * @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.LocatorConfigurer + * @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport + * @since 2.2.0 + */ +@Configuration +@SuppressWarnings("unused") +public class LocatorApplicationConfiguration extends AbstractAnnotationConfigSupport implements ImportAware { + + public static final int DEFAULT_PORT = 10334; + + public static final String DEFAULT_LOG_LEVEL = "config"; + public static final String DEFAULT_NAME = "SpringBasedLocatorApplication"; + + protected static final String EXCLUSIVE_LOCATOR_APPLICATION_ERROR_MESSAGE = + "A Spring application cannot be both a Cache and a Locator application;" + + " You may annotate your Spring application main class with either 1 of" + + " [@ClientCacheApplication, @CacheServerApplication, @PeerCacheApplication] or @LocatorApplication;" + + " If you want to create a Spring, Apache Geode/Pivotal GemFire server application " + + " (i.e. [@PeerCacheApplication, @CacheServerApplication] and also run an embedded Locator service," + + " then use @EnableLocator with 1 of the server-side, cache application annotations instead;" + + " Locators are not applicable to clients."; + + private static final List CACHE_FACTORY_BEAN_CLASS_NAMES = + Arrays.asList(CacheFactoryBean.class.getName(), ClientCacheFactoryBean.class.getName()); + + private int port = DEFAULT_PORT; + + @Autowired(required = false) + private List locatorConfigurers = Collections.emptyList(); + + private String bindAddress; + private String hostnameForClients; + private String logLevel; + private String name; + + @Override + protected Class getAnnotationType() { + return LocatorApplication.class; + } + + @Bean + BeanFactoryPostProcessor exclusiveLocatorApplicationBeanFactoryPostProcessor() { + + return configurableListableBeanFactory -> { + + String[] beanDefinitionNames = configurableListableBeanFactory.getBeanDefinitionNames(); + + boolean match = Arrays.stream(nullSafeArray(beanDefinitionNames, String.class)) + .map(configurableListableBeanFactory::getBeanDefinition) + .anyMatch(beanDefinition -> + + resolveBeanClassName(beanDefinition) + .map(beanClassName -> { + try { + + Class possibleCacheType = + ClassUtils.resolveClassName(beanClassName, getBeanClassLoader()); + + 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); + } + }; + } + + private boolean isCacheType(Class type) { + + return type != null + && (CacheFactoryBean.class.isAssignableFrom(type) || GemFireCache.class.isAssignableFrom(type)); + } + + @Override + public void setImportMetadata(AnnotationMetadata importMetadata) { + + if (isAnnotationPresent(importMetadata)) { + + AnnotationAttributes locatorApplicationAnnotationAttributes = getAnnotationAttributes(importMetadata); + + setBindAddress(resolveProperty(locatorProperty("bind-address"), + locatorApplicationAnnotationAttributes.getString("bindAddress"))); + + setHostnameForClients(resolveProperty(locatorProperty("hostname-for-clients"), + locatorApplicationAnnotationAttributes.getString("hostnameForClients"))); + + setLogLevel(resolveProperty(locatorProperty("log-level"), + locatorApplicationAnnotationAttributes.getString("logLevel"))); + + setName(resolveProperty(locatorProperty("name"), + locatorApplicationAnnotationAttributes.getString("name"))); + + setPort(resolveProperty(locatorProperty("port"), + locatorApplicationAnnotationAttributes.getNumber("port"))); + } + } + + @Bean + public LocatorFactoryBean locatorApplication() { + + LocatorFactoryBean locatorFactoryBean = new LocatorFactoryBean(); + + locatorFactoryBean.setBindAddress(getBindAddress()); + locatorFactoryBean.setHostnameForClients(getHostnameForClients()); + locatorFactoryBean.setLocatorConfigurers(resolveLocatorConfigurers()); + locatorFactoryBean.setLogLevel(getLogLevel()); + locatorFactoryBean.setName(getName()); + locatorFactoryBean.setPort(getPort()); + + return locatorFactoryBean; + } + + private List resolveLocatorConfigurers() { + + return Optional.ofNullable(this.locatorConfigurers) + .filter(locatorConfigurers -> !locatorConfigurers.isEmpty()) + .orElseGet(() -> + + Optional.of(getBeanFactory()) + .filter(beanFactory -> beanFactory instanceof ListableBeanFactory) + .map(beanFactory -> { + + Map beansOfType = ((ListableBeanFactory) beanFactory) + .getBeansOfType(LocatorConfigurer.class, true, false); + + return nullSafeMap(beansOfType).values().stream() + .collect(Collectors.toList()); + + }) + .orElseGet(Collections::emptyList) + ); + } + + public void setBindAddress(String bindAddress) { + this.bindAddress = bindAddress; + } + + public String getBindAddress() { + return this.bindAddress; + } + + public void setHostnameForClients(String hostnameForClients) { + this.hostnameForClients = hostnameForClients; + } + + public String getHostnameForClients() { + return this.hostnameForClients; + } + + public void setLogLevel(String logLevel) { + this.logLevel = logLevel; + } + + public String getLogLevel() { + return this.logLevel; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + public void setPort(int port) { + this.port = port; + } + + public int getPort() { + return this.port; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/LocatorConfigurer.java b/src/main/java/org/springframework/data/gemfire/config/annotation/LocatorConfigurer.java new file mode 100644 index 00000000..cdd46a28 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/LocatorConfigurer.java @@ -0,0 +1,50 @@ +/* + * Copyright 2018 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 + * + * http://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.config.annotation; + +import org.apache.geode.distributed.Locator; +import org.springframework.data.gemfire.LocatorFactoryBean; + +/** + * A Spring Configurer used to apply additional, customized configuration for an Apache Geode or Pivotal GemFire + * {@link Locator}. + * + * This Configurer is particularly useful when using {@link LocatorApplication} annotation to configure and bootstrap + * an Apache Geode or Pivotal GemFire {@link Locator}. + * + * This Configurer is NOT applied when configuring an enabling an embedded {@link Locator} + * using SDG's {@link @EnableLocator} annotation. + * + * @author John Blum + * @see java.lang.FunctionalInterface + * @see org.apache.geode.distributed.Locator + * @see org.springframework.data.gemfire.LocatorFactoryBean + * @since 2.2.0 + */ +@FunctionalInterface +public interface LocatorConfigurer { + + /** + * Customizes the configuration of the {@link LocatorFactoryBean}. + * + * @param beanName {@link String name} of the bean in the Spring context. + * @param bean {@link LocatorFactoryBean} used to configure and bootstrap an Apache Geode or Pivotal GemFire + * {@link Locator} using Spring. + * @see org.springframework.data.gemfire.LocatorFactoryBean + */ + void configure(String beanName, LocatorFactoryBean bean); + +} diff --git a/src/test/java/org/springframework/data/gemfire/LocatorFactoryBeanUnitTests.java b/src/test/java/org/springframework/data/gemfire/LocatorFactoryBeanUnitTests.java new file mode 100644 index 00000000..a1cd6b7c --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/LocatorFactoryBeanUnitTests.java @@ -0,0 +1,389 @@ +/* + * Copyright 2018 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 + * + * http://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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +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.when; + +import java.net.InetAddress; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import org.apache.geode.distributed.Locator; +import org.apache.geode.distributed.LocatorLauncher; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.springframework.data.gemfire.config.annotation.LocatorConfigurer; + +/** + * Unit Tests for {@link LocatorFactoryBean}. + * + * @author John Blum + * @see java.util.Properties + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.mockito.Spy + * @see org.apache.geode.distributed.Locator + * @see org.apache.geode.distributed.LocatorLauncher + * @see org.springframework.data.gemfire.LocatorFactoryBean + * @see org.springframework.data.gemfire.config.annotation.LocatorConfigurer + * @since 2.2.0 + */ +public class LocatorFactoryBeanUnitTests { + + public LocatorFactoryBean locatorFactoryBean; + + @Before + public void setup() { + + this.locatorFactoryBean = spy(new LocatorFactoryBean()); + } + + @Test + public void afterPropertiesSetCallsApplyLocatorConfigurersAndInit() throws Exception { + + doNothing().when(this.locatorFactoryBean).applyLocatorConfigurers(any(LocatorConfigurer.class)); + doNothing().when(this.locatorFactoryBean).init(); + + this.locatorFactoryBean.afterPropertiesSet(); + + InOrder inOrder = Mockito.inOrder(this.locatorFactoryBean); + + inOrder.verify(this.locatorFactoryBean, times(1)).getCompositeLocatorConfigurer(); + inOrder.verify(this.locatorFactoryBean, times(1)).applyLocatorConfigurers(any(LocatorConfigurer.class)); + inOrder.verify(this.locatorFactoryBean, times(1)).init(); + } + + @Test + @SuppressWarnings("unchecked") + public void appliesArrayOfLocatorConfigurersCallsIterableOfLocatorConfigurers() { + + LocatorConfigurer mockLocatorConfigurerOne = mock(LocatorConfigurer.class); + LocatorConfigurer mockLocatorConfigurerTwo = mock(LocatorConfigurer.class); + + doCallRealMethod().when(this.locatorFactoryBean).applyLocatorConfigurers(any(LocatorConfigurer.class)); + + doAnswer(invocation -> { + + Iterable locatorConfigurers = invocation.getArgument(0, Iterable.class); + + assertThat(locatorConfigurers).isNotNull(); + assertThat(locatorConfigurers).containsExactly(mockLocatorConfigurerOne, mockLocatorConfigurerTwo); + + return null; + + }).when(this.locatorFactoryBean).applyLocatorConfigurers(any(Iterable.class)); + + this.locatorFactoryBean.applyLocatorConfigurers(mockLocatorConfigurerOne, mockLocatorConfigurerTwo); + + verify(this.locatorFactoryBean, times(1)).applyLocatorConfigurers(any(Iterable.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void appliesIterableOfLocatorConfigurersInvokesLocatorConfigurers() { + + LocatorConfigurer mockLocatorConfigurerOne = mock(LocatorConfigurer.class); + LocatorConfigurer mockLocatorConfigurerTwo = mock(LocatorConfigurer.class); + + doCallRealMethod().when(this.locatorFactoryBean).applyLocatorConfigurers(any(Iterable.class)); + + this.locatorFactoryBean.setBeanName("TestLocator"); + this.locatorFactoryBean + .applyLocatorConfigurers(Arrays.asList(mockLocatorConfigurerOne, mockLocatorConfigurerTwo)); + + verify(mockLocatorConfigurerOne, times(1)) + .configure(eq("TestLocator"), eq(this.locatorFactoryBean)); + + verify(mockLocatorConfigurerTwo, times(1)) + .configure(eq("TestLocator"), eq(this.locatorFactoryBean)); + } + + @Test + public void initBuildsLocator() throws Exception { + + Locator mockLocator = mock(Locator.class); + + LocatorLauncher mockLocatorLauncher = mock(LocatorLauncher.class); + + LocatorLauncher.Builder locatorBuilderSpy = spy(new LocatorLauncher.Builder()); + + when(mockLocatorLauncher.getLocator()).thenReturn(mockLocator); + when(locatorBuilderSpy.build()).thenReturn(mockLocatorLauncher); + when(this.locatorFactoryBean.newLocatorLauncherBuilder()).thenReturn(locatorBuilderSpy); + + String testBindAddress = InetAddress.getLocalHost().getHostAddress(); + + this.locatorFactoryBean.setBeanName("TestLocatorBean"); + this.locatorFactoryBean.setBindAddress(testBindAddress); + this.locatorFactoryBean.setHostnameForClients("skullbox"); + this.locatorFactoryBean.setName("TestMember"); + this.locatorFactoryBean.setPort(54321); + this.locatorFactoryBean.init(); + + assertThat(this.locatorFactoryBean.getLocator()).isEqualTo(mockLocator); + assertThat(this.locatorFactoryBean.getLocatorLauncher()).isEqualTo(mockLocatorLauncher); + + verify(locatorBuilderSpy, times(1)).set(eq("log-level"), eq("config")); + verify(locatorBuilderSpy, times(1)).setBindAddress(eq(testBindAddress)); + verify(locatorBuilderSpy, times(1)).setHostnameForClients(eq("skullbox")); + verify(locatorBuilderSpy, times(1)).setMemberName(eq("TestMember")); + verify(locatorBuilderSpy, times(1)).setPort(eq(54321)); + verify(this.locatorFactoryBean, times(1)).postProcess(eq(locatorBuilderSpy)); + verify(this.locatorFactoryBean, times(1)).postProcess(eq(mockLocatorLauncher)); + verify(mockLocatorLauncher, times(1)).start(); + } + + @Test + public void configuresLocatorLauncherBuilderGemFireProperties() { + + Properties gemfireProperties = new Properties(); + + gemfireProperties.setProperty("name", "TEST"); + gemfireProperties.setProperty("log-level", "config"); + gemfireProperties.setProperty("locators", "localhost[11235],skullbox[12480]"); + + LocatorLauncher.Builder locatorBuilderSpy = spy(new LocatorLauncher.Builder()); + + this.locatorFactoryBean.setGemFireProperties(gemfireProperties); + this.locatorFactoryBean.configureGemfireProperties(locatorBuilderSpy); + + gemfireProperties.stringPropertyNames().forEach(propertyName -> + verify(locatorBuilderSpy, times(1)) + .set(eq(propertyName), eq(gemfireProperties.getProperty(propertyName)))); + } + + @Test + public void getObjectReturnsLocator() throws Exception { + + Locator mockLocator = mock(Locator.class); + + doReturn(mockLocator).when(this.locatorFactoryBean).getLocator(); + + assertThat(this.locatorFactoryBean.getObject()).isEqualTo(mockLocator); + + verify(this.locatorFactoryBean, times(1)).getLocator(); + } + + @Test(expected = IllegalStateException.class) + public void getObjectThrowsIllegalStateException() throws Exception { + + try { + this.locatorFactoryBean.getObject(); + } + catch (IllegalStateException expected) { + + assertThat(expected).hasMessage("Locator was not configured and initialized"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test + public void getObjectTypeEqualsLocatorClass() { + + assertThat(this.locatorFactoryBean.getObjectType()).isEqualTo(Locator.class); + + verify(this.locatorFactoryBean, times(1)).getLocator(); + } + + @Test + public void getObjectTypeIsALocatorType() { + + Locator mockLocator = mock(Locator.class); + + doReturn(mockLocator).when(this.locatorFactoryBean).getLocator(); + + Class objectType = this.locatorFactoryBean.getObjectType(); + + assertThat(Locator.class).isAssignableFrom(objectType); + assertThat(objectType).isEqualTo(mockLocator.getClass()); + + verify(this.locatorFactoryBean, times(1)).getLocator(); + } + + @Test + public void setAndGetBindAddress() { + + this.locatorFactoryBean.setBindAddress("10.105.210.16"); + + assertThat(this.locatorFactoryBean.getBindAddress().orElse(null)).isEqualTo("10.105.210.16"); + + this.locatorFactoryBean.setBindAddress(null); + + assertThat(this.locatorFactoryBean.getBindAddress().orElse(null)).isNull(); + + this.locatorFactoryBean.setBindAddress(" "); + + assertThat(this.locatorFactoryBean.getBindAddress().orElse(null)).isNull(); + } + + @Test + public void setAndGetGemFireProperties() { + + Properties testGemFireProperties = new Properties(); + + testGemFireProperties.setProperty("testKey", "testValue"); + + this.locatorFactoryBean.setGemFireProperties(testGemFireProperties); + + assertThat(this.locatorFactoryBean.getGemFireProperties()).isEqualTo(testGemFireProperties); + + this.locatorFactoryBean.setGemFireProperties(null); + + Properties actualGemFireProperties = this.locatorFactoryBean.getGemFireProperties(); + + assertThat(actualGemFireProperties).isNotNull(); + assertThat(actualGemFireProperties).isNotEqualTo(testGemFireProperties); + assertThat(actualGemFireProperties).isEmpty(); + } + + @Test + public void setAndGetHostnameForClients() { + + this.locatorFactoryBean.setHostnameForClients("skullbox"); + + assertThat(this.locatorFactoryBean.getHostnameForClients().orElse(null)).isEqualTo("skullbox"); + + this.locatorFactoryBean.setHostnameForClients(null); + + assertThat(this.locatorFactoryBean.getHostnameForClients().orElse(null)).isNull(); + + this.locatorFactoryBean.setHostnameForClients(" "); + + assertThat(this.locatorFactoryBean.getHostnameForClients().orElse(null)).isNull(); + } + + @Test + public void setArrayOfLocatorConfigurersCallsListOfLocatorConfigurers() { + + LocatorConfigurer mockLocatorConfigurerOne = mock(LocatorConfigurer.class); + LocatorConfigurer mockLocatorConfigurerTwo = mock(LocatorConfigurer.class); + + this.locatorFactoryBean.setLocatorConfigurers(mockLocatorConfigurerOne, mockLocatorConfigurerTwo); + + List locatorconfigurers = Arrays.asList(mockLocatorConfigurerOne, mockLocatorConfigurerTwo); + + verify(this.locatorFactoryBean, times(1)).setLocatorConfigurers(eq(locatorconfigurers)); + } + + @Test + public void setListOfLocatorConfigurersAddsAll() { + + LocatorConfigurer mockLocatorConfigurerOne = mock(LocatorConfigurer.class); + LocatorConfigurer mockLocatorConfigurerTwo = mock(LocatorConfigurer.class); + + List locatorconfigurers = Arrays.asList(mockLocatorConfigurerOne, mockLocatorConfigurerTwo); + + this.locatorFactoryBean.setLocatorConfigurers(locatorconfigurers); + + LocatorConfigurer compositeLocatorConfigurer = this.locatorFactoryBean.getCompositeLocatorConfigurer(); + + assertThat(compositeLocatorConfigurer).isNotNull(); + + compositeLocatorConfigurer.configure("TestLocator", this.locatorFactoryBean); + + verify(mockLocatorConfigurerOne, times(1)) + .configure(eq("TestLocator"), eq(this.locatorFactoryBean)); + + verify(mockLocatorConfigurerTwo, times(1)) + .configure(eq("TestLocator"), eq(this.locatorFactoryBean)); + } + + @Test + public void setAndGetLogLevel() { + + this.locatorFactoryBean.setLogLevel("info"); + + assertThat(this.locatorFactoryBean.getLogLevel()).isEqualTo("info"); + + this.locatorFactoryBean.setLogLevel(null); + + assertThat(this.locatorFactoryBean.getLogLevel()).isEqualTo(LocatorFactoryBean.DEFAULT_LOG_LEVEL); + + this.locatorFactoryBean.setLogLevel(" "); + + assertThat(this.locatorFactoryBean.getLogLevel()).isEqualTo(LocatorFactoryBean.DEFAULT_LOG_LEVEL); + } + + @Test + public void setAndGetName() { + + this.locatorFactoryBean.setName("TestLocator"); + + assertThat(this.locatorFactoryBean.getName().orElse(null)).isEqualTo("TestLocator"); + + this.locatorFactoryBean.setName(null); + + assertThat(this.locatorFactoryBean.getName().orElse(null)).isNull(); + + this.locatorFactoryBean.setName(" "); + + assertThat(this.locatorFactoryBean.getName().orElse(null)).isNull(); + } + + @Test + public void setPortToValidValue() { + + this.locatorFactoryBean.setPort(54321); + + assertThat(this.locatorFactoryBean.getPort()).isEqualTo(54321); + } + + @Test(expected = IllegalArgumentException.class) + public void setPortToOverflowValue() { + + try { + this.locatorFactoryBean.setPort(65536); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("Network port [65536] is not valid"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test(expected = IllegalArgumentException.class) + public void setPortToUnderflowValue() { + + try { + this.locatorFactoryBean.setPort(-1); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("Network port [-1] is not valid"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationCannotCoexistWithCacheApplicationIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationCannotCoexistWithCacheApplicationIntegrationTests.java new file mode 100644 index 00000000..38a4582c --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationCannotCoexistWithCacheApplicationIntegrationTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2018 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 + * + * http://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.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; +import static org.mockito.Mockito.mock; + +import java.util.Optional; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.distributed.Locator; +import org.junit.After; +import org.junit.Test; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects; + +/** + * Integration Tests for {@link LocatorApplication} and {@link LocatorApplicationConfiguration} asserting that + * {@link GemFireCache} and {@link Locator} instances are mutually exclusive. + * + * @author John Blum + * @see org.junit.Test + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.client.ClientCache + * @see org.apache.geode.distributed.Locator + * @see org.springframework.data.gemfire.config.annotation.LocatorApplication + * @see org.springframework.data.gemfire.config.annotation.LocatorApplicationConfiguration + * @see org.springframework.context.ConfigurableApplicationContext + * @see org.springframework.context.annotation.AnnotationConfigApplicationContext + * @see org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects + * @since 2.2.0 + */ +@SuppressWarnings("unused") +public class LocatorApplicationCannotCoexistWithCacheApplicationIntegrationTests { + + private static final String GEMFIRE_LOG_LEVEL = "error"; + + private ConfigurableApplicationContext applicationContext; + + @After + public void tearDown() { + + Optional.ofNullable(this.applicationContext) + .ifPresent(ConfigurableApplicationContext::close); + } + + private ConfigurableApplicationContext newApplicationContext(Class... annotatedClasses) { + + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + + applicationContext.register(annotatedClasses); + applicationContext.registerShutdownHook(); + applicationContext.refresh(); + + return applicationContext; + } + + private void testCacheAndLocatorApplication(Class testConfiguration) { + + try { + + this.applicationContext = newApplicationContext(testConfiguration); + this.applicationContext.getBean(GemFireCache.class); + this.applicationContext.getBean(Locator.class); + + fail("Caches and Locators cannot coexist!"); + } + catch (BeanDefinitionStoreException expected) { + + assertThat(expected) + .hasMessage(LocatorApplicationConfiguration.EXCLUSIVE_LOCATOR_APPLICATION_ERROR_MESSAGE); + + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test(expected = BeanDefinitionStoreException.class) + public void clientCacheAndLocatorApplicationThrowsException() { + testCacheAndLocatorApplication(ClientCacheAndLocatorTestConfiguration.class); + } + + @Test(expected = BeanDefinitionStoreException.class) + public void locatorAndPeerCacheApplicationThrowsException() { + testCacheAndLocatorApplication(LocatorAndPeerCacheTestConfiguration.class); + } + + @EnableGemFireMockObjects + @LocatorApplication(logLevel = GEMFIRE_LOG_LEVEL, port = 0) + static class ClientCacheAndLocatorTestConfiguration { + + @Bean + ClientCache mockClientCache() { + return mock(ClientCache.class); + } + } + + @EnableGemFireMockObjects + @LocatorApplication(logLevel = GEMFIRE_LOG_LEVEL, port = 0) + @PeerCacheApplication(logLevel = GEMFIRE_LOG_LEVEL) + static class LocatorAndPeerCacheTestConfiguration { } + +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationConfigurerIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationConfigurerIntegrationTests.java new file mode 100644 index 00000000..a509f8b1 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationConfigurerIntegrationTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2018 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 + * + * http://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.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 java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.LocatorFactoryBean; +import org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects; +import org.springframework.lang.Nullable; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration Tests for {@link LocatorApplication} and {@link LocatorConfigurer}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.config.annotation.LocatorApplication + * @see org.springframework.data.gemfire.config.annotation.LocatorApplicationConfiguration + * @see org.springframework.data.gemfire.config.annotation.LocatorConfigurer + * @see org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @since 2.2.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@SuppressWarnings("unused") +public class LocatorApplicationConfigurerIntegrationTests { + + private static final String GEMFIRE_LOG_LEVEL = "error"; + + @Autowired + private LocatorFactoryBean locatorFactoryBean; + + @Autowired + @Qualifier("locatorConfigurerOne") + private LocatorConfigurer locatorConfigurerOne; + + @Autowired + @Qualifier("locatorConfigurerTwo") + private LocatorConfigurer locatorConfigurerTwo; + + @Test + public void locatorConfigurersInvoked() { + + assertThat(this.locatorFactoryBean).isNotNull(); + assertThat(this.locatorConfigurerOne).isNotNull(); + assertThat(this.locatorConfigurerTwo).isNotNull(); + + Arrays.asList(this.locatorConfigurerOne, this.locatorConfigurerTwo).forEach(locatorConfigurer -> + verify(locatorConfigurer, times(1)) + .configure(eq("locatorApplication"), eq(this.locatorFactoryBean))); + } + + @EnableGemFireMockObjects + @LocatorApplication(logLevel = GEMFIRE_LOG_LEVEL, port = 0) + static class TestConfiguration { + + @Bean + BeanPostProcessor locatorFactoryBeanPostProcessor() { + + return new BeanPostProcessor() { + + @Nullable @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + + if (bean instanceof LocatorFactoryBean) { + + LocatorFactoryBean locatorFactoryBean = spy((LocatorFactoryBean) bean); + + doNothing().when(locatorFactoryBean).init(); + + bean = locatorFactoryBean; + + } + + return bean; + } + }; + } + + @Bean + LocatorConfigurer locatorConfigurerOne() { + return mock(LocatorConfigurer.class); + } + + @Bean + LocatorConfigurer locatorConfigurerTwo() { + return mock(LocatorConfigurer.class); + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationIntegrationTests.java new file mode 100644 index 00000000..6ab84705 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationIntegrationTests.java @@ -0,0 +1,106 @@ +/* + * Copyright 2018 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 + * + * http://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.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Properties; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.CacheFactory; +import org.apache.geode.distributed.DistributedSystem; +import org.apache.geode.distributed.Locator; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.gemfire.GemfireUtils; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration Tests for {@link LocatorApplication} and {@link LocatorApplicationConfiguration} asserting that + * an Apache Geode / Pivotal GemFire {@link Cache} application can connect to the {@link Locator} configured + * and bootstrapped by {@link LocatorApplication}. + * + * @author John Blum + * @see java.util.Properties + * @see org.junit.Test + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.CacheFactory + * @see org.apache.geode.distributed.DistributedSystem + * @see org.apache.geode.distributed.Locator + * @see org.springframework.data.gemfire.config.annotation.LocatorApplication + * @see org.springframework.data.gemfire.config.annotation.LocatorApplicationConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @since 2.2.0 + */ +@RunWith(SpringRunner.class) +@SuppressWarnings("unused") +public class LocatorApplicationIntegrationTests { + + private static final String GEMFIRE_LOG_LEVEL = "error"; + + @Autowired + private Locator locator; + + @Before + public void setup() { + assertThat(this.locator).isNotNull(); + } + + @Test + public void gemfireCacheCanConnectToLocator() { + + DistributedSystem distributedSystem = this.locator.getDistributedSystem(); + + assertThat(distributedSystem).isNotNull(); + + Properties distributedSystemProperties = distributedSystem.getProperties(); + + assertThat(distributedSystemProperties).isNotNull(); + + Cache peerCache = null; + + try { + peerCache = new CacheFactory() + .set("name", LocatorApplicationIntegrationTests.class.getSimpleName()) + .set("bind-address", distributedSystemProperties.getProperty("bind-address")) + .set("cache-xml-file", distributedSystemProperties.getProperty("cache-xml-file")) + .set("jmx-manager", distributedSystemProperties.getProperty("jmx-manager")) + .set("locators", distributedSystemProperties.getProperty("locators")) + .set("log-file", distributedSystemProperties.getProperty("log-file")) + .create(); + + assertThat(peerCache).isNotNull(); + assertThat(peerCache.getDistributedSystem()).isNotNull(); + assertThat(peerCache.getDistributedSystem().getProperties()).isNotNull(); + assertThat(peerCache.getDistributedSystem().getProperties().getProperty("locators")) + .isEqualTo(distributedSystemProperties.getProperty("locators")); + } + finally { + GemfireUtils.close(peerCache); + } + } + + @LocatorApplication( + name = "LocatorApplicationIntegrationTests", + bindAddress = "localhost", + logLevel = GEMFIRE_LOG_LEVEL, + port = 0 + ) + static class TestConfiguration { } + +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationPropertiesIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationPropertiesIntegrationTests.java new file mode 100644 index 00000000..a1874b34 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/LocatorApplicationPropertiesIntegrationTests.java @@ -0,0 +1,139 @@ +/* + * Copyright 2018 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 + * + * http://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.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; + +import java.util.Optional; +import java.util.Properties; + +import org.apache.geode.distributed.Locator; +import org.junit.After; +import org.junit.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.data.gemfire.LocatorFactoryBean; +import org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects; +import org.springframework.lang.Nullable; +import org.springframework.mock.env.MockPropertySource; + +/** + * Integration Tests for {@link LocatorApplication} and {@link LocatorApplicationConfiguration} asserting that + * the {@link Locator} is configured with {@link Properties}. + * + * @author John Blum + * @see java.util.Properties + * @see org.junit.Test + * @see org.apache.geode.distributed.Locator + * @see org.springframework.context.ConfigurableApplicationContext + * @see org.springframework.context.annotation.AnnotationConfigApplicationContext + * @see org.springframework.core.env.PropertySource + * @see org.springframework.data.gemfire.LocatorFactoryBean + * @see org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects + * @see org.springframework.mock.env.MockPropertySource + * @since 2.2.0 + */ +@SuppressWarnings("unused") +public class LocatorApplicationPropertiesIntegrationTests { + + private ConfigurableApplicationContext applicationContext; + + @After + public void tearDown() { + + Optional.ofNullable(this.applicationContext) + .ifPresent(ConfigurableApplicationContext::close); + } + + private ConfigurableApplicationContext newApplicationContext(PropertySource testPropertySource, + Class... annotatedClasses) { + + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + + MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources(); + + propertySources.addFirst(testPropertySource); + + applicationContext.register(annotatedClasses); + applicationContext.registerShutdownHook(); + applicationContext.refresh(); + + return applicationContext; + } + + @Test + public void locatorIsConfiguredWithProperties() { + + MockPropertySource testPropertySource = new MockPropertySource() + .withProperty("spring.data.gemfire.locator.bind-address", "10.120.240.32") + .withProperty("spring.data.gemfire.locator.hostname-for-clients", "skullbox") + .withProperty("spring.data.gemfire.locator.log-level", "error") + .withProperty("spring.data.gemfire.locator.name", "MockLocator") + .withProperty("spring.data.gemfire.locator.port", 54321); + + this.applicationContext = newApplicationContext(testPropertySource, TestConfiguration.class); + + LocatorFactoryBean locatorFactoryBean = this.applicationContext.getBean(LocatorFactoryBean.class); + + assertThat(locatorFactoryBean).isNotNull(); + assertThat(locatorFactoryBean.getBindAddress().orElse(null)).isEqualTo("10.120.240.32"); + assertThat(locatorFactoryBean.getHostnameForClients().orElse(null)).isEqualTo("skullbox"); + assertThat(locatorFactoryBean.getLogLevel()).isEqualTo("error"); + assertThat(locatorFactoryBean.getName().orElse(null)).isEqualTo("MockLocator"); + assertThat(locatorFactoryBean.getPort()).isEqualTo(54321); + } + + @EnableGemFireMockObjects + @LocatorApplication( + bindAddress = "10.105.210.16", + hostnameForClients = "mailbox", + logLevel = "info", + name = "TestLocator", + port = 12345 + ) + static class TestConfiguration { + + @Bean + BeanPostProcessor locatorFactoryBeanPostProcessor() { + + return new BeanPostProcessor() { + + @Nullable @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + + if (bean instanceof LocatorFactoryBean) { + + LocatorFactoryBean locatorFactoryBean = spy((LocatorFactoryBean) bean); + + doNothing().when(locatorFactoryBean).init(); + + bean = locatorFactoryBean; + + } + + return bean; + } + }; + } + } +}