Browse Source
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-2942pull/3532/merge
14 changed files with 680 additions and 85 deletions
@ -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"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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; |
||||||
@ -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<String, QueueConfig> 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<String, QueueConfig> 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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.5.xsd" |
||||||
|
xmlns="http://www.hazelcast.com/schema/config" |
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||||
|
|
||||||
|
<queue name="foobar"/> |
||||||
|
|
||||||
|
</hazelcast> |
||||||
Loading…
Reference in new issue