Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,31 @@ public class NacosPropertySource extends MapPropertySource {
*/
private final boolean isRefreshable;

/**
* File extension (suffix) of the config data (e.g., "properties", "yml", "json").
*/
private final String suffix;

NacosPropertySource(String group, String dataId, Map<String, Object> source,
Date timestamp, boolean isRefreshable) {
Date timestamp, boolean isRefreshable, String suffix) {
super(String.join(NacosConfigProperties.COMMAS, dataId, group), source);
this.group = group;
this.dataId = dataId;
this.timestamp = timestamp;
this.isRefreshable = isRefreshable;
this.suffix = suffix;
}

public NacosPropertySource(List<PropertySource<?>> propertySources, String group,
String dataId, Date timestamp, boolean isRefreshable) {
this(group, dataId, getSourceMap(group, dataId, propertySources), timestamp,
isRefreshable);
isRefreshable, "properties");
}

public NacosPropertySource(List<PropertySource<?>> propertySources, String group,
String dataId, Date timestamp, boolean isRefreshable, String suffix) {
this(group, dataId, getSourceMap(group, dataId, propertySources), timestamp,
isRefreshable, suffix);
}

private static Map<String, Object> getSourceMap(String group, String dataId,
Expand Down Expand Up @@ -129,4 +141,8 @@ public boolean isRefreshable() {
return isRefreshable;
}

public String getSuffix() {
return suffix;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public NacosPropertySource build(String dataId, String group, String fileExtensi
List<PropertySource<?>> propertySources = loadNacosData(dataId, group,
fileExtension);
NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
group, dataId, new Date(), isRefreshable);
group, dataId, new Date(), isRefreshable, fileExtension);
NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
return nacosPropertySource;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public ConfigData doLoad(ConfigDataLoaderContext context,

NacosPropertySource propertySource = new NacosPropertySource(propertySources,
config.getGroup(), config.getDataId(), new Date(),
config.isRefreshEnabled());
config.isRefreshEnabled(), config.getSuffix());

NacosPropertySourceRepository.collectNacosPropertySource(propertySource);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,32 @@ public void handle(NacosConfigRefreshEvent event) {

NacosPropertySourceBuilder nacosPropertySourceBuilder = new NacosPropertySourceBuilder(nacosConfigManager.getConfigService(), nacosConfigManager.getNacosConfigProperties()
.getTimeout());
String sourceName = String.join(NacosConfigProperties.COMMAS, event.dataId, event.group);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the fix in #4341, it looks like the naming mismatch issue should no longer occur.

// Try both naming conventions: bootstrap path (dataId,group) and ConfigData path (group@dataId)
String bootstrapName = String.join(NacosConfigProperties.COMMAS, event.dataId, event.group);
String configDataName = event.group + "@" + event.dataId;

ConfigurableEnvironment environment = ((ConfigurableApplicationContext) applicationContext).getEnvironment();
MutablePropertySources target = environment.getPropertySources();
PropertySource<?> prevpropertySource = target.get(sourceName);

PropertySource<?> prevpropertySource = target.get(bootstrapName);
String sourceName = bootstrapName;

// If not found with bootstrap naming, try ConfigData naming
if (prevpropertySource == null) {
prevpropertySource = target.get(configDataName);
sourceName = configDataName;
}

if (prevpropertySource instanceof NacosPropertySource) {
NacosPropertySource newProperSource = nacosPropertySourceBuilder.build(event.getDataId(), event.getGroup(), "properties", ((NacosPropertySource) prevpropertySource).isRefreshable());
NacosPropertySource prevNacosSource = (NacosPropertySource) prevpropertySource;
// Use the actual suffix from the previous source instead of hardcoding "properties"
String fileExtension = prevNacosSource.getSuffix();
if (fileExtension == null || fileExtension.isEmpty()) {
fileExtension = "properties";
}
NacosPropertySource newProperSource = nacosPropertySourceBuilder.build(
event.getDataId(), event.getGroup(), fileExtension, prevNacosSource.isRefreshable());
target.replace(sourceName, newProperSource);
log.info("Replace Nacos Property Source : " + sourceName);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright 2013-2023 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 com.alibaba.cloud.nacos.refresh;

import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.cloud.nacos.client.NacosPropertySource;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
* Test for {@link NacosPropertySourceRefreshListener}.
*
* @author wushiyuan
*/
public class NacosPropertySourceRefreshListenerTest {

private NacosPropertySourceRefreshListener listener;
private ConfigurableApplicationContext applicationContext;
private ConfigurableEnvironment environment;
private MutablePropertySources propertySources;
private NacosConfigManager nacosConfigManager;
private ConfigService configService;

@BeforeEach
public void setUp() throws NacosException {
nacosConfigManager = mock(NacosConfigManager.class);
configService = mock(ConfigService.class);
NacosConfigProperties properties = new NacosConfigProperties();
properties.setTimeout(3000);

when(nacosConfigManager.getConfigService()).thenReturn(configService);
when(nacosConfigManager.getNacosConfigProperties()).thenReturn(properties);

applicationContext = mock(ConfigurableApplicationContext.class);
environment = mock(ConfigurableEnvironment.class);
propertySources = new MutablePropertySources();

when(applicationContext.getEnvironment()).thenReturn(environment);
when(environment.getPropertySources()).thenReturn(propertySources);
when(applicationContext.containsBean("nacosConfigSpringCloudRefreshEventListener")).thenReturn(false);

listener = new NacosPropertySourceRefreshListener(nacosConfigManager);
listener.setApplicationContext(applicationContext);
}

/**
* Test refresh with ConfigData path naming (group@dataId).
* This test verifies that the listener can locate a NacosPropertySource
* registered under the ConfigData naming convention and refresh it.
*/
@Test
public void testRefreshWithConfigDataPathNaming() throws NacosException {
// Given: a property source with ConfigData path naming (group@dataId)
String group = "DEFAULT_GROUP";
String dataId = "test-config.yml";
String configDataName = group + "@" + dataId; // ConfigData path uses group@dataId

Map<String, Object> initialData = new HashMap<>();
initialData.put("app.name", "old-value");
MapPropertySource innerSource = new MapPropertySource(configDataName, initialData);
NacosPropertySource nacosPropertySource = new NacosPropertySource(
Collections.singletonList(innerSource), group, dataId, new Date(), true, "yml");

propertySources.addLast(nacosPropertySource);

// Mark app as ready
listener.handle(mock(ApplicationReadyEvent.class));

// When: config changes in Nacos
String newConfig = "app:\n name: new-value";
when(configService.getConfig(dataId, group, 3000L)).thenReturn(newConfig);

NacosConfigRefreshEvent event = new NacosConfigRefreshEvent(this, null, "test refresh");
event.setDataId(dataId);
event.setGroup(group);

listener.handle(event);

// Then: the listener should have found the source via configDataName fallback
// and replaced it. After replacement, the new NacosPropertySource uses
// the standard "dataId,group" naming from its constructor.
String standardName = dataId + "," + group;
assertThat(propertySources.contains(standardName)).isTrue();
Object updatedValue = propertySources.get(standardName).getProperty("app.name");
assertThat(updatedValue).isEqualTo("new-value");
}

/**
* Test refresh with yml file extension.
* This test verifies that the listener uses the actual file extension
* from the existing NacosPropertySource instead of hardcoding "properties".
*/
@Test
public void testRefreshWithYmlExtension() throws NacosException {
// Given: a yml config
String group = "DEFAULT_GROUP";
String dataId = "test-config.yml";
String sourceName = dataId + "," + group; // bootstrap path naming

Map<String, Object> initialData = new HashMap<>();
initialData.put("app.port", "8080");
MapPropertySource innerSource = new MapPropertySource(sourceName, initialData);
NacosPropertySource nacosPropertySource = new NacosPropertySource(
Collections.singletonList(innerSource), group, dataId, new Date(), true, "yml");

propertySources.addLast(nacosPropertySource);

// Mark app as ready
listener.handle(mock(ApplicationReadyEvent.class));

// When: yml config changes in Nacos
String newYmlConfig = "app:\n port: 9090";
when(configService.getConfig(dataId, group, 3000L)).thenReturn(newYmlConfig);

NacosConfigRefreshEvent event = new NacosConfigRefreshEvent(this, null, "test refresh");
event.setDataId(dataId);
event.setGroup(group);

listener.handle(event);

// Then: the yml should be parsed correctly (not as properties)
assertThat(propertySources.contains(sourceName)).isTrue();
Object updatedValue = propertySources.get(sourceName).getProperty("app.port");
// YAML parser returns Integer for numeric values
assertThat(String.valueOf(updatedValue)).isEqualTo("9090");
}
}
Loading