From d98c8d13347f340858667c5ebdee597d1700bc53 Mon Sep 17 00:00:00 2001 From: John Blum Date: Thu, 21 Jun 2018 17:51:42 -0700 Subject: [PATCH] Add annotation configuration support to configure Apache Geode/Pivotal GemFire Locators ('locators') used in P2P topologies and remote Locators ('remote-locators') used in WAN topologies. --- .../annotation/LocatorsConfiguration.java | 154 ++++++++++++++++++ .../geode/config/annotation/UseLocators.java | 105 ++++++++++++ ...LocatorsConfigurationIntegrationTests.java | 95 +++++++++++ 3 files changed, 354 insertions(+) create mode 100644 spring-geode/src/main/java/org/springframework/geode/config/annotation/LocatorsConfiguration.java create mode 100644 spring-geode/src/main/java/org/springframework/geode/config/annotation/UseLocators.java create mode 100644 spring-geode/src/test/java/org/springframework/geode/config/annotation/LocatorsConfigurationIntegrationTests.java diff --git a/spring-geode/src/main/java/org/springframework/geode/config/annotation/LocatorsConfiguration.java b/spring-geode/src/main/java/org/springframework/geode/config/annotation/LocatorsConfiguration.java new file mode 100644 index 00000000..f1196513 --- /dev/null +++ b/spring-geode/src/main/java/org/springframework/geode/config/annotation/LocatorsConfiguration.java @@ -0,0 +1,154 @@ +/* + * 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.geode.config.annotation; + +import java.lang.annotation.Annotation; +import java.util.Optional; +import java.util.Properties; + +import org.apache.geode.cache.Cache; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.config.annotation.ClientCacheConfigurer; +import org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer; +import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport; +import org.springframework.util.StringUtils; + +/** + * The {@link LocatorsConfiguration} class is a Spring {@link Configuration} class used to configure Apache Geode + * or Pivotal GemFire's {@literal locators} and/or {@literal remote-locators} properties used by a + * {@link Cache peer Cache member} to join a cluster of servers when using the P2P topology. + * + * The {@literal remote-locators} property is used to configure the Locators that a cluster will use in order to + * connect to a remote site in a multi-site (WAN) topology configuration. To use Locators in a WAN configuration, + * you must specify a unique distributed system ID ({@literal distributed-system-id}) for the local cluster + * and remote Locator(s) for the remote clusters to which you will connect. + * + * @author John Blum + * @see org.apache.geode.cache.Cache + * @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.config.annotation.PeerCacheConfigurer + * @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport + * @see org.springframework.geode.config.annotation.UseLocators + * @since 1.0.0 + */ +@Configuration +@SuppressWarnings("unused") +public class LocatorsConfiguration extends AbstractAnnotationConfigSupport implements ImportAware { + + protected static final String DEFAULT_LOCATORS = "localhost[10334]"; + protected static final String DEFAULT_REMOTE_LOCATORS = ""; + protected static final String LOCATORS_PROPERTY = "locators"; + protected static final String REMOTE_LOCATORS_PROPERTY = "remote-locators"; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private String locators; + private String remoteLocators; + + @Override + protected Class getAnnotationType() { + return UseLocators.class; + } + + @Override + public void setImportMetadata(AnnotationMetadata importMetadata) { + + if (isAnnotationPresent(importMetadata)) { + + AnnotationAttributes useLocatorsAttributes = getAnnotationAttributes(importMetadata); + + setLocators(useLocatorsAttributes.containsKey("locators") + ? useLocatorsAttributes.getString("locators") : null); + + setRemoteLocators(useLocatorsAttributes.containsKey("remoteLocators") + ? useLocatorsAttributes.getString("remoteLocators") : null); + } + } + + protected void setLocators(String locators) { + this.locators = StringUtils.hasText(locators) ? locators : null; + } + + protected Optional getLocators() { + + return Optional.ofNullable(this.locators) + .filter(StringUtils::hasText); + } + + protected Logger getLogger() { + return this.logger; + } + + protected void setRemoteLocators(String remoteLocators) { + this.remoteLocators = StringUtils.hasText(remoteLocators) ? remoteLocators : null; + } + + protected Optional getRemoteLocators() { + + return Optional.ofNullable(this.remoteLocators) + .filter(StringUtils::hasText); + } + + @Bean + ClientCacheConfigurer clientCacheLocatorsConfigurer() { + + return (beanName, clientCacheFactoryBean) -> { + + Logger logger = getLogger(); + + getLocators().ifPresent(locators -> { + if (logger.isWarnEnabled()) { + logger.warn("The '%s' property was configured [%s];" + + " however, this value does not have any effect for ClientCache instances", + LOCATORS_PROPERTY, locators); + } + }); + + getRemoteLocators().ifPresent(remoteLocators -> { + if (logger.isWarnEnabled()) { + logger.warn("The '%s' property was configured [%s];" + + " however, this value does not have any effect for ClientCache instances", + REMOTE_LOCATORS_PROPERTY, remoteLocators); + } + }); + }; + } + + @Bean + PeerCacheConfigurer peerCacheLocatorsConfigurer() { + + return (beanName, cacheFactoryBean) -> { + + Properties gemfireProperties = cacheFactoryBean.getProperties(); + + getLocators().ifPresent(locators -> gemfireProperties.setProperty(LOCATORS_PROPERTY, locators)); + + getRemoteLocators().ifPresent(remoteLocators -> + gemfireProperties.setProperty(REMOTE_LOCATORS_PROPERTY, remoteLocators)); + }; + } +} diff --git a/spring-geode/src/main/java/org/springframework/geode/config/annotation/UseLocators.java b/spring-geode/src/main/java/org/springframework/geode/config/annotation/UseLocators.java new file mode 100644 index 00000000..c913d3d0 --- /dev/null +++ b/spring-geode/src/main/java/org/springframework/geode/config/annotation/UseLocators.java @@ -0,0 +1,105 @@ +/* + * 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.geode.config.annotation; + +import static org.springframework.geode.config.annotation.LocatorsConfiguration.DEFAULT_LOCATORS; +import static org.springframework.geode.config.annotation.LocatorsConfiguration.DEFAULT_REMOTE_LOCATORS; + +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.cache.Cache; +import org.springframework.context.annotation.Import; +import org.springframework.core.annotation.AliasFor; + +/** + * The {@link UseLocators} annotation configures the {@literal locators} and/or {@literal remote-locators} + * Apache Geode/Pivotal GemFire properties used by a {@link Cache peer Cache member} to join a cluster of servers + * when using the P2P topology as well as when configuring the multi-site, WAN topology. + * + * @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.apache.geode.cache.Cache + * @see org.springframework.context.annotation.Import + * @see org.springframework.core.annotation.AliasFor + * @see org.springframework.geode.config.annotation.MemberNameConfiguration + * @since 1.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Import(LocatorsConfiguration.class) +@SuppressWarnings("unused") +public @interface UseLocators { + + /** + * @see #locators() + */ + @AliasFor("locators") + String value() default DEFAULT_LOCATORS; + + /** + * The list of Locators used by system members. The list must be configured consistently for every member of + * the cluster (a.k.a. distributed system). If the list is empty, Locators will not be used. + * + * For each Locator, provide a hostname and/or address (separated by ‘@’, if you use both), followed by + * a port number in brackets. + * + * For example: + * + * + * locators=address1[port1],address2[port2],...,addressN[portN] + * locators=hostname1@address1[port1],hostname2@address2[port2],...,hostnameN@addressN[portN] + * locators=hostname1[port1],hostname2[port2],...,hostnameN[portN] + * + * + * Defaults to {@literal localhost[10334]}. + */ + @AliasFor("value") + String locators() default DEFAULT_LOCATORS; + + /** + * Used to configure the Locators that a cluster will use in order to connect to a remote site in a multi-site + * (WAN) topology configuration. + * + * To use Locators in a WAN configuration, you must specify a unique distributed system ID ({@literal distributed-system-id}) + * for the local cluster and remote Locator(s) for the remote clusters to which you will connect. + * + * For each remote Locator, provide a host name and/or address (separated by ‘@’, if you use both), followed by + * a port number in brackets. + * + * For example: + * + * + * remote-locators=address1[port1],address2[port2],...,addressN[portN] + * remote-locators=hostname1@address1[port1],hostname2@address2[port2],...,hostnameN@addressN[portN] + * remote-locators=hostname1[port1],hostname2[port2],...,hostnameN[portN] + * + * + * Defaults to unset. + */ + String remoteLocators() default DEFAULT_REMOTE_LOCATORS; + +} diff --git a/spring-geode/src/test/java/org/springframework/geode/config/annotation/LocatorsConfigurationIntegrationTests.java b/spring-geode/src/test/java/org/springframework/geode/config/annotation/LocatorsConfigurationIntegrationTests.java new file mode 100644 index 00000000..94033d6e --- /dev/null +++ b/spring-geode/src/test/java/org/springframework/geode/config/annotation/LocatorsConfigurationIntegrationTests.java @@ -0,0 +1,95 @@ +/* + * 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.geode.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.geode.config.annotation.LocatorsConfiguration.LOCATORS_PROPERTY; +import static org.springframework.geode.config.annotation.LocatorsConfiguration.REMOTE_LOCATORS_PROPERTY; + +import java.util.Properties; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.client.ClientCache; +import org.junit.Test; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.gemfire.config.annotation.ClientCacheApplication; +import org.springframework.data.gemfire.config.annotation.PeerCacheApplication; +import org.springframework.data.gemfire.tests.integration.SpringApplicationContextIntegrationTestsSupport; +import org.springframework.data.gemfire.tests.mock.annotation.EnableGemFireMockObjects; + +/** + * Integration tests for {@link UseLocators} and {@link LocatorsConfiguration}. + * + * @author John Blum + * @see org.junit.Test + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication + * @see org.springframework.data.gemfire.config.annotation.PeerCacheApplication + * @see org.springframework.data.gemfire.tests.integration.SpringApplicationContextIntegrationTestsSupport + * @see org.springframework.data.gemfire.tests.mock.annotation.EnableGemFireMockObjects + * @since 1.0.0 + */ +@SuppressWarnings("unused") +public class LocatorsConfigurationIntegrationTests extends SpringApplicationContextIntegrationTestsSupport { + + @Test + public void clientCacheLocatorPropertiesAreNotPresent() { + + ClientCache clientCache = + newApplicationContext(ClientCacheTestConfiguration.class).getBean("gemfireCache", ClientCache.class); + + assertThat(clientCache).isNotNull(); + assertThat(clientCache.getDistributedSystem()).isNotNull(); + + Properties gemfireProperties = clientCache.getDistributedSystem().getProperties(); + + assertThat(gemfireProperties).isNotNull(); + assertThat(gemfireProperties.containsKey(LOCATORS_PROPERTY)).isFalse(); + assertThat(gemfireProperties.containsKey(REMOTE_LOCATORS_PROPERTY)).isFalse(); + } + + @Test + public void peerCacheLocatorPropertiesArePresent() { + + Cache peerCache = + newApplicationContext(PeerCacheTestConfiguration.class).getBean("gemfireCache", Cache.class); + + assertThat(peerCache).isNotNull(); + assertThat(peerCache.getDistributedSystem()).isNotNull(); + + Properties gemfireProperties = peerCache.getDistributedSystem().getProperties(); + + assertThat(gemfireProperties).isNotNull(); + assertThat(gemfireProperties.containsKey(LOCATORS_PROPERTY)).isTrue(); + assertThat(gemfireProperties.getProperty(LOCATORS_PROPERTY)).isEqualTo("mailbox[11235],skullbox[12480]"); + assertThat(gemfireProperties.containsKey(REMOTE_LOCATORS_PROPERTY)).isTrue(); + assertThat(gemfireProperties.getProperty(REMOTE_LOCATORS_PROPERTY)).isEqualTo("remotehost[10334]"); + } + + @Configuration + @ClientCacheApplication + @EnableGemFireMockObjects + @UseLocators(remoteLocators = "remotehost[10334]") + static class ClientCacheTestConfiguration { } + + @Configuration + @PeerCacheApplication + @EnableGemFireMockObjects + @UseLocators(locators = "mailbox[11235],skullbox[12480]", remoteLocators = "remotehost[10334]") + static class PeerCacheTestConfiguration { } + +}