From 721b5a2395322b7c13a1361296361d26118dd6d3 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 26 Aug 2015 17:44:50 +0200 Subject: [PATCH] Hazelcast auto-configuration Provide a general purpose Hazelcast integration (i.e. not tied to caching). Auto-configure a `HazelcastInstance` either based on the presence of a `Config` bean or a configuration file. Said configuration file can be specified explicitly or automatically found from default locations. The cache integration already supports Hazelcast so it has been reworked to automatically reuse an existing `HazelcastInstance` if available. Closes gh-2942 --- .../cache/EhCacheCacheConfiguration.java | 5 +- .../cache/HazelcastCacheConfiguration.java | 135 ++++++++----- .../ResourceCondition.java} | 33 +++- .../hazelcast/HazelcastAutoConfiguration.java | 128 ++++++++++++ .../HazelcastConfigResourceCondition.java | 52 +++++ .../hazelcast/HazelcastProperties.java | 60 ++++++ .../autoconfigure/hazelcast/package-info.java | 20 ++ .../main/resources/META-INF/spring.factories | 1 + .../cache/CacheAutoConfigurationTests.java | 60 +++++- .../ResourceConditionTests.java} | 46 ++--- .../HazelcastAutoConfigurationTests.java | 182 ++++++++++++++++++ .../hazelcast/hazelcast-specific.xml | 7 + .../appendix-application-properties.adoc | 3 + .../main/asciidoc/spring-boot-features.adoc | 47 ++++- 14 files changed, 687 insertions(+), 92 deletions(-) rename spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/{cache/CacheConfigFileCondition.java => condition/ResourceCondition.java} (64%) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConfigResourceCondition.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastProperties.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/package-info.java rename spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/{cache/CacheConfigFileConditionTests.java => condition/ResourceConditionTests.java} (63%) create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationTests.java create mode 100644 spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-specific.xml diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/EhCacheCacheConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/EhCacheCacheConfiguration.java index a3e19e3feaf..ee3642c0849 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/EhCacheCacheConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/EhCacheCacheConfiguration.java @@ -20,6 +20,7 @@ import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ResourceCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cache.ehcache.EhCacheCacheManager; @@ -68,10 +69,10 @@ class EhCacheCacheConfiguration { * default configuration has been found or if property referring to the file to use * has been set. */ - static class ConfigAvailableCondition extends CacheConfigFileCondition { + static class ConfigAvailableCondition extends ResourceCondition { public ConfigAvailableCondition() { - super("EhCache", "spring.cache.ehcache", "classpath:/ehcache.xml"); + super("EhCache", "spring.cache.ehcache", "config", "classpath:/ehcache.xml"); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastCacheConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastCacheConfiguration.java index 81893823bb0..137de632138 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastCacheConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastCacheConfiguration.java @@ -16,85 +16,118 @@ package org.springframework.boot.autoconfigure.cache; +import java.io.Closeable; import java.io.IOException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.cache.CacheManager; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.context.annotation.Conditional; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.Resource; -import org.springframework.core.type.AnnotatedTypeMetadata; - -import com.hazelcast.config.Config; -import com.hazelcast.config.XmlConfigBuilder; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.spring.cache.HazelcastCacheManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastConfigResourceCondition; +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; + /** - * Hazelcast cache configuration. Only kick in if a configuration file location is set or - * if a default configuration file exists (either placed in the default location or set - * via the {@value #CONFIG_SYSTEM_PROPERTY} system property). + * Hazelcast cache configuration. Can either reuse the {@link HazelcastInstance} that + * has been configured by the general {@link HazelcastAutoConfiguration} or create + * a separate one if the {@code spring.cache.hazelcast.config} property has been set. + *

+ * If the {@link HazelcastAutoConfiguration} has been disabled, an attempt to configure + * a default {@link HazelcastInstance} is still made, using the same defaults. * * @author Stephane Nicoll * @since 1.3.0 + * @see HazelcastConfigResourceCondition */ @Configuration -@ConditionalOnClass({ HazelcastInstance.class, HazelcastCacheManager.class }) +@ConditionalOnClass({HazelcastInstance.class, HazelcastCacheManager.class}) @ConditionalOnMissingBean(CacheManager.class) -@Conditional({ CacheCondition.class, - HazelcastCacheConfiguration.ConfigAvailableCondition.class }) +@Conditional(CacheCondition.class) +@AutoConfigureAfter(HazelcastAutoConfiguration.class) class HazelcastCacheConfiguration { - static final String CONFIG_SYSTEM_PROPERTY = "hazelcast.config"; + @Configuration + @ConditionalOnSingleCandidate(HazelcastInstance.class) + static class ExistingHazelcastInstanceConfiguration { - @Autowired - private CacheProperties cacheProperties; + @Autowired + private CacheProperties cacheProperties; - @Bean - public HazelcastCacheManager cacheManager(HazelcastInstance hazelcastInstance) { - return new HazelcastCacheManager(hazelcastInstance); + @Bean + public HazelcastCacheManager cacheManager(HazelcastInstance existingHazelcastInstance) + throws IOException { + Resource location = this.cacheProperties + .resolveConfigLocation(this.cacheProperties.getHazelcast().getConfig()); + if (location != null) { + HazelcastInstance cacheHazelcastInstance = + HazelcastAutoConfiguration.createHazelcastInstance(location); + return new CloseableHazelcastCacheManager(cacheHazelcastInstance); + } + else { + return new HazelcastCacheManager(existingHazelcastInstance); + } + + } } - @Bean - @ConditionalOnMissingBean - public HazelcastInstance hazelcastInstance() throws IOException { - Resource location = this.cacheProperties - .resolveConfigLocation(this.cacheProperties.getHazelcast().getConfig()); - if (location != null) { - Config cfg = new XmlConfigBuilder(location.getURL()).build(); - return Hazelcast.newHazelcastInstance(cfg); + @Configuration + @ConditionalOnMissingBean(HazelcastInstance.class) + @Conditional(ConfigAvailableCondition.class) + static class DefaultHazelcastInstanceConfiguration { + + @Autowired + private CacheProperties cacheProperties; + + @Bean + public HazelcastInstance hazelcastInstance() throws IOException { + Resource location = this.cacheProperties + .resolveConfigLocation(this.cacheProperties.getHazelcast().getConfig()); + if (location != null) { + HazelcastAutoConfiguration.createHazelcastInstance(location); + } + return Hazelcast.newHazelcastInstance(); } - return Hazelcast.newHazelcastInstance(); + + @Bean + public HazelcastCacheManager cacheManager() throws IOException { + return new HazelcastCacheManager(hazelcastInstance()); + } + } /** - * Determines if the Hazelcast configuration is available. This either kicks in if a - * default configuration has been found or if property referring to the file to use - * has been set. + * {@link HazelcastConfigResourceCondition} that checks if the + * {@code spring.cache.hazelcast.config} configuration key is defined. */ - static class ConfigAvailableCondition extends CacheConfigFileCondition { + static class ConfigAvailableCondition extends HazelcastConfigResourceCondition { public ConfigAvailableCondition() { - super("Hazelcast", "spring.cache.hazelcast", "file:./hazelcast.xml", - "classpath:/hazelcast.xml"); - } - - @Override - protected ConditionOutcome getResourceOutcome(ConditionContext context, - AnnotatedTypeMetadata metadata) { - if (System.getProperty(CONFIG_SYSTEM_PROPERTY) != null) { - return ConditionOutcome.match("System property '" - + CONFIG_SYSTEM_PROPERTY + "' is set."); - } - return super.getResourceOutcome(context, metadata); + super("spring.cache.hazelcast", "config"); } } + private static class CloseableHazelcastCacheManager extends HazelcastCacheManager implements Closeable { + private final HazelcastInstance hazelcastInstance; + + public CloseableHazelcastCacheManager(HazelcastInstance hazelcastInstance) { + super(hazelcastInstance); + this.hazelcastInstance = hazelcastInstance; + } + + @Override + public void close() throws IOException { + this.hazelcastInstance.shutdown(); + } + } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigFileCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ResourceCondition.java similarity index 64% rename from spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigFileCondition.java rename to spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ResourceCondition.java index 280f3f91fbb..bb4afce96e7 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigFileCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ResourceCondition.java @@ -14,33 +14,44 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.cache; +package org.springframework.boot.autoconfigure.condition; -import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotatedTypeMetadata; /** - * {@link SpringBootCondition} used to check if a cache configuration file can be found. + * {@link SpringBootCondition} used to check if a resource can be found using a + * configurable property and optional default location(s). * * @author Stephane Nicoll * @author Phillip Webb + * @since 1.3.0 */ -abstract class CacheConfigFileCondition extends SpringBootCondition { +public abstract class ResourceCondition extends SpringBootCondition { private final String name; private final String prefix; + private final String propertyName; + private final String[] resourceLocations; - public CacheConfigFileCondition(String name, String prefix, + /** + * Create a new condition. + * @param name the name of the component + * @param prefix the prefix of the configuration key + * @param propertyName the name of the configuration key + * @param resourceLocations default location(s) where the configuration file can be + * found if the configuration key is not specified + */ + protected ResourceCondition(String name, String prefix, String propertyName, String... resourceLocations) { this.name = name; this.prefix = (prefix.endsWith(".") ? prefix : prefix + "."); + this.propertyName = propertyName; this.resourceLocations = resourceLocations; } @@ -49,13 +60,19 @@ abstract class CacheConfigFileCondition extends SpringBootCondition { AnnotatedTypeMetadata metadata) { RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( context.getEnvironment(), this.prefix); - if (resolver.containsProperty("config")) { - return ConditionOutcome.match("A '" + this.prefix + ".config' " + if (resolver.containsProperty(propertyName)) { + return ConditionOutcome.match("A '" + this.prefix + propertyName +"' " + "property is specified"); } return getResourceOutcome(context, metadata); } + /** + * Check if one of the default resource locations actually exists. + * @param context the condition context + * @param metadata the annotation metadata + * @return the condition outcome + */ protected ConditionOutcome getResourceOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { for (String location : this.resourceLocations) { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfiguration.java new file mode 100644 index 00000000000..e6a1e92ef6d --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfiguration.java @@ -0,0 +1,128 @@ +/* + * Copyright 2012-2015 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.boot.autoconfigure.hazelcast; + +import java.io.IOException; +import java.net.URL; + +import com.hazelcast.config.Config; +import com.hazelcast.config.XmlConfigBuilder; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.util.ResourceUtils; +import org.springframework.util.StringUtils; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Hazelcast. Creates a + * {@link HazelcastInstance} based on explicit configuration or when a default + * configuration file is found in the environment. + * + * @author Stephane Nicoll + * @since 1.3.0 + * @see HazelcastConfigResourceCondition + */ +@Configuration +@ConditionalOnClass(HazelcastInstance.class) +@ConditionalOnMissingBean(HazelcastInstance.class) +@EnableConfigurationProperties(HazelcastProperties.class) +public class HazelcastAutoConfiguration { + + + /** + * Create a {@link HazelcastInstance} based on the specified configuration location. + * @param location the location of the configuration file + * @return a {@link HazelcastInstance} for the specified configuration + * @throws IOException the configuration file could not be read + */ + public static HazelcastInstance createHazelcastInstance(Resource location) + throws IOException { + Assert.notNull(location, "Config must not be null"); + URL configUrl = location.getURL(); + Config config = new XmlConfigBuilder(configUrl).build(); + if (ResourceUtils.isFileURL(configUrl)) { + config.setConfigurationFile(location.getFile()); + } + else { + config.setConfigurationUrl(configUrl); + } + return createHazelcastInstance(config); + } + + private static HazelcastInstance createHazelcastInstance(Config config) { + if (StringUtils.hasText(config.getInstanceName())) { + return Hazelcast.getOrCreateHazelcastInstance(config); + } + return Hazelcast.newHazelcastInstance(config); + } + + + @Configuration + @ConditionalOnMissingBean({HazelcastInstance.class, Config.class}) + @Conditional(ConfigAvailableCondition.class) + static class HazelcastConfigFileConfiguration { + + @Autowired + private HazelcastProperties hazelcastProperties; + + @Bean + @ConditionalOnMissingBean + public HazelcastInstance hazelcastInstance() throws IOException { + Resource config = this.hazelcastProperties.resolveConfigLocation(); + if (config != null) { + return createHazelcastInstance(config); + } + return Hazelcast.newHazelcastInstance(); + } + + } + + @Configuration + @ConditionalOnMissingBean(HazelcastInstance.class) + @ConditionalOnSingleCandidate(Config.class) + static class HazelcastConfigConfiguration { + + @Bean + public HazelcastInstance hazelcastInstance(Config config) { + return createHazelcastInstance(config); + } + + } + + /** + * {@link HazelcastConfigResourceCondition} that checks if the + * {@code spring.hazelcast.config} configuration key is defined. + */ + static class ConfigAvailableCondition extends HazelcastConfigResourceCondition { + + public ConfigAvailableCondition() { + super("spring.hazelcast", "config"); + } + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConfigResourceCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConfigResourceCondition.java new file mode 100644 index 00000000000..f0d24996988 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConfigResourceCondition.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2015 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.boot.autoconfigure.hazelcast; + +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.ResourceCondition; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link SpringBootCondition} used to check if the Hazelcast configuration is + * available. This either kicks in if a default configuration has been found or + * if configurable property referring to the resource to use has been set. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +public abstract class HazelcastConfigResourceCondition extends ResourceCondition { + + static final String CONFIG_SYSTEM_PROPERTY = "hazelcast.config"; + + protected HazelcastConfigResourceCondition(String prefix, String propertyName) { + super("Hazelcast", prefix, propertyName, "file:./hazelcast.xml", + "classpath:/hazelcast.xml"); + } + + @Override + protected ConditionOutcome getResourceOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + if (System.getProperty(CONFIG_SYSTEM_PROPERTY) != null) { + return ConditionOutcome.match("System property '" + + CONFIG_SYSTEM_PROPERTY + "' is set."); + } + return super.getResourceOutcome(context, metadata); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastProperties.java new file mode 100644 index 00000000000..ba33e4f458b --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastProperties.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2015 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.boot.autoconfigure.hazelcast; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; + +/** + * Configuration properties for the hazelcast integration. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +@ConfigurationProperties("spring.hazelcast") +public class HazelcastProperties { + + /** + * The location of the configuration file to use to initialize Hazelcast. + */ + private Resource config; + + public Resource getConfig() { + return this.config; + } + + public void setConfig(Resource config) { + this.config = config; + } + + /** + * Resolve the config location if set. + * @return the location or {@code null} if it is not set + * @throws IllegalArgumentException if the config attribute is set to an unknown + * location + */ + public Resource resolveConfigLocation() { + if (this.config != null) { + Assert.isTrue(this.config.exists(), "Hazelcast configuration does not exist '" + + this.config.getDescription() + "'"); + return this.config; + } + return null; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/package-info.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/package-info.java new file mode 100644 index 00000000000..8a40591cf51 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2015 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. + */ + +/** + * Auto-configuration for Hazelcast. + */ +package org.springframework.boot.autoconfigure.hazelcast; \ No newline at end of file diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 9980c1a0cd5..94718ac2c0f 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -24,6 +24,7 @@ org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ +org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java index 6b6f6f0b2d5..ff57a16f7ec 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.cache; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -38,6 +39,7 @@ import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.cache.support.MockCachingProvider; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; @@ -74,6 +76,7 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -367,6 +370,51 @@ public class CacheAutoConfigurationTests { is(this.context.getBean("customHazelcastInstance"))); } + @Test + public void hazelcastCacheWithMainHazelcastAutoConfiguration() throws IOException { + Collection> configs = new ArrayList>(); + configs.add(DefaultCacheConfiguration.class); + configs.add(HazelcastAutoConfiguration.class); + String mainConfig = + "org/springframework/boot/autoconfigure/hazelcast/hazelcast-specific.xml"; + doLoad(configs, "spring.cache.type=hazelcast", + "spring.hazelcast.config=" + mainConfig); + + HazelcastCacheManager cacheManager = validateCacheManager(HazelcastCacheManager.class); + HazelcastInstance hazelcastInstance = this.context.getBean(HazelcastInstance.class); + assertThat( + new DirectFieldAccessor(cacheManager) + .getPropertyValue("hazelcastInstance"), + is((Object) hazelcastInstance)); + assertThat(hazelcastInstance.getConfig().getConfigurationFile(), + is(new ClassPathResource(mainConfig).getFile())); + } + + @Test + public void hazelcastCacheWithMainHazelcastAutoConfigurationAndSeparateCacheConfig() + throws IOException { + Collection> configs = new ArrayList>(); + configs.add(DefaultCacheConfiguration.class); + configs.add(HazelcastAutoConfiguration.class); + String mainConfig = "org/springframework/boot/autoconfigure/hazelcast/hazelcast-specific.xml"; + String cacheConfig = "org/springframework/boot/autoconfigure/cache/hazelcast-specific.xml"; + doLoad(configs, "spring.cache.type=hazelcast", + "spring.cache.hazelcast.config=" + cacheConfig, + "spring.hazelcast.config=" + mainConfig); + HazelcastInstance hazelcastInstance = this.context.getBean(HazelcastInstance.class); + HazelcastCacheManager cacheManager = validateCacheManager(HazelcastCacheManager.class); + HazelcastInstance cacheHazelcastInstance = (HazelcastInstance) + new DirectFieldAccessor(cacheManager).getPropertyValue("hazelcastInstance"); + + assertThat( + cacheHazelcastInstance, + is(not(hazelcastInstance))); // Our custom cache instance + assertThat(hazelcastInstance.getConfig().getConfigurationFile(), + is(new ClassPathResource(mainConfig).getFile())); + assertThat(cacheHazelcastInstance.getConfig().getConfigurationFile(), + is(new ClassPathResource(cacheConfig).getFile())); + } + @Test public void hazelcastAsJCacheWithCaches() { String cachingProviderFqn = HazelcastCachingProvider.class.getName(); @@ -499,17 +547,21 @@ public class CacheAutoConfigurationTests { } private void load(Class config, String... environment) { - this.context = doLoad(config, environment); + Collection> configs = new ArrayList>(); + configs.add(config); + doLoad(configs, environment); } - private AnnotationConfigApplicationContext doLoad(Class config, + private void doLoad(Collection> configs, String... environment) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(applicationContext, environment); - applicationContext.register(config); + for (Class config : configs) { + applicationContext.register(config); + } applicationContext.register(CacheAutoConfiguration.class); applicationContext.refresh(); - return applicationContext; + this.context = applicationContext; } @Configuration diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheConfigFileConditionTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ResourceConditionTests.java similarity index 63% rename from spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheConfigFileConditionTests.java rename to spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ResourceConditionTests.java index cc8a9ebce3d..22d60c9027c 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheConfigFileConditionTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ResourceConditionTests.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.cache; +package org.springframework.boot.autoconfigure.condition; import org.junit.After; import org.junit.Test; + import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -29,11 +30,11 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** - * Test for {@link CacheConfigFileCondition}. + * Test for {@link ResourceCondition}. * * @author Stephane Nicoll */ -public class CacheConfigFileConditionTests { +public class ResourceConditionTests { private ConfigurableApplicationContext context; @@ -45,20 +46,21 @@ public class CacheConfigFileConditionTests { } @Test - public void defaultFileAndNoExplicitKey() { - load(DefaultFileConfiguration.class); + public void defaultResourceAndNoExplicitKey() { + load(DefaultLocationConfiguration.class); assertTrue(this.context.containsBean("foo")); } @Test - public void noDefaultFileAndNoExplicitKey() { - load(NoDefaultFileConfiguration.class); + public void unknownDefaultLocationAndNoExplicitKey() { + load(UnknownDefaultLocationConfiguration.class); assertFalse(this.context.containsBean("foo")); } @Test - public void noDefaultFileAndExplicitKeyToResource() { - load(NoDefaultFileConfiguration.class, "spring.cache.test.config=ehcache.xml"); + public void unknownDefaultLocationAndExplicitKeyToResource() { + load(UnknownDefaultLocationConfiguration.class, + "spring.foo.test.config=logging.properties"); assertTrue(this.context.containsBean("foo")); } @@ -71,8 +73,8 @@ public class CacheConfigFileConditionTests { } @Configuration - @Conditional(CacheConfigFileDefaultFileCondition.class) - static class DefaultFileConfiguration { + @Conditional(DefaultLocationResourceCondition.class) + static class DefaultLocationConfiguration { @Bean public String foo() { @@ -81,8 +83,8 @@ public class CacheConfigFileConditionTests { } @Configuration - @Conditional(CacheConfigFileNoDefaultFileCondition.class) - static class NoDefaultFileConfiguration { + @Conditional(UnknownDefaultLocationResourceCondition.class) + static class UnknownDefaultLocationConfiguration { @Bean public String foo() { @@ -90,19 +92,19 @@ public class CacheConfigFileConditionTests { } } - private static class CacheConfigFileDefaultFileCondition extends - CacheConfigFileCondition { + private static class DefaultLocationResourceCondition extends + ResourceCondition { - public CacheConfigFileDefaultFileCondition() { - super("test", "spring.cache.test.", "classpath:/ehcache.xml"); + public DefaultLocationResourceCondition() { + super("test", "spring.foo.test.", "config", "classpath:/logging.properties"); } } - private static class CacheConfigFileNoDefaultFileCondition extends - CacheConfigFileCondition { - public CacheConfigFileNoDefaultFileCondition() { - super("test", "spring.cache.test", - "classpath:/this-cache-file-does-not-exist.xml"); + private static class UnknownDefaultLocationResourceCondition extends + ResourceCondition { + public UnknownDefaultLocationResourceCondition() { + super("test", "spring.foo.test", "config", + "classpath:/this-file-does-not-exist.xml"); } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationTests.java new file mode 100644 index 00000000000..88a04d34fba --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationTests.java @@ -0,0 +1,182 @@ +/* + * Copyright 2012-2015 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.boot.autoconfigure.hazelcast; + +import java.io.IOException; +import java.util.Map; + +import com.hazelcast.config.Config; +import com.hazelcast.config.QueueConfig; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; + +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.hamcrest.collection.IsMapContaining.hasKey; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link HazelcastAutoConfiguration}. + * + * @author Stephane Nicoll + */ +public class HazelcastAutoConfigurationTests { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private AnnotationConfigApplicationContext context; + + @After + public void closeContext() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void defaultConfigFile() throws IOException { + load(); // hazelcast.xml present in root classpath + HazelcastInstance hazelcastInstance = this.context.getBean( + HazelcastInstance.class); + assertThat(hazelcastInstance.getConfig().getConfigurationUrl(), + is(new ClassPathResource("hazelcast.xml").getURL())); + } + + @Test + public void systemProperty() throws IOException { + System.setProperty(HazelcastConfigResourceCondition.CONFIG_SYSTEM_PROPERTY, + "classpath:org/springframework/boot/autoconfigure/hazelcast/hazelcast-specific.xml"); + try { + load(); + HazelcastInstance hazelcastInstance = this.context.getBean( + HazelcastInstance.class); + Map queueConfigs = hazelcastInstance.getConfig().getQueueConfigs(); + assertThat(queueConfigs.values(), hasSize(1)); + assertThat(queueConfigs, hasKey("foobar")); + } + finally { + System.clearProperty(HazelcastConfigResourceCondition.CONFIG_SYSTEM_PROPERTY); + } + } + + @Test + public void explicitConfigFile() throws IOException { + load("spring.hazelcast.config=org/springframework/boot/autoconfigure/hazelcast/" + + "hazelcast-specific.xml"); + HazelcastInstance hazelcastInstance = this.context.getBean( + HazelcastInstance.class); + assertThat(hazelcastInstance.getConfig().getConfigurationFile(), + is(new ClassPathResource("org/springframework/boot/autoconfigure/hazelcast" + + "/hazelcast-specific.xml").getFile())); + } + + @Test + public void explicitConfigUrl() throws IOException { + load("spring.hazelcast.config=hazelcast-default.xml"); + HazelcastInstance hazelcastInstance = this.context.getBean( + HazelcastInstance.class); + assertThat(hazelcastInstance.getConfig().getConfigurationUrl(), + is(new ClassPathResource("hazelcast-default.xml").getURL())); + } + + @Test + public void unknownConfigFile() { + this.thrown.expect(BeanCreationException.class); + this.thrown.expectMessage("foo/bar/unknown.xml"); + load("spring.hazelcast.config=foo/bar/unknown.xml"); + } + + @Test + public void configInstanceWithName() { + Config config = new Config("my-test-instance"); + HazelcastInstance existingHazelcastInstance = Hazelcast.newHazelcastInstance(config); + try { + load(HazelcastConfigWithName.class, + "spring.hazelcast.config=this-is-ignored.xml"); + HazelcastInstance hazelcastInstance = this.context.getBean( + HazelcastInstance.class); + assertThat(hazelcastInstance.getConfig().getInstanceName(), is("my-test-instance")); + // Should reuse any existing instance by default. + assertThat(hazelcastInstance, is(existingHazelcastInstance)); + } + finally { + existingHazelcastInstance.shutdown(); + } + } + + @Test + public void configInstanceWithoutName() { + load(HazelcastConfigNoName.class, + "spring.hazelcast.config=this-is-ignored.xml"); + HazelcastInstance hazelcastInstance = this.context.getBean( + HazelcastInstance.class); + Map queueConfigs = hazelcastInstance.getConfig().getQueueConfigs(); + assertThat(queueConfigs.values(), hasSize(1)); + assertThat(queueConfigs, hasKey("another-queue")); + } + + private void load(String... environment) { + load(null, environment); + } + + private void load(Class config, String... environment) { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(applicationContext, environment); + if (config != null) { + applicationContext.register(config); + } + applicationContext.register(HazelcastAutoConfiguration.class); + applicationContext.refresh(); + this.context = applicationContext; + } + + + @Configuration + static class HazelcastConfigWithName { + + @Bean + public Config myHazelcastConfig() { + return new Config("my-test-instance"); + } + + } + + @Configuration + static class HazelcastConfigNoName { + + @Bean + public Config anotherHazelcastConfig() { + Config config = new Config(); + config.addQueueConfig(new QueueConfig("another-queue")); + return config; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-specific.xml b/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-specific.xml new file mode 100644 index 00000000000..91b25d412de --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/hazelcast/hazelcast-specific.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 162338111f9..cc644490314 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -585,6 +585,9 @@ content into your application; rather pick only the properties that you need. spring.cache.jcache.provider= # fully qualified name of the CachingProvider implementation to use spring.cache.guava.spec= # link:http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheBuilderSpec.html[guava specs] + # HAZELCAST ({sc-spring-boot-autoconfigure}/hazelcast/HazelcastProperties.{sc-ext}[HazelcastProperties]) + spring.hazelcast.config= # location of the hazelcast configuration + # AOP spring.aop.auto= spring.aop.proxy-target-class= diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 4859333b15c..56053032549 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -2900,16 +2900,23 @@ manager. An alternate configuration file can be provide a well using: [[boot-features-caching-provider-hazelcast]] ==== Hazelcast -Hazelcast is used if a `hazelcast.xml` file can be found in the current working -directory, at the root of the classpath or a location specified via the `hazelcast.config` -system property. Spring Boot detects all of these and also allows for explicit location -using: + +Spring Boot has a <>. If +a `HazelcastInstance` has been auto-configured, it is automatically wrapped in a +`CacheManager`. + +If for some reason you need a different `HazelcastInstance` for caching, you can +request Spring Boot to create a separate one that will only used by the +`CacheManager`: [source,properties,indent=0] ---- - spring.cache.hazelcast.config=classpath:config/my-hazelcast.xml + spring.cache.hazelcast.config=classpath:config/my-cache-hazelcast.xml ---- +TIP: If a separate `HazelcastInstance` is created that way, it is not registered +in the application context. + [[boot-features-caching-provider-infinispan]] @@ -3391,6 +3398,36 @@ provide good examples of how to write XA wrappers. +[[boot-features-hazelcast]] +== Hazelcast + +If hazelcast is on the classpath, Spring Boot will auto-configure an `HazelcastInstance` +that you can inject in your application. The `HazelcastInstance` is only created if a +configuration is found. + +You can define a `com.hazelcast.config.Config` bean and we'll use that. If your +configuration defines an instance name, we'll try to locate an existing instance rather +than creating a new one. + +You could also specify the `hazelcast.xml` configuration file to use via configuration: + +[source,properties,indent=0] +---- + spring.hazelcast.config=classpath:config/my-hazelcast.xml +---- + +Otherwise, Spring Boot tries to find the Hazelcast configuration from the default +locations, that is `hazelcast.xml` in the working directory or at the root of the +classpath. We also check if the `hazelcast.config` system property is set. Check the +Hazelcast documentation for more details. + +NOTE: Spring Boot also has an +<>. The +`HazelcastInstance` is automatically wrapped in a `CacheManager` implementation if +caching is enabled. + + + [[boot-features-integration]] == Spring Integration Spring Integration provides abstractions over messaging and also other transports such as