Create spring-boot-hazelcast module

This commit is contained in:
Stéphane Nicoll
2025-03-21 16:27:03 +01:00
committed by Phillip Webb
parent cf7d8332e2
commit 8d5c834df9
52 changed files with 132 additions and 106 deletions

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import com.hazelcast.core.HazelcastInstance;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Import;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Hazelcast IMDG. Creates a
* {@link HazelcastInstance} based on explicit configuration or when a default
* configuration file is found in the environment.
*
* @author Stephane Nicoll
* @author Vedran Pavic
* @since 4.0.0
* @see HazelcastConfigResourceCondition
*/
@AutoConfiguration
@ConditionalOnClass(HazelcastInstance.class)
@EnableConfigurationProperties(HazelcastProperties.class)
@Import({ HazelcastClientConfiguration.class, HazelcastServerConfiguration.class })
public class HazelcastAutoConfiguration {
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import com.hazelcast.client.config.ClientConfigRecognizer;
import com.hazelcast.config.ConfigStream;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Builder;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* {@link HazelcastConfigResourceCondition} that checks if the
* {@code spring.hazelcast.config} configuration key is defined.
*
* @author Stephane Nicoll
*/
class HazelcastClientConfigAvailableCondition extends HazelcastConfigResourceCondition {
HazelcastClientConfigAvailableCondition() {
super(HazelcastClientConfiguration.CONFIG_SYSTEM_PROPERTY, "file:./hazelcast-client.xml",
"classpath:/hazelcast-client.xml", "file:./hazelcast-client.yaml", "classpath:/hazelcast-client.yaml",
"file:./hazelcast-client.yml", "classpath:/hazelcast-client.yml");
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment().containsProperty(HAZELCAST_CONFIG_PROPERTY)) {
ConditionOutcome configValidationOutcome = HazelcastClientValidation.clientConfigOutcome(context,
HAZELCAST_CONFIG_PROPERTY, startConditionMessage());
return (configValidationOutcome != null) ? configValidationOutcome : ConditionOutcome
.match(startConditionMessage().foundExactly("property " + HAZELCAST_CONFIG_PROPERTY));
}
return getResourceOutcome(context, metadata);
}
static class HazelcastClientValidation {
static ConditionOutcome clientConfigOutcome(ConditionContext context, String propertyName, Builder builder) {
String resourcePath = context.getEnvironment().getProperty(propertyName);
Resource resource = context.getResourceLoader().getResource(resourcePath);
if (!resource.exists()) {
return ConditionOutcome.noMatch(builder.because("Hazelcast configuration does not exist"));
}
try (InputStream in = resource.getInputStream()) {
boolean clientConfig = new ClientConfigRecognizer().isRecognized(new ConfigStream(in));
return new ConditionOutcome(clientConfig, existingConfigurationOutcome(resource, clientConfig));
}
catch (Throwable ex) {
return null;
}
}
private static String existingConfigurationOutcome(Resource resource, boolean client) throws IOException {
URL location = resource.getURL();
return client ? "Hazelcast client configuration detected at '" + location + "'"
: "Hazelcast server configuration detected at '" + location + "'";
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import com.hazelcast.client.HazelcastClient;
import com.hazelcast.core.HazelcastInstance;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Configuration for Hazelcast client.
*
* @author Vedran Pavic
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HazelcastClient.class)
@ConditionalOnMissingBean(HazelcastInstance.class)
@Import({ HazelcastConnectionDetailsConfiguration.class, HazelcastClientInstanceConfiguration.class })
class HazelcastClientConfiguration {
static final String CONFIG_SYSTEM_PROPERTY = "hazelcast.client.config";
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import com.hazelcast.client.HazelcastClient;
import com.hazelcast.client.config.ClientConfig;
import com.hazelcast.core.HazelcastInstance;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
/**
* Configuration for Hazelcast client instance.
*
* @author Dmytro Nosan
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(HazelcastConnectionDetails.class)
class HazelcastClientInstanceConfiguration {
@Bean
HazelcastInstance hazelcastInstance(HazelcastConnectionDetails hazelcastConnectionDetails) {
ClientConfig config = hazelcastConnectionDetails.getClientConfig();
return (!StringUtils.hasText(config.getInstanceName())) ? HazelcastClient.newHazelcastClient(config)
: HazelcastClient.getOrCreateHazelcastClient(config);
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import com.hazelcast.config.Config;
/**
* Callback interface that can be implemented by beans wishing to customize the Hazelcast
* server {@link Config configuration}.
*
* @author Jaromir Hamala
* @author Stephane Nicoll
* @since 4.0.0
*/
@FunctionalInterface
public interface HazelcastConfigCustomizer {
/**
* Customize the configuration.
* @param config the {@link Config} to customize
*/
void customize(Config config);
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
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;
import org.springframework.util.Assert;
/**
* {@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
* @author Madhura Bhave
* @author Vedran Pavic
* @since 4.0.0
*/
public abstract class HazelcastConfigResourceCondition extends ResourceCondition {
protected static final String HAZELCAST_CONFIG_PROPERTY = "spring.hazelcast.config";
private final String configSystemProperty;
protected HazelcastConfigResourceCondition(String configSystemProperty, String... resourceLocations) {
super("Hazelcast", HAZELCAST_CONFIG_PROPERTY, resourceLocations);
Assert.notNull(configSystemProperty, "'configSystemProperty' must not be null");
this.configSystemProperty = configSystemProperty;
}
@Override
protected ConditionOutcome getResourceOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (System.getProperty(this.configSystemProperty) != null) {
return ConditionOutcome
.match(startConditionMessage().because("System property '" + this.configSystemProperty + "' is set."));
}
return super.getResourceOutcome(context, metadata);
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import com.hazelcast.client.config.ClientConfig;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
/**
* Details required to establish a client connection to a Hazelcast instance.
*
* @author Dmytro Nosan
* @since 4.0.0
*/
public interface HazelcastConnectionDetails extends ConnectionDetails {
/**
* The {@link ClientConfig} for Hazelcast client.
* @return the client config
*/
ClientConfig getClientConfig();
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import com.hazelcast.client.config.ClientConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
/**
* {@link Configuration} for providing {@link HazelcastConnectionDetails}.
*
* @author Dmytro Nosan
* @author Moritz Halbritter
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(HazelcastConnectionDetails.class)
class HazelcastConnectionDetailsConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(ClientConfig.class)
@Conditional(HazelcastClientConfigAvailableCondition.class)
static class HazelcastClientConfigFileConfiguration {
@Bean
HazelcastConnectionDetails hazelcastConnectionDetails(HazelcastProperties properties,
ResourceLoader resourceLoader) {
return new PropertiesHazelcastConnectionDetails(properties, resourceLoader);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(ClientConfig.class)
static class HazelcastClientConfigConfiguration {
@Bean
HazelcastConnectionDetails hazelcastConnectionDetails(ClientConfig config) {
return () -> config;
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import com.hazelcast.core.HazelcastInstance;
import jakarta.persistence.EntityManagerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.hazelcast.autoconfigure.HazelcastJpaDependencyAutoConfiguration.HazelcastInstanceEntityManagerFactoryDependsOnConfiguration;
import org.springframework.boot.jpa.autoconfigure.EntityManagerFactoryDependsOnPostProcessor;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
/**
* Additional configuration to ensure that {@link EntityManagerFactory} beans depend on
* the {@code hazelcastInstance} bean.
*
* @author Stephane Nicoll
* @since 4.0.0
*/
@AutoConfiguration(after = HazelcastAutoConfiguration.class,
afterName = "org.springframework.boot.jpa.autoconfigure.hibernate.HibernateJpaAutoConfiguration")
@ConditionalOnClass({ HazelcastInstance.class, LocalContainerEntityManagerFactoryBean.class })
@Import(HazelcastInstanceEntityManagerFactoryDependsOnConfiguration.class)
public class HazelcastJpaDependencyAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EntityManagerFactoryDependsOnPostProcessor.class)
@Conditional(OnHazelcastAndJpaCondition.class)
@Import(HazelcastInstanceEntityManagerFactoryDependsOnPostProcessor.class)
static class HazelcastInstanceEntityManagerFactoryDependsOnConfiguration {
}
static class HazelcastInstanceEntityManagerFactoryDependsOnPostProcessor
extends EntityManagerFactoryDependsOnPostProcessor {
HazelcastInstanceEntityManagerFactoryDependsOnPostProcessor() {
super("hazelcastInstance");
}
}
static class OnHazelcastAndJpaCondition extends AllNestedConditions {
OnHazelcastAndJpaCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(name = "hazelcastInstance")
static class HasHazelcastInstance {
}
@ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
static class HasJpa {
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
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 4.0.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) {
return null;
}
Assert.state(this.config.exists(),
() -> "Hazelcast configuration does not exist '" + this.config.getDescription() + "'");
return this.config;
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.spring.context.SpringManagedContext;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
* Configuration for Hazelcast server.
*
* @author Stephane Nicoll
* @author Vedran Pavic
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(HazelcastInstance.class)
class HazelcastServerConfiguration {
static final String CONFIG_SYSTEM_PROPERTY = "hazelcast.config";
static final String HAZELCAST_LOGGING_TYPE = "hazelcast.logging.type";
private static HazelcastInstance getHazelcastInstance(Config config) {
if (StringUtils.hasText(config.getInstanceName())) {
return Hazelcast.getOrCreateHazelcastInstance(config);
}
return Hazelcast.newHazelcastInstance(config);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(Config.class)
@Conditional(ConfigAvailableCondition.class)
static class HazelcastServerConfigFileConfiguration {
@Bean
HazelcastInstance hazelcastInstance(HazelcastProperties properties, ResourceLoader resourceLoader,
ObjectProvider<HazelcastConfigCustomizer> hazelcastConfigCustomizers) throws IOException {
Resource configLocation = properties.resolveConfigLocation();
Config config = (configLocation != null) ? loadConfig(configLocation) : Config.load();
config.setClassLoader(resourceLoader.getClassLoader());
hazelcastConfigCustomizers.orderedStream().forEach((customizer) -> customizer.customize(config));
return getHazelcastInstance(config);
}
private Config loadConfig(Resource configLocation) throws IOException {
URL configUrl = configLocation.getURL();
Config config = loadConfig(configUrl);
if (ResourceUtils.isFileURL(configUrl)) {
config.setConfigurationFile(configLocation.getFile());
}
else {
config.setConfigurationUrl(configUrl);
}
return config;
}
private Config loadConfig(URL configUrl) throws IOException {
try (InputStream stream = configUrl.openStream()) {
return Config.loadFromStream(stream);
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(Config.class)
static class HazelcastServerConfigConfiguration {
@Bean
HazelcastInstance hazelcastInstance(Config config) {
return getHazelcastInstance(config);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SpringManagedContext.class)
static class SpringManagedContextHazelcastConfigCustomizerConfiguration {
@Bean
@Order(0)
HazelcastConfigCustomizer springManagedContextHazelcastConfigCustomizer(ApplicationContext applicationContext) {
return (config) -> {
SpringManagedContext managementContext = new SpringManagedContext();
managementContext.setApplicationContext(applicationContext);
config.setManagedContext(managementContext);
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(org.slf4j.Logger.class)
static class HazelcastLoggingConfigCustomizerConfiguration {
@Bean
@Order(0)
HazelcastConfigCustomizer loggingHazelcastConfigCustomizer() {
return (config) -> {
if (!config.getProperties().containsKey(HAZELCAST_LOGGING_TYPE)) {
config.setProperty(HAZELCAST_LOGGING_TYPE, "slf4j");
}
};
}
}
/**
* {@link HazelcastConfigResourceCondition} that checks if the
* {@code spring.hazelcast.config} configuration key is defined.
*/
static class ConfigAvailableCondition extends HazelcastConfigResourceCondition {
ConfigAvailableCondition() {
super(CONFIG_SYSTEM_PROPERTY, "file:./hazelcast.xml", "classpath:/hazelcast.xml", "file:./hazelcast.yaml",
"classpath:/hazelcast.yaml", "file:./hazelcast.yml", "classpath:/hazelcast.yml");
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.util.Locale;
import com.hazelcast.client.config.ClientConfig;
import com.hazelcast.client.config.XmlClientConfigBuilder;
import com.hazelcast.client.config.YamlClientConfigBuilder;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
/**
* Adapts {@link HazelcastProperties} to {@link HazelcastConnectionDetails}.
*
* @author Dmytro Nosan
*/
class PropertiesHazelcastConnectionDetails implements HazelcastConnectionDetails {
private final HazelcastProperties properties;
private final ResourceLoader resourceLoader;
PropertiesHazelcastConnectionDetails(HazelcastProperties properties, ResourceLoader resourceLoader) {
this.properties = properties;
this.resourceLoader = resourceLoader;
}
@Override
public ClientConfig getClientConfig() {
Resource configLocation = this.properties.resolveConfigLocation();
ClientConfig config = (configLocation != null) ? loadClientConfig(configLocation) : ClientConfig.load();
config.setClassLoader(this.resourceLoader.getClassLoader());
return config;
}
private ClientConfig loadClientConfig(Resource configLocation) {
try {
URL configUrl = configLocation.getURL();
String configFileName = configUrl.getPath().toLowerCase(Locale.ROOT);
return (!isYaml(configFileName)) ? new XmlClientConfigBuilder(configUrl).build()
: new YamlClientConfigBuilder(configUrl).build();
}
catch (IOException ex) {
throw new UncheckedIOException("Failed to load Hazelcast config", ex);
}
}
private boolean isYaml(String configFileName) {
return configFileName.endsWith(".yaml") || configFileName.endsWith(".yml");
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;

View File

@@ -0,0 +1,2 @@
org.springframework.boot.hazelcast.autoconfigure.HazelcastAutoConfiguration
org.springframework.boot.hazelcast.autoconfigure.HazelcastJpaDependencyAutoConfiguration

View File

@@ -0,0 +1,253 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.util.Set;
import com.hazelcast.client.HazelcastClient;
import com.hazelcast.client.config.ClientConfig;
import com.hazelcast.client.impl.clientside.HazelcastClientProxy;
import com.hazelcast.config.Config;
import com.hazelcast.config.NetworkConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import org.assertj.core.api.Condition;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HazelcastAutoConfiguration} specific to the client.
*
* @author Vedran Pavic
* @author Stephane Nicoll
*/
class HazelcastAutoConfigurationClientTests {
/**
* Servers the test clients will connect to.
*/
private static HazelcastInstance hazelcastServer;
private static String endpointAddress;
@BeforeAll
static void init() {
Config config = Config.load();
NetworkConfig networkConfig = config.getNetworkConfig();
networkConfig.setPort(0);
networkConfig.setPublicAddress("localhost");
hazelcastServer = Hazelcast.newHazelcastInstance(config);
InetSocketAddress inetSocketAddress = (InetSocketAddress) hazelcastServer.getLocalEndpoint().getSocketAddress();
endpointAddress = inetSocketAddress.getHostString() + ":" + inetSocketAddress.getPort();
}
@AfterAll
static void close() {
if (hazelcastServer != null) {
hazelcastServer.shutdown();
}
}
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HazelcastAutoConfiguration.class));
@Test
void systemPropertyWithXml() {
File config = prepareConfiguration("src/test/resources/org/springframework/"
+ "boot/hazelcast/autoconfigure/hazelcast-client-specific.xml");
this.contextRunner
.withSystemProperties(HazelcastClientConfiguration.CONFIG_SYSTEM_PROPERTY + "=" + config.getAbsolutePath())
.run(assertSpecificHazelcastClient("explicit-xml"));
}
@Test
void systemPropertyWithYaml() {
File config = prepareConfiguration("src/test/resources/org/springframework/"
+ "boot/hazelcast/autoconfigure/hazelcast-client-specific.yaml");
this.contextRunner
.withSystemProperties(HazelcastClientConfiguration.CONFIG_SYSTEM_PROPERTY + "=" + config.getAbsolutePath())
.run(assertSpecificHazelcastClient("explicit-yaml"));
}
@Test
void systemPropertyWithYml() {
File config = prepareConfiguration("src/test/resources/org/springframework/"
+ "boot/hazelcast/autoconfigure/hazelcast-client-specific.yml");
this.contextRunner
.withSystemProperties(HazelcastClientConfiguration.CONFIG_SYSTEM_PROPERTY + "=" + config.getAbsolutePath())
.run(assertSpecificHazelcastClient("explicit-yml"));
}
@Test
void explicitConfigUrlWithXml() throws MalformedURLException {
File config = prepareConfiguration("src/test/resources/org/springframework/"
+ "boot/hazelcast/autoconfigure/hazelcast-client-specific.xml");
this.contextRunner.withPropertyValues("spring.hazelcast.config=" + config.toURI().toURL())
.run(assertSpecificHazelcastClient("explicit-xml"));
}
@Test
void explicitConfigUrlWithYaml() throws MalformedURLException {
File config = prepareConfiguration("src/test/resources/org/springframework/"
+ "boot/hazelcast/autoconfigure/hazelcast-client-specific.yaml");
this.contextRunner.withPropertyValues("spring.hazelcast.config=" + config.toURI().toURL())
.run(assertSpecificHazelcastClient("explicit-yaml"));
}
@Test
void explicitConfigUrlWithYml() throws MalformedURLException {
File config = prepareConfiguration("src/test/resources/org/springframework/"
+ "boot/hazelcast/autoconfigure/hazelcast-client-specific.yml");
this.contextRunner.withPropertyValues("spring.hazelcast.config=" + config.toURI().toURL())
.run(assertSpecificHazelcastClient("explicit-yml"));
}
@Test
void unknownConfigFile() {
this.contextRunner.withPropertyValues("spring.hazelcast.config=foo/bar/unknown.xml")
.run((context) -> assertThat(context).getFailure()
.isInstanceOf(BeanCreationException.class)
.hasMessageContaining("foo/bar/unknown.xml"));
}
@Test
void clientConfigTakesPrecedence() {
this.contextRunner.withUserConfiguration(HazelcastServerAndClientConfig.class)
.withPropertyValues("spring.hazelcast.config=this-is-ignored.xml")
.run((context) -> assertThat(context).getBean(HazelcastInstance.class)
.isInstanceOf(HazelcastClientProxy.class));
}
@Test
void connectionDetailsTakesPrecedenceOverConfigFile() {
this.contextRunner.withUserConfiguration(HazelcastConnectionDetailsConfig.class)
.withPropertyValues("spring.hazelcast.config=this-is-ignored.xml")
.run(assertSpecificHazelcastClient("connection-details"));
}
@Test
void connectionDetailsTakesPrecedenceOverUserDefinedClientConfig() {
this.contextRunner
.withUserConfiguration(HazelcastConnectionDetailsConfig.class, HazelcastServerAndClientConfig.class)
.withPropertyValues("spring.hazelcast.config=this-is-ignored.xml")
.run(assertSpecificHazelcastClient("connection-details"));
}
@Test
void clientConfigWithInstanceNameCreatesClientIfNecessary() throws MalformedURLException {
assertThat(HazelcastClient.getHazelcastClientByName("spring-boot")).isNull();
File config = prepareConfiguration("src/test/resources/org/springframework/"
+ "boot/hazelcast/autoconfigure/hazelcast-client-instance.xml");
this.contextRunner.withPropertyValues("spring.hazelcast.config=" + config.toURI().toURL())
.run((context) -> assertThat(context).getBean(HazelcastInstance.class)
.extracting(HazelcastInstance::getName)
.isEqualTo("spring-boot"));
}
@Test
void autoConfiguredClientConfigUsesApplicationClassLoader() throws MalformedURLException {
File config = prepareConfiguration("src/test/resources/org/springframework/"
+ "boot/hazelcast/autoconfigure/hazelcast-client-specific.xml");
this.contextRunner.withPropertyValues("spring.hazelcast.config=" + config.toURI().toURL()).run((context) -> {
HazelcastInstance hazelcast = context.getBean(HazelcastInstance.class);
assertThat(hazelcast).isInstanceOf(HazelcastClientProxy.class);
ClientConfig clientConfig = ((HazelcastClientProxy) hazelcast).getClientConfig();
assertThat(clientConfig.getClassLoader()).isSameAs(context.getSourceApplicationContext().getClassLoader());
});
}
private ContextConsumer<AssertableApplicationContext> assertSpecificHazelcastClient(String label) {
return (context) -> assertThat(context).getBean(HazelcastInstance.class)
.isInstanceOf(HazelcastInstance.class)
.has(labelEqualTo(label));
}
private static Condition<HazelcastInstance> labelEqualTo(String label) {
return new Condition<>((o) -> ((HazelcastClientProxy) o).getClientConfig()
.getLabels()
.stream()
.anyMatch((e) -> e.equals(label)), "Label equals to " + label);
}
private File prepareConfiguration(String input) {
File configFile = new File(input);
try {
String config = FileCopyUtils.copyToString(new FileReader(configFile));
config = config.replace("${address}", endpointAddress);
System.out.println(config);
File outputFile = new File(Files.createTempDirectory(getClass().getSimpleName()).toFile(),
configFile.getName());
FileCopyUtils.copy(config, new FileWriter(outputFile));
return outputFile;
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
@Configuration(proxyBeanMethods = false)
static class HazelcastConnectionDetailsConfig {
@Bean
HazelcastConnectionDetails hazelcastConnectionDetails() {
ClientConfig config = new ClientConfig();
config.setLabels(Set.of("connection-details"));
config.getConnectionStrategyConfig().getConnectionRetryConfig().setClusterConnectTimeoutMillis(60000);
config.getNetworkConfig().getAddresses().add(endpointAddress);
return () -> config;
}
}
@Configuration(proxyBeanMethods = false)
static class HazelcastServerAndClientConfig {
@Bean
Config config() {
return new Config();
}
@Bean
ClientConfig clientConfig() {
ClientConfig config = new ClientConfig();
config.getConnectionStrategyConfig().getConnectionRetryConfig().setClusterConnectTimeoutMillis(60000);
config.getNetworkConfig().getAddresses().add(endpointAddress);
return config;
}
}
}

View File

@@ -0,0 +1,358 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
import com.hazelcast.config.Config;
import com.hazelcast.config.JoinConfig;
import com.hazelcast.config.QueueConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.map.EntryProcessor;
import com.hazelcast.map.IMap;
import com.hazelcast.spring.context.SpringAware;
import com.hazelcast.spring.context.SpringManagedContext;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HazelcastAutoConfiguration} when the client library is not present.
*
* @author Stephane Nicoll
*/
class HazelcastAutoConfigurationServerTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HazelcastAutoConfiguration.class));
@Test
@WithHazelcastXmlResource
void defaultConfigFile() {
// hazelcast.xml present in root classpath
this.contextRunner.run((context) -> {
Config config = context.getBean(HazelcastInstance.class).getConfig();
assertThat(config.getConfigurationUrl()).isEqualTo(new ClassPathResource("hazelcast.xml").getURL());
});
}
@Test
void systemPropertyWithXml() {
this.contextRunner
.withSystemProperties(HazelcastServerConfiguration.CONFIG_SYSTEM_PROPERTY
+ "=classpath:org/springframework/boot/hazelcast/autoconfigure/hazelcast-specific.xml")
.run((context) -> {
Config config = context.getBean(HazelcastInstance.class).getConfig();
assertThat(config.getMapConfigs().keySet()).containsOnly("foobar");
});
}
@Test
void systemPropertyWithYaml() {
this.contextRunner
.withSystemProperties(HazelcastServerConfiguration.CONFIG_SYSTEM_PROPERTY
+ "=classpath:org/springframework/boot/hazelcast/autoconfigure/hazelcast-specific.yaml")
.run((context) -> {
Config config = context.getBean(HazelcastInstance.class).getConfig();
assertThat(config.getMapConfigs().keySet()).containsOnly("foobar");
});
}
@Test
void systemPropertyWithYml() {
this.contextRunner
.withSystemProperties(HazelcastServerConfiguration.CONFIG_SYSTEM_PROPERTY
+ "=classpath:org/springframework/boot/hazelcast/autoconfigure/hazelcast-specific.yml")
.run((context) -> {
Config config = context.getBean(HazelcastInstance.class).getConfig();
assertThat(config.getMapConfigs().keySet()).containsOnly("foobar");
});
}
@Test
void explicitConfigFileWithXml() {
this.contextRunner
.withPropertyValues("spring.hazelcast.config=org/springframework/boot/hazelcast/autoconfigure/"
+ "hazelcast-specific.xml")
.run(assertSpecificHazelcastServer(
"org/springframework/boot/hazelcast/autoconfigure/hazelcast-specific.xml"));
}
@Test
void explicitConfigFileWithYaml() {
this.contextRunner
.withPropertyValues("spring.hazelcast.config=org/springframework/boot/hazelcast/autoconfigure/"
+ "hazelcast-specific.yaml")
.run(assertSpecificHazelcastServer(
"org/springframework/boot/hazelcast/autoconfigure/hazelcast-specific.yaml"));
}
@Test
void explicitConfigFileWithYml() {
this.contextRunner
.withPropertyValues("spring.hazelcast.config=org/springframework/boot/hazelcast/autoconfigure/"
+ "hazelcast-specific.yml")
.run(assertSpecificHazelcastServer(
"org/springframework/boot/hazelcast/autoconfigure/hazelcast-specific.yml"));
}
@Test
void explicitConfigUrlWithXml() {
this.contextRunner
.withPropertyValues("spring.hazelcast.config=classpath:org/springframework/"
+ "boot/hazelcast/autoconfigure/hazelcast-specific.xml")
.run(assertSpecificHazelcastServer(
"org/springframework/boot/hazelcast/autoconfigure/hazelcast-specific.xml"));
}
@Test
void explicitConfigUrlWithYaml() {
this.contextRunner
.withPropertyValues("spring.hazelcast.config=classpath:org/springframework/"
+ "boot/hazelcast/autoconfigure/hazelcast-specific.yaml")
.run(assertSpecificHazelcastServer(
"org/springframework/boot/hazelcast/autoconfigure/hazelcast-specific.yaml"));
}
@Test
void explicitConfigUrlWithYml() {
this.contextRunner
.withPropertyValues("spring.hazelcast.config=classpath:org/springframework/"
+ "boot/hazelcast/autoconfigure/hazelcast-specific.yml")
.run(assertSpecificHazelcastServer(
"org/springframework/boot/hazelcast/autoconfigure/hazelcast-specific.yml"));
}
private ContextConsumer<AssertableApplicationContext> assertSpecificHazelcastServer(String location) {
return (context) -> {
Config config = context.getBean(HazelcastInstance.class).getConfig();
String configurationLocation = (config.getConfigurationUrl() != null)
? config.getConfigurationUrl().toString()
: config.getConfigurationFile().toURI().toURL().toString();
assertThat(configurationLocation).endsWith(location);
};
}
@Test
void unknownConfigFile() {
this.contextRunner.withPropertyValues("spring.hazelcast.config=foo/bar/unknown.xml")
.run((context) -> assertThat(context).getFailure()
.isInstanceOf(BeanCreationException.class)
.hasMessageContaining("foo/bar/unknown.xml"));
}
@Test
void configInstanceWithName() {
Config config = createTestConfig("my-test-instance");
HazelcastInstance existing = Hazelcast.newHazelcastInstance(config);
try {
this.contextRunner.withUserConfiguration(HazelcastConfigWithName.class)
.withPropertyValues("spring.hazelcast.config=this-is-ignored.xml")
.run((context) -> {
HazelcastInstance hazelcast = context.getBean(HazelcastInstance.class);
assertThat(hazelcast.getConfig().getInstanceName()).isEqualTo("my-test-instance");
// Should reuse any existing instance by default.
assertThat(hazelcast).isEqualTo(existing);
});
}
finally {
existing.shutdown();
}
}
@Test
void configInstanceWithoutName() {
this.contextRunner.withUserConfiguration(HazelcastConfigNoName.class)
.withPropertyValues("spring.hazelcast.config=this-is-ignored.xml")
.run((context) -> {
Config config = context.getBean(HazelcastInstance.class).getConfig();
Map<String, QueueConfig> queueConfigs = config.getQueueConfigs();
assertThat(queueConfigs.keySet()).containsOnly("another-queue");
});
}
@Test
@WithHazelcastXmlResource
void autoConfiguredConfigUsesApplicationClassLoader() {
this.contextRunner.run((context) -> {
Config config = context.getBean(HazelcastInstance.class).getConfig();
assertThat(config.getClassLoader()).isSameAs(context.getSourceApplicationContext().getClassLoader());
});
}
@Test
@WithHazelcastXmlResource
void autoConfiguredConfigUsesSpringManagedContext() {
this.contextRunner.run((context) -> {
Config config = context.getBean(HazelcastInstance.class).getConfig();
assertThat(config.getManagedContext()).isInstanceOf(SpringManagedContext.class);
});
}
@Test
@WithHazelcastXmlResource
void autoConfiguredConfigCanUseSpringAwareComponent() {
this.contextRunner.withPropertyValues("test.hazelcast.key=42").run((context) -> {
HazelcastInstance hz = context.getBean(HazelcastInstance.class);
IMap<String, String> map = hz.getMap("test");
assertThat(map.executeOnKey("test.hazelcast.key", new SpringAwareEntryProcessor<>())).isEqualTo("42");
});
}
@Test
@WithHazelcastXmlResource
void autoConfiguredConfigWithoutHazelcastSpringDoesNotUseSpringManagedContext() {
this.contextRunner
.withClassLoader(
new FilteredClassLoader(Thread.currentThread().getContextClassLoader(), SpringManagedContext.class))
.run((context) -> {
Config config = context.getBean(HazelcastInstance.class).getConfig();
assertThat(config.getManagedContext()).isNull();
});
}
@Test
@WithHazelcastXmlResource
void autoConfiguredContextCanOverrideManagementContextUsingCustomizer() {
this.contextRunner.withBean(TestHazelcastConfigCustomizer.class).run((context) -> {
Config config = context.getBean(HazelcastInstance.class).getConfig();
assertThat(config.getManagedContext()).isNull();
});
}
@Test
@WithHazelcastXmlResource
void autoConfiguredConfigSetsHazelcastLoggingToSlf4j() {
this.contextRunner.run((context) -> {
Config config = context.getBean(HazelcastInstance.class).getConfig();
assertThat(config.getProperty(HazelcastServerConfiguration.HAZELCAST_LOGGING_TYPE)).isEqualTo("slf4j");
});
}
@Test
void autoConfiguredConfigCanOverrideHazelcastLogging() {
this.contextRunner.withUserConfiguration(HazelcastConfigWithJDKLogging.class).run((context) -> {
Config config = context.getBean(HazelcastInstance.class).getConfig();
assertThat(config.getProperty(HazelcastServerConfiguration.HAZELCAST_LOGGING_TYPE)).isEqualTo("jdk");
});
}
private static Config createTestConfig(String instanceName) {
Config config = new Config(instanceName);
JoinConfig join = config.getNetworkConfig().getJoin();
join.getAutoDetectionConfig().setEnabled(false);
join.getMulticastConfig().setEnabled(false);
return config;
}
@Configuration(proxyBeanMethods = false)
static class HazelcastConfigWithName {
@Bean
Config myHazelcastConfig() {
return new Config("my-test-instance");
}
}
@Configuration(proxyBeanMethods = false)
static class HazelcastConfigNoName {
@Bean
Config anotherHazelcastConfig() {
Config config = createTestConfig("another-test-instance");
config.addQueueConfig(new QueueConfig("another-queue"));
return config;
}
}
@Configuration(proxyBeanMethods = false)
static class HazelcastConfigWithJDKLogging {
@Bean
Config anotherHazelcastConfig() {
Config config = new Config();
config.setProperty(HazelcastServerConfiguration.HAZELCAST_LOGGING_TYPE, "jdk");
return config;
}
}
@SpringAware
static class SpringAwareEntryProcessor<V> implements EntryProcessor<String, V, String> {
@Autowired
private Environment environment;
@Override
public String process(Map.Entry<String, V> entry) {
return this.environment.getProperty(entry.getKey());
}
}
@Order(1)
static class TestHazelcastConfigCustomizer implements HazelcastConfigCustomizer {
@Override
public void customize(Config config) {
config.setManagedContext(null);
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@WithResource(name = "hazelcast.xml", content = """
<hazelcast
xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-5.0.xsd"
xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<instance-name>default-instance</instance-name>
<map name="defaultCache" />
<network>
<join>
<auto-detection enabled="false" />
<multicast enabled="false" />
</join>
</network>
</hazelcast>
""")
@interface WithHazelcastXmlResource {
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import com.hazelcast.config.Config;
import com.hazelcast.core.HazelcastInstance;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HazelcastAutoConfiguration} with full classpath.
*
* @author Stephane Nicoll
*/
class HazelcastAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HazelcastAutoConfiguration.class));
@Test
@WithResource(name = "hazelcast.xml", content = """
<hazelcast
xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-5.0.xsd"
xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<instance-name>default-instance</instance-name>
<map name="defaultCache" />
<network>
<join>
<auto-detection enabled="false" />
<multicast enabled="false" />
</join>
</network>
</hazelcast>
""")
@WithResource(name = "hazelcast.yml", content = """
hazelcast:
network:
join:
auto-detection:
enabled: false
multicast:
enabled: false
""")
@WithResource(name = "hazelcast.yaml", content = """
hazelcast:
network:
join:
auto-detection:
enabled: false
multicast:
enabled: false
""")
void defaultConfigFileIsHazelcastXml() {
// no hazelcast-client.xml and hazelcast.xml is present in root classpath
// this also asserts that XML has priority over YAML
// as hazelcast.yaml, hazelcast.yml, and hazelcast.xml are available.
this.contextRunner.run((context) -> {
Config config = context.getBean(HazelcastInstance.class).getConfig();
assertThat(config.getConfigurationUrl()).isEqualTo(new ClassPathResource("hazelcast.xml").getURL());
});
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link HazelcastClientConfigAvailableCondition}.
*
* @author Stephane Nicoll
*/
class HazelcastClientConfigAvailableConditionTests {
private final HazelcastClientConfigAvailableCondition condition = new HazelcastClientConfigAvailableCondition();
@Test
void explicitConfigurationWithClientConfigMatches() {
ConditionOutcome outcome = getMatchOutcome(new MockEnvironment().withProperty("spring.hazelcast.config",
"classpath:org/springframework/boot/hazelcast/autoconfigure/hazelcast-client-specific.xml"));
assertThat(outcome.isMatch()).isTrue();
assertThat(outcome.getMessage()).contains("Hazelcast client configuration detected");
}
@Test
void explicitConfigurationWithServerConfigDoesNotMatch() {
ConditionOutcome outcome = getMatchOutcome(new MockEnvironment().withProperty("spring.hazelcast.config",
"classpath:org/springframework/boot/hazelcast/autoconfigure/hazelcast-specific.xml"));
assertThat(outcome.isMatch()).isFalse();
assertThat(outcome.getMessage()).contains("Hazelcast server configuration detected");
}
@Test
void explicitConfigurationWithMissingConfigDoesNotMatch() {
ConditionOutcome outcome = getMatchOutcome(new MockEnvironment().withProperty("spring.hazelcast.config",
"classpath:org/springframework/boot/hazelcast/autoconfigure/test-config-does-not-exist.xml"));
assertThat(outcome.isMatch()).isFalse();
assertThat(outcome.getMessage()).contains("Hazelcast configuration does not exist");
}
private ConditionOutcome getMatchOutcome(Environment environment) {
ConditionContext conditionContext = mock(ConditionContext.class);
given(conditionContext.getEnvironment()).willReturn(environment);
given(conditionContext.getResourceLoader()).willReturn(new DefaultResourceLoader());
return this.condition.getMatchOutcome(conditionContext, mock(AnnotatedTypeMetadata.class));
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2012-2025 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
*
* https://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.hazelcast.autoconfigure;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.hazelcast.core.HazelcastInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.hazelcast.autoconfigure.HazelcastJpaDependencyAutoConfiguration.HazelcastInstanceEntityManagerFactoryDependsOnPostProcessor;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.jpa.autoconfigure.EntityManagerFactoryDependsOnPostProcessor;
import org.springframework.boot.jpa.autoconfigure.hibernate.HibernateJpaAutoConfiguration;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link HazelcastJpaDependencyAutoConfiguration}.
*
* @author Stephane Nicoll
*/
class HazelcastJpaDependencyAutoConfigurationTests {
private static final String POST_PROCESSOR_BEAN_NAME = HazelcastInstanceEntityManagerFactoryDependsOnPostProcessor.class
.getName();
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
HazelcastJpaDependencyAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name=true");
@Test
void registrationIfHazelcastInstanceHasRegularBeanName() {
this.contextRunner.withUserConfiguration(HazelcastConfiguration.class).run((context) -> {
assertThat(postProcessors(context)).containsKey(POST_PROCESSOR_BEAN_NAME);
assertThat(entityManagerFactoryDependencies(context)).contains("hazelcastInstance");
});
}
@Test
void noRegistrationIfHazelcastInstanceHasCustomBeanName() {
this.contextRunner.withUserConfiguration(HazelcastCustomNameConfiguration.class).run((context) -> {
assertThat(entityManagerFactoryDependencies(context)).doesNotContain("hazelcastInstance");
assertThat(postProcessors(context)).doesNotContainKey(POST_PROCESSOR_BEAN_NAME);
});
}
@Test
void noRegistrationWithNoHazelcastInstance() {
this.contextRunner.run((context) -> {
assertThat(entityManagerFactoryDependencies(context)).doesNotContain("hazelcastInstance");
assertThat(postProcessors(context)).doesNotContainKey(POST_PROCESSOR_BEAN_NAME);
});
}
@Test
void noRegistrationWithNoEntityManagerFactory() {
new ApplicationContextRunner().withUserConfiguration(HazelcastConfiguration.class)
.withConfiguration(AutoConfigurations.of(HazelcastJpaDependencyAutoConfiguration.class))
.run((context) -> assertThat(postProcessors(context)).doesNotContainKey(POST_PROCESSOR_BEAN_NAME));
}
private Map<String, EntityManagerFactoryDependsOnPostProcessor> postProcessors(
AssertableApplicationContext context) {
return context.getBeansOfType(EntityManagerFactoryDependsOnPostProcessor.class);
}
private List<String> entityManagerFactoryDependencies(AssertableApplicationContext context) {
String[] dependsOn = ((BeanDefinitionRegistry) context.getSourceApplicationContext())
.getBeanDefinition("entityManagerFactory")
.getDependsOn();
return (dependsOn != null) ? Arrays.asList(dependsOn) : Collections.emptyList();
}
@Configuration(proxyBeanMethods = false)
static class HazelcastConfiguration {
@Bean
HazelcastInstance hazelcastInstance() {
return mock(HazelcastInstance.class);
}
}
@Configuration(proxyBeanMethods = false)
static class HazelcastCustomNameConfiguration {
@Bean
HazelcastInstance myHazelcastInstance() {
return mock(HazelcastInstance.class);
}
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<hazelcast-client xmlns="http://www.hazelcast.com/schema/client-config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/client-config hazelcast-client-config-5.0.xsd">
<instance-name>spring-boot</instance-name>
<connection-strategy>
<connection-retry>
<cluster-connect-timeout-millis>60000</cluster-connect-timeout-millis>
</connection-retry>
</connection-strategy>
<network>
<cluster-members>
<address>${address}</address>
</cluster-members>
</network>
</hazelcast-client>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<hazelcast-client xmlns="http://www.hazelcast.com/schema/client-config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/client-config hazelcast-client-config-5.0.xsd">
<client-labels>
<label>explicit-xml</label>
</client-labels>
<connection-strategy>
<connection-retry>
<cluster-connect-timeout-millis>60000</cluster-connect-timeout-millis>
</connection-retry>
</connection-strategy>
<network>
<cluster-members>
<address>${address}</address>
</cluster-members>
</network>
</hazelcast-client>

View File

@@ -0,0 +1,9 @@
hazelcast-client:
client-labels:
- explicit-yaml
connection-strategy:
connection-retry:
cluster-connect-timeout-millis: 60000
network:
cluster-members:
- ${address}

View File

@@ -0,0 +1,9 @@
hazelcast-client:
client-labels:
- explicit-yml
connection-strategy:
connection-retry:
cluster-connect-timeout-millis: 60000
network:
cluster-members:
- ${address}

View File

@@ -0,0 +1,19 @@
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-5.0.xsd"
xmlns="http://www.hazelcast.com/schema/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<queue name="foobar"/>
<map name="foobar">
<time-to-live-seconds>3600</time-to-live-seconds>
<max-idle-seconds>600</max-idle-seconds>
</map>
<network>
<join>
<auto-detection enabled="false" />
<multicast enabled="false"/>
</join>
</network>
</hazelcast>

View File

@@ -0,0 +1,12 @@
hazelcast:
network:
join:
auto-detection:
enabled: false
multicast:
enabled: false
map:
foobar:
time-to-live-seconds: 3600
max-idle-seconds: 600

View File

@@ -0,0 +1,12 @@
hazelcast:
network:
join:
auto-detection:
enabled: false
multicast:
enabled: false
map:
foobar:
time-to-live-seconds: 3600
max-idle-seconds: 600