PlatformConfigNamedProvider.java
/**
* Copyright (c) 2020, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* SPDX-License-Identifier: MPL-2.0
*/
package com.powsybl.commons.config;
import com.google.common.collect.Lists;
import com.powsybl.commons.PowsyblException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
/**
* A provider that can be loaded by by Java's ServiceLoader based on its name
* present in an entry in the PlatformConfig.
*
* @author Jon Harper {@literal <jon.harper at rte-france.com>}
*/
public interface PlatformConfigNamedProvider {
/**
* Get the name.
*
* @return the name
*/
String getName();
/**
* Get the Provider name used for identifying this provider in the
* PlatformConfig. Defaults to getName(). Override this method only if getName() is
* already implemented and returns the wrong name.
*
* @return the name
*/
default String getPlatformConfigName() {
return getName();
}
/**
* A utility class to find providers in the {@link PlatformConfig} by their
* names configured in standard fields. the find* methods use the standard
* fields while the find*BackwardsCompatible methods also look in the legacy
* fields.
*
* @author Jon harper {@literal <jon.harper at rte-france.com>}
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
*/
final class Finder {
private Finder() {
}
private static final String DEFAULT_SERVICE_IMPL_NAME_PROPERTY = "default-impl-name";
private static final Map<Class<? extends PlatformConfigNamedProvider>, List<? extends PlatformConfigNamedProvider>> PROVIDERS = new ConcurrentHashMap<>();
/**
* Find the default provider configured in the standard field of
* {@code moduleName} in {@code platformConfig} among the {@code providers}
* arguments based on its name.
*
* @return the provider
*/
public static <T extends PlatformConfigNamedProvider> T findDefault(String moduleName,
Class<T> clazz, PlatformConfig platformConfig) {
return find(null, moduleName,
List.of(DEFAULT_SERVICE_IMPL_NAME_PROPERTY), clazz,
platformConfig);
}
/**
* Find the provider among the {@code providers} based on its {@code name}, or
* if {@code name} is null find the default provider like @{link findDefault}
*
* @return the provider
*/
public static <T extends PlatformConfigNamedProvider> T find(String name, String moduleName,
Class<T> clazz, PlatformConfig platformConfig) {
return find(name, moduleName,
List.of(DEFAULT_SERVICE_IMPL_NAME_PROPERTY), clazz,
platformConfig);
}
private static Optional<String> getOptionalFirstProperty(ModuleConfig moduleConfig,
List<String> propertyNames) {
return propertyNames.stream()
.map(moduleConfig::getOptionalStringProperty)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
}
@SuppressWarnings("unchecked")
private static <K, V, T extends V> T alwaysSameComputeIfAbsent(
Map<K, V> map, K key,
Function<? super K, T> mappingFunction) {
// Casting to (T) is safe if we awlays pass the same T argument for a given key
return (T) map.computeIfAbsent(key, mappingFunction);
}
private static <T extends PlatformConfigNamedProvider> T find(String name,
String moduleName, List<String> propertyNames, Class<T> clazz,
PlatformConfig platformConfig) {
List<T> providers = alwaysSameComputeIfAbsent(PROVIDERS, clazz,
k -> Lists.newArrayList(ServiceLoader.load(clazz, PlatformConfigNamedProvider.class.getClassLoader())));
return find(name, moduleName, propertyNames, providers, platformConfig, clazz);
}
// package private for tests
static <T extends PlatformConfigNamedProvider> T find(String name,
String moduleName, List<String> propertyNames, List<T> providers,
PlatformConfig platformConfig, Class<T> clazz) {
Objects.requireNonNull(moduleName);
Objects.requireNonNull(propertyNames);
Objects.requireNonNull(providers);
Objects.requireNonNull(platformConfig);
Objects.requireNonNull(clazz);
if (providers.isEmpty()) {
throw new PowsyblException("No " + clazz.getSimpleName() + " providers found");
}
// if no implementation name is provided through the API we look for information
// in platform configuration
String finalName = name != null ? name
: platformConfig.getOptionalModuleConfig(moduleName)
.flatMap(mc -> getOptionalFirstProperty(mc, propertyNames))
.orElse(null);
T provider;
if (providers.size() == 1 && finalName == null) {
// no information to select the implementation but only one provider, so we can
// use it by default (that is the most common use case)
provider = providers.get(0);
} else {
if (providers.size() > 1 && finalName == null) {
// several providers and no information to select which one to choose, we can
// only throw an exception
List<String> providerNames = providers.stream()
.map(PlatformConfigNamedProvider::getPlatformConfigName)
.toList();
throw new PowsyblException(
"Several " + clazz.getSimpleName() + " implementations found (" + providerNames
+ "), you must add configuration in PlatformConfig's module \""
+ moduleName + "\" to select the implementation");
}
provider = providers.stream()
.filter(p -> p.getPlatformConfigName().equals(finalName)).findFirst()
.orElseThrow(() -> new PowsyblException(
clazz.getSimpleName() + " '" + finalName + "' not found"));
}
return provider;
}
}
}