Skip to content

Commit 90f69d9

Browse files
authored
Meta config can now use config sources provided via service registry (#10842)
* Meta config can now use config sources provided via service registry (Helidon declarative). * A workaround for test profile config source in testing extension. * Docs update
1 parent c198312 commit 90f69d9

File tree

19 files changed

+722
-77
lines changed

19 files changed

+722
-77
lines changed

config/config/src/main/java/io/helidon/config/ConfigProvider.java

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,37 +53,32 @@ class ConfigProvider implements Supplier<Config> {
5353
Supplier<List<ConfigMapperProvider>> configMappers) {
5454
if (io.helidon.common.config.GlobalConfig.configured()) {
5555
config = wrapCommon(io.helidon.common.config.GlobalConfig.config());
56-
} else {
57-
Optional<MetaConfig> metaConfig = metaConfigSupplier.get();
58-
config = Config.builder()
59-
.update(it -> metaConfig.ifPresent(metaConfigInstance ->
60-
it.config(metaConfigInstance.metaConfiguration())))
61-
.update(it -> configSources.get()
62-
.forEach(it::addSource))
63-
.update(it -> {
64-
if (metaConfig.isEmpty()) {
65-
defaultConfigSources(it, configParsers);
66-
}
67-
})
68-
.disableParserServices()
69-
.update(it -> configParsers.get()
70-
.forEach(it::addParser))
71-
.disableFilterServices()
72-
.update(it -> configFilters.get()
73-
.forEach(it::addFilter))
74-
//.disableMapperServices()
75-
// cannot do this for now, removed ConfigMapperProvider from service loaded services, config does it on its
76-
// own
77-
// ObjectConfigMapper is before EnumMapper, and both are before essential and built-in
78-
.update(it -> configMappers.get()
79-
.forEach(it::addMapper))
80-
.build();
56+
return;
8157
}
82-
}
58+
Optional<MetaConfig> metaConfig = metaConfigSupplier.get();
59+
Config.Builder builder = Config.builder();
8360

84-
@Override
85-
public Config get() {
86-
return config;
61+
if (metaConfig.isPresent()) {
62+
builder.config(metaConfig.get().metaConfiguration());
63+
} else {
64+
builder.update(it -> configSources.get()
65+
.forEach(it::addSource))
66+
.update(it -> defaultConfigSources(it, configParsers));
67+
}
68+
builder.disableParserServices()
69+
.update(it -> configParsers.get()
70+
.forEach(it::addParser))
71+
.disableFilterServices()
72+
.update(it -> configFilters.get()
73+
.forEach(it::addFilter))
74+
//.disableMapperServices()
75+
// cannot do this for now, removed ConfigMapperProvider from service loaded services, config does it on its
76+
// own
77+
// ObjectConfigMapper is before EnumMapper, and both are before essential and built-in
78+
.update(it -> configMappers.get()
79+
.forEach(it::addMapper));
80+
81+
this.config = builder.build();
8782
}
8883

8984
static Config wrapCommon(io.helidon.common.config.Config config) {
@@ -93,6 +88,11 @@ static Config wrapCommon(io.helidon.common.config.Config config) {
9388
return new CommonConfigWrapper(Config.empty(), config);
9489
}
9590

91+
@Override
92+
public Config get() {
93+
return config;
94+
}
95+
9696
private void defaultConfigSources(io.helidon.config.Config.Builder configBuilder,
9797
Supplier<List<ConfigParser>> configParsers) {
9898

config/config/src/main/java/io/helidon/config/MetaConfig.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2019, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -89,8 +89,8 @@ public final class MetaConfig {
8989

9090
private final Config metaConfig;
9191

92-
MetaConfig() {
93-
this.metaConfig = metaConfig().orElseGet(Config::empty);
92+
MetaConfig(Config metaConfig) {
93+
this.metaConfig = metaConfig;
9494
}
9595

9696
/**

config/config/src/main/java/io/helidon/config/MetaConfigFactory.java

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2024, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,19 +16,73 @@
1616

1717
package io.helidon.config;
1818

19-
import java.util.Optional;
20-
import java.util.function.Supplier;
19+
import java.lang.System.Logger.Level;
20+
import java.util.ArrayList;
21+
import java.util.HashMap;
22+
import java.util.List;
23+
import java.util.Map;
2124

25+
import io.helidon.service.registry.Qualifier;
2226
import io.helidon.service.registry.Service;
2327

2428
/**
2529
* A ServiceRegistry factory that creates the meta config only if it is defined.
2630
*/
2731
@Service.Singleton
28-
class MetaConfigFactory implements Supplier<Optional<MetaConfig>> {
32+
class MetaConfigFactory implements Service.ServicesFactory<MetaConfig> {
33+
private static final System.Logger LOGGER = System.getLogger(MetaConfigFactory.class.getName());
34+
2935
@Override
30-
public Optional<MetaConfig> get() {
31-
return MetaConfig.metaConfig()
32-
.map(it -> new MetaConfig());
36+
public List<Service.QualifiedInstance<MetaConfig>> services() {
37+
var foundMetaConfig = MetaConfig.metaConfig();
38+
if (foundMetaConfig.isEmpty()) {
39+
return List.of();
40+
}
41+
42+
var metaConfig = foundMetaConfig.get();
43+
44+
List<Service.QualifiedInstance<MetaConfig>> instances = new ArrayList<>();
45+
46+
// add the main meta config
47+
instances.add(Service.QualifiedInstance.create(new MetaConfig(metaConfig)));
48+
49+
// now for each config source type defined add a named instance
50+
var sources = metaConfig.get("sources")
51+
.asNodeList()
52+
.orElse(List.of());
53+
54+
Map<String, List<NameAndConfig>> byType = new HashMap<>();
55+
for (Config source : sources) {
56+
String type = source.get("type").asString()
57+
.orElseThrow(() -> new ConfigException("Missing type of a config source in config-profile sources."));
58+
String name = source.get("name").asString().orElse(type);
59+
byType.computeIfAbsent(type, k -> new ArrayList<>()).add(new NameAndConfig(name, source.get("properties")));
60+
}
61+
62+
byType.forEach((type, configs) -> {
63+
if (configs.size() == 1) {
64+
// only add if exactly one for this type, otherwise we cannot create an instance from registry
65+
instances.add(Service.QualifiedInstance.create(new MetaConfig(configs.getFirst().config()),
66+
Qualifier.createNamed(type)));
67+
if (LOGGER.isLoggable(Level.DEBUG)) {
68+
LOGGER.log(Level.DEBUG,
69+
"Adding config source meta-configuration for type {0} to service registry.",
70+
type);
71+
}
72+
} else {
73+
if (LOGGER.isLoggable(Level.DEBUG)) {
74+
LOGGER.log(Level.DEBUG,
75+
"There are {0} sources of type {1} defined in config-profile sources, "
76+
+ "but only one is allowed for service registry. Ignoring this type",
77+
configs.size(), type);
78+
}
79+
}
80+
});
81+
82+
return instances;
83+
}
84+
85+
private record NameAndConfig(String name, Config config) {
86+
3387
}
3488
}

config/config/src/main/java/io/helidon/config/MetaConfigFinder.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2019, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,6 +34,9 @@
3434
import io.helidon.config.spi.ConfigNode.ListNode;
3535
import io.helidon.config.spi.ConfigNode.ObjectNode;
3636
import io.helidon.config.spi.ConfigSource;
37+
import io.helidon.service.registry.GlobalServiceRegistry;
38+
import io.helidon.service.registry.Service;
39+
import io.helidon.service.registry.ServiceInfo;
3740

3841
/**
3942
* Utility class that locates the meta configuration source.
@@ -220,6 +223,25 @@ private static ConfigSource profileSource(Function<MediaType, Boolean> supported
220223
sourceListBuilder.addObject(ObjectNode.builder().addValue("type", "environment-variables").build())
221224
.addObject(ObjectNode.builder().addValue("type", "system-properties").build());
222225

226+
// all types from service registry
227+
Set<String> namedSources = GlobalServiceRegistry.registry()
228+
.allServices(ConfigSource.class)
229+
.stream()
230+
.map(ServiceInfo::qualifiers)
231+
.flatMap(Set::stream)
232+
.filter(it -> it.typeName().equals(Service.Named.TYPE))
233+
.flatMap(it -> it.value().stream())
234+
.collect(Collectors.toSet());
235+
236+
for (String namedSource : namedSources) {
237+
sourceListBuilder.addObject(ObjectNode.builder()
238+
.addValue("type", namedSource)
239+
.addObject("properties", ObjectNode.builder()
240+
.addValue("enabled", "false") // this is a default, use an explicit one
241+
.build())
242+
.build());
243+
}
244+
223245
// all profile files
224246
for (String supportedSuffix : supportedSuffixes) {
225247
addFile(sourceListBuilder, "application-" + profileName, supportedSuffix);

config/config/src/main/java/io/helidon/config/MetaProviders.java

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2019, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,13 +15,16 @@
1515
*/
1616
package io.helidon.config;
1717

18+
import java.util.ArrayList;
1819
import java.util.HashMap;
1920
import java.util.HashSet;
2021
import java.util.List;
2122
import java.util.Map;
23+
import java.util.Optional;
2224
import java.util.ServiceLoader;
2325
import java.util.Set;
2426
import java.util.function.Function;
27+
import java.util.stream.Collectors;
2528

2629
import io.helidon.common.HelidonServiceLoader;
2730
import io.helidon.common.Weight;
@@ -35,34 +38,23 @@
3538
import io.helidon.config.spi.PollingStrategyProvider;
3639
import io.helidon.config.spi.RetryPolicy;
3740
import io.helidon.config.spi.RetryPolicyProvider;
41+
import io.helidon.service.registry.Services;
3842

3943
/**
4044
* Access to Java service loaders for config sources, retry policies and polling strategies.
4145
*/
4246
final class MetaProviders {
43-
private static final List<ConfigSourceProvider> CONFIG_SOURCE_PROVIDERS;
4447
private static final List<RetryPolicyProvider> RETRY_POLICY_PROVIDERS;
4548
private static final List<PollingStrategyProvider> POLLING_STRATEGY_PROVIDERS;
4649
private static final List<ChangeWatcherProvider> CHANGE_WATCHER_PROVIDERS;
4750
private static final List<OverrideSourceProvider> OVERRIDE_SOURCE_PROVIDERS;
4851

49-
private static final Set<String> SUPPORTED_CONFIG_SOURCES = new HashSet<>();
5052
private static final Set<String> SUPPORTED_RETRY_POLICIES = new HashSet<>();
5153
private static final Set<String> SUPPORTED_POLLING_STRATEGIES = new HashSet<>();
5254
private static final Set<String> SUPPORTED_CHANGE_WATCHERS = new HashSet<>();
5355
private static final Set<String> SUPPORTED_OVERRIDE_SOURCES = new HashSet<>();
5456

5557
static {
56-
CONFIG_SOURCE_PROVIDERS = HelidonServiceLoader
57-
.builder(ServiceLoader.load(ConfigSourceProvider.class))
58-
.addService(new BuiltInConfigSourcesProvider())
59-
.build()
60-
.asList();
61-
62-
CONFIG_SOURCE_PROVIDERS.stream()
63-
.map(ConfigSourceProvider::supported)
64-
.forEach(SUPPORTED_CONFIG_SOURCES::addAll);
65-
6658
RETRY_POLICY_PROVIDERS = HelidonServiceLoader
6759
.builder(ServiceLoader.load(RetryPolicyProvider.class))
6860
.addService(new BuiltInRetryPolicyProvider())
@@ -93,7 +85,6 @@ final class MetaProviders {
9385
.map(ChangeWatcherProvider::supported)
9486
.forEach(SUPPORTED_CHANGE_WATCHERS::addAll);
9587

96-
9788
OVERRIDE_SOURCE_PROVIDERS = HelidonServiceLoader
9889
.builder(ServiceLoader.load(OverrideSourceProvider.class))
9990
.addService(new BuiltinOverrideSourceProvider())
@@ -108,22 +99,43 @@ final class MetaProviders {
10899
private MetaProviders() {
109100
}
110101

102+
public static ChangeWatcher<?> changeWatcher(String type, Config config) {
103+
return CHANGE_WATCHER_PROVIDERS.stream()
104+
.filter(provider -> provider.supports(type))
105+
.findFirst()
106+
.map(provider -> provider.create(type, config))
107+
.orElseThrow(() -> new MetaConfigException("Change watcher of type " + type + " is not supported."
108+
+ " Supported types: " + SUPPORTED_CHANGE_WATCHERS));
109+
}
110+
111111
static ConfigSource configSource(String type, Config config) {
112-
return CONFIG_SOURCE_PROVIDERS.stream()
112+
var all = allProviders();
113+
var supported = all.stream()
114+
.map(ConfigSourceProvider::supported)
115+
.flatMap(Set::stream)
116+
.collect(Collectors.toUnmodifiableSet());
117+
118+
return all.stream()
113119
.filter(provider -> provider.supports(type))
114120
.findFirst()
115121
.map(provider -> provider.create(type, config))
122+
.or(() -> findSourceFromRegistry(type))
116123
.orElseThrow(() -> new MetaConfigException("Config source of type " + type + " is not supported."
117-
+ " Supported types: " + SUPPORTED_CONFIG_SOURCES));
124+
+ " Supported types: " + supported));
118125
}
119126

120127
static List<ConfigSource> configSources(String type, Config sourceProperties) {
121-
return CONFIG_SOURCE_PROVIDERS.stream()
128+
var all = allProviders();
129+
var supported = all.stream()
130+
.map(ConfigSourceProvider::supported)
131+
.flatMap(Set::stream)
132+
.collect(Collectors.toUnmodifiableSet());
133+
return all.stream()
122134
.filter(provider -> provider.supports(type))
123135
.findFirst()
124136
.map(provider -> provider.createMulti(type, sourceProperties))
125137
.orElseThrow(() -> new MetaConfigException("Config source of type " + type + " is not supported."
126-
+ " Supported types: " + SUPPORTED_CONFIG_SOURCES));
138+
+ " Supported types: " + supported));
127139
}
128140

129141
static OverrideSource overrideSource(String type, Config config) {
@@ -132,7 +144,7 @@ static OverrideSource overrideSource(String type, Config config) {
132144
.findFirst()
133145
.map(provider -> provider.create(type, config))
134146
.orElseThrow(() -> new MetaConfigException("Config source of type " + type + " is not supported."
135-
+ " Supported types: " + SUPPORTED_OVERRIDE_SOURCES));
147+
+ " Supported types: " + SUPPORTED_OVERRIDE_SOURCES));
136148
}
137149

138150
static PollingStrategy pollingStrategy(String type, Config config) {
@@ -141,7 +153,7 @@ static PollingStrategy pollingStrategy(String type, Config config) {
141153
.findFirst()
142154
.map(provider -> provider.create(type, config))
143155
.orElseThrow(() -> new MetaConfigException("Polling strategy of type " + type + " is not supported."
144-
+ " Supported types: " + SUPPORTED_POLLING_STRATEGIES));
156+
+ " Supported types: " + SUPPORTED_POLLING_STRATEGIES));
145157
}
146158

147159
static RetryPolicy retryPolicy(String type, Config config) {
@@ -150,16 +162,17 @@ static RetryPolicy retryPolicy(String type, Config config) {
150162
.findFirst()
151163
.map(provider -> provider.create(type, config))
152164
.orElseThrow(() -> new MetaConfigException("Retry policy of type " + type + " is not supported."
153-
+ " Supported types: " + SUPPORTED_RETRY_POLICIES));
165+
+ " Supported types: " + SUPPORTED_RETRY_POLICIES));
154166
}
155167

156-
public static ChangeWatcher<?> changeWatcher(String type, Config config) {
157-
return CHANGE_WATCHER_PROVIDERS.stream()
158-
.filter(provider -> provider.supports(type))
159-
.findFirst()
160-
.map(provider -> provider.create(type, config))
161-
.orElseThrow(() -> new MetaConfigException("Change watcher of type " + type + " is not supported."
162-
+ " Supported types: " + SUPPORTED_CHANGE_WATCHERS));
168+
private static Optional<ConfigSource> findSourceFromRegistry(String type) {
169+
return Services.firstNamed(ConfigSource.class, type);
170+
}
171+
172+
private static List<ConfigSourceProvider> allProviders() {
173+
var result = new ArrayList<>(Services.all(ConfigSourceProvider.class));
174+
result.add(new BuiltInConfigSourcesProvider());
175+
return result;
163176
}
164177

165178
@Weight(0)

config/config/src/main/resources/META-INF/helidon/io.helidon.config/service.loader

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ io.helidon.config.spi.ConfigFilter
44
# This cannot be done for now, as ObjectConfigMapper ends up before built-ins when
55
# we disable mapper services
66
# io.helidon.config.spi.ConfigMapperProvider
7+
io.helidon.config.spi.ConfigSourceProvider

config/tests/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
<module>test-meta-source-fail</module>
6363
<module>test-parsers-1-complex</module>
6464
<module>service-registry</module>
65+
<module>service-registry-metaconfig</module>
6566
<module>config-metadata-meta-api</module>
6667
<module>config-metadata-builder-api</module>
6768
<module>test-lazy-source</module>

0 commit comments

Comments
 (0)