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