Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Config with injection #7080

Merged
merged 5 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions common/config/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
<name>Helidon Common Config</name>

<dependencies>
<dependency>
<groupId>io.helidon.common</groupId>
<artifactId>helidon-common</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand Down
103 changes: 103 additions & 0 deletions common/config/src/main/java/io/helidon/common/config/GlobalConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* 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 io.helidon.common.config;

import java.util.List;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

import io.helidon.common.HelidonServiceLoader;
import io.helidon.common.LazyValue;
import io.helidon.common.config.spi.ConfigProvider;

/**
* Global configuration can be set by a user before any Helidon code is invoked, to override default discovery
* of configuration done by Helidon components.
* <p>
* If method {@link #config(java.util.function.Supplier)} is called before Helidon is started, Helidon will only use that
* configuration.
* <p>
* You may still use custom instances of configuration when using configurable APIs directly.
*/
public final class GlobalConfig {
private static final Config EMPTY = Config.empty();
private static final LazyValue<Config> DEFAULT_CONFIG = LazyValue.create(() -> {
List<ConfigProvider> providers = HelidonServiceLoader.create(ServiceLoader.load(ConfigProvider.class))
.asList();
// no implementations available, use empty configuration
if (providers.isEmpty()) {
return EMPTY;
}
// there is a valid provider, let's use its default configuration
return providers.get(0)
.create();
});
private static final AtomicReference<Config> CONFIG = new AtomicReference<>();

private GlobalConfig() {
}

/**
* Whether a global configuration has already been configured.
*
* @return {@code true} if there is a global configuration set already, {@code false} otherwise
*/
public static boolean configured() {
return CONFIG.get() != null;
}

/**
* Global configuration instance.
*
* @return Helidon shared configuration instance if configured, or an empty configuration if not
* @see #config(java.util.function.Supplier)
* @see #config(java.util.function.Supplier, boolean)
*/
public static Config config() {
return configured() ? CONFIG.get() : DEFAULT_CONFIG.get();
}

/**
* Set global configuration if not yet configured.
*
* @param config configuration supplier to use if config is not yet configured
* @return used global configuration instance
*/
public static Config config(Supplier<Config> config) {
return config(config, false);
}

/**
* Set global configuration.
*
* @param config configuration to use
* @param overwrite whether to overwrite an existing configured value
* @return current global config
*/
public static Config config(Supplier<Config> config, boolean overwrite) {
Objects.requireNonNull(config);

if (overwrite || !configured()) {
// there is a certain risk we may do this twice, if two components try to set global config in parallel.
// as the result was already unclear (as order matters), we do not need to be 100% thread safe here
CONFIG.set(config.get());
}
return CONFIG.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* 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 io.helidon.common.config.spi;

import io.helidon.common.config.Config;

/**
* Service loader provider interface to discover config implementation that would be used to
* obtain a default configuration instance.
*/
public interface ConfigProvider {
/**
* Create the default configuration instance.
*
* @return a new configuration
*/
Config create();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* 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.
*/

/**
* SPI to load an implementation to be able to discover the default configuration of current environment.
*/
package io.helidon.common.config.spi;
7 changes: 6 additions & 1 deletion common/config/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,5 +18,10 @@
* Helidon Common Config Library.
*/
module io.helidon.common.config {
requires io.helidon.common;

exports io.helidon.common.config;
exports io.helidon.common.config.spi;

uses io.helidon.common.config.spi.ConfigProvider;
}
54 changes: 50 additions & 4 deletions config/config/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,17 @@
<artifactId>helidon-logging-jul</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.common.features</groupId>
<artifactId>helidon-common-features-api</artifactId>
<groupId>io.helidon.pico</groupId>
<artifactId>helidon-pico-api</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.pico</groupId>
<artifactId>helidon-pico-runtime</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<!-- this is required to add the processor to reactor at the right time -->
<groupId>io.helidon.common.features</groupId>
<artifactId>helidon-common-features-processor</artifactId>
<artifactId>helidon-common-features-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
Expand Down Expand Up @@ -108,6 +111,49 @@

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.helidon.common.features</groupId>
<artifactId>helidon-common-features-processor</artifactId>
<version>${helidon.version}</version>
</path>
<path>
<groupId>io.helidon.builder</groupId>
<artifactId>helidon-builder-processor</artifactId>
<version>${helidon.version}</version>
</path>
<path>
<groupId>io.helidon.pico</groupId>
<artifactId>helidon-pico-processor</artifactId>
<version>${helidon.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
<dependencies>
<dependency>
<!-- this is required to add the processor to reactor at the right time -->
<groupId>io.helidon.common.features</groupId>
<artifactId>helidon-common-features-processor</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<!-- this is required to add the processor to reactor at the right time -->
<groupId>io.helidon.pico</groupId>
<artifactId>helidon-pico-processor</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<!-- this is required to add the processor to reactor at the right time -->
<groupId>io.helidon.builder</groupId>
<artifactId>helidon-builder-processor</artifactId>
<version>${helidon.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
Expand Down
127 changes: 127 additions & 0 deletions config/config/src/main/java/io/helidon/config/ConfigProducer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* 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 io.helidon.config;

import java.util.List;
import java.util.Map;
import java.util.function.Function;

import io.helidon.common.config.Config;
import io.helidon.common.config.ConfigException;
import io.helidon.common.config.ConfigValue;
import io.helidon.common.config.GlobalConfig;
import io.helidon.config.spi.ConfigSource;
import io.helidon.pico.api.ExternalContracts;
import io.helidon.pico.api.PicoServices;
import io.helidon.pico.api.ServiceInfoCriteria;
import io.helidon.pico.api.ServiceProvider;

import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.inject.Singleton;

@Singleton
@ExternalContracts(Config.class)
class ConfigProducer implements Config {
private final Config config;

@Inject
ConfigProducer() {
// this should be moved to constructor injection when we support zero length lists for injection
List<ServiceProvider<?>> serviceProviders = PicoServices.realizedServices()
.lookupAll(ServiceInfoCriteria
.builder()
.addContractImplemented(ConfigSource.class).build(), false);

if (GlobalConfig.configured()) {
config = GlobalConfig.config();
} else {
config = io.helidon.config.Config.builder()
.metaConfig()
.update(it -> serviceProviders.stream()
.map(Provider::get)
.map(ConfigSource.class::cast)
.forEach(it::addSource))
.build();
}
}

@Override
public Key key() {
return config.key();
}

@Override
public Config get(String key) throws ConfigException {
return config.get(key);
}

@Override
public Config detach() throws ConfigException {
return config.detach();
}

@Override
public boolean exists() {
return config.exists();
}

@Override
public boolean isLeaf() {
return config.isLeaf();
}

@Override
public boolean isObject() {
return config.isObject();
}

@Override
public boolean isList() {
return config.isList();
}

@Override
public boolean hasValue() {
return config.hasValue();
}

@Override
public <T> ConfigValue<T> as(Class<T> type) {
return config.as(type);
}

@Override
public <T> ConfigValue<T> map(Function<Config, T> mapper) {
return config.map(mapper);
}

@Override
public <T> ConfigValue<List<T>> asList(Class<T> type) throws ConfigException {
return config.asList(type);
}

@Override
public <C extends Config> ConfigValue<List<C>> asNodeList() throws ConfigException {
return config.asNodeList();
}

@Override
public ConfigValue<Map<String, String>> asMap() throws ConfigException {
return config.asMap();
}
}