diff --git a/core/common/boot/src/main/java/org/eclipse/edc/boot/system/DependencyGraph.java b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/DependencyGraph.java index 3feae01d26c..61ab48ff0fe 100644 --- a/core/common/boot/src/main/java/org/eclipse/edc/boot/system/DependencyGraph.java +++ b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/DependencyGraph.java @@ -36,7 +36,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -93,7 +92,8 @@ public List> of(List exte var sort = new TopologicalSort>(); - var unsatisfiedInjectionPoints = new ArrayList>(); + // check if all injected fields are satisfied, collect missing ones and throw exception otherwise + var unsatisfiedInjectionPoints = new HashMap, List>(); var unsatisfiedRequirements = new ArrayList(); injectionContainers.forEach(container -> { @@ -109,15 +109,13 @@ public List> of(List exte injectionPointScanner.getInjectionPoints(container.getInjectionTarget()) .peek(injectionPoint -> { - var maybeProviders = Optional.of(injectionPoint.getType()).map(dependencyMap::get); - - if (maybeProviders.isPresent() || context.hasService(injectionPoint.getType())) { - maybeProviders.ifPresent(l -> l.stream() - .filter(d -> !Objects.equals(d, container)) // remove dependencies onto oneself - .forEach(provider -> sort.addDependency(container, provider))); + var providersResult = injectionPoint.getProviders(dependencyMap, context); + if (providersResult.succeeded()) { + List> providers = providersResult.getContent(); + providers.stream().filter(d -> !Objects.equals(d, container)).forEach(provider -> sort.addDependency(container, provider)); } else { if (injectionPoint.isRequired()) { - unsatisfiedInjectionPoints.add(injectionPoint); + unsatisfiedInjectionPoints.computeIfAbsent(injectionPoint.getTargetInstance().getClass(), s -> new ArrayList<>()).add(new InjectionFailure(injectionPoint, providersResult.getFailureDetail())); } } @@ -130,8 +128,9 @@ public List> of(List exte }); if (!unsatisfiedInjectionPoints.isEmpty()) { - var message = "The following injected fields were not provided:\n"; - message += unsatisfiedInjectionPoints.stream().map(InjectionPoint::toString).collect(Collectors.joining("\n")); + var message = "The following injected fields or values were not provided or could not be resolved:\n"; + message += unsatisfiedInjectionPoints.entrySet().stream() + .map(entry -> String.format("%s is missing \n --> %s", entry.getKey(), String.join("\n --> ", entry.getValue().stream().map(Object::toString).toList()))).collect(Collectors.joining("\n")); throw new EdcInjectionException(message); } @@ -169,4 +168,10 @@ private Set> getProvidedFeatures(ServiceExtension ext) { return allProvides; } + private record InjectionFailure(InjectionPoint injectionPoint, String failureDetail) { + @Override + public String toString() { + return "%s %s".formatted(injectionPoint.getTypeString(), failureDetail); + } + } } diff --git a/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/ConfigurationInjectionPoint.java b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/ConfigurationInjectionPoint.java new file mode 100644 index 00000000000..6f61ecf6666 --- /dev/null +++ b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/ConfigurationInjectionPoint.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.boot.system.injection; + +import org.eclipse.edc.boot.system.injection.lifecycle.ServiceProvider; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.result.AbstractResult; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Stream; + +/** + * Injection point for configuration objects. Configuration objects are records or POJOs that contain fields annotated with {@link Setting}. + * Configuration objects themselves must be annotated with {@link org.eclipse.edc.runtime.metamodel.annotation.Settings}. + * Example: + *
+ *      public class SomeExtension implements ServiceExtension {
+ *          \@Settings
+ *          private SomeConfig someConfig;
+ *      }
+ *
+ *      \@Settings
+ *      public record SomeConfig(@Setting(key = "foo.bar.baz") String fooValue){ }
+ * 
+ * + * @param The type of the declaring class. + */ +public class ConfigurationInjectionPoint implements InjectionPoint { + private final T targetInstance; + private final Field configurationObject; + + public ConfigurationInjectionPoint(T instance, Field configurationObject) { + this.targetInstance = instance; + this.configurationObject = configurationObject; + this.configurationObject.setAccessible(true); + + } + + @Override + public T getTargetInstance() { + return targetInstance; + } + + @Override + public Class getType() { + return configurationObject.getType(); + } + + @Override + public boolean isRequired() { + return Arrays.stream(configurationObject.getType().getDeclaredFields()) + .filter(f -> f.getAnnotation(Setting.class) != null) + .anyMatch(f -> f.getAnnotation(Setting.class).required()); + } + + @Override + public Result setTargetValue(Object value) { + try { + configurationObject.set(targetInstance, value); + return Result.success(); + } catch (IllegalAccessException e) { + return Result.failure("Could not assign value '%s' to field '%s'. Reason: %s".formatted(value, configurationObject, e.getMessage())); + } + } + + /** + * Not used here, will always return null + */ + @Override + public ServiceProvider getDefaultServiceProvider() { + return null; + } + + /** + * Not used here + */ + @Override + public void setDefaultServiceProvider(ServiceProvider defaultServiceProvider) { + + } + + @Override + public Object resolve(ServiceExtensionContext context, DefaultServiceSupplier defaultServiceSupplier) { + + // all fields annotated with the @Setting annotation + var settingsFields = resolveSettingsFields(context, configurationObject.getType().getDeclaredFields()); + + // records are treated specially, because they only contain final fields, and must be constructed with a non-default CTOR + // where every constructor arg MUST be named the same as the field value. We can't rely on this with normal classes + if (configurationObject.getType().isRecord()) { + // find matching constructor + var constructor = Stream.of(configurationObject.getType().getDeclaredConstructors()) + .filter(constructorFilter(settingsFields)) + .findFirst() + .orElseThrow(() -> new EdcInjectionException("No suitable constructor found on record class '%s'".formatted(configurationObject.getType()))); + + try { + // invoke CTor with the previously resolved config values + constructor.setAccessible(true); + return constructor.newInstance(settingsFields.stream().map(FieldValue::value).toArray()); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new EdcInjectionException(e); + } + + } else { // all other classes MUST have a default constructor. + try { + var pojoClass = Class.forName(configurationObject.getType().getName()); + var defaultCtor = pojoClass.getDeclaredConstructor(); + defaultCtor.setAccessible(true); + var instance = defaultCtor.newInstance(); + + // set the field values on the newly-constructed object instance + settingsFields.forEach(fe -> { + try { + var field = pojoClass.getDeclaredField(fe.fieldName()); + field.setAccessible(true); + field.set(instance, fe.value()); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new EdcInjectionException(e); + } + }); + + return instance; + } catch (NoSuchMethodException e) { + throw new EdcInjectionException("Configuration objects must declare a default constructor, but '%s' does not.".formatted(configurationObject.getType())); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | + InvocationTargetException e) { + throw new EdcInjectionException(e); + } + } + } + + @Override + public Result>> getProviders(Map, List>> dependencyMap, ServiceExtensionContext context) { + var violators = injectionPointsFrom(configurationObject.getType().getDeclaredFields()) + .map(ip -> ip.getProviders(dependencyMap, context)) + .filter(Result::failed) + .map(AbstractResult::getFailureDetail) + .toList(); + return violators.isEmpty() ? Result.success(List.of()) : Result.failure("%s (%s) --> %s".formatted(configurationObject.getName(), configurationObject.getType().getSimpleName(), violators)); + } + + @Override + public String getTypeString() { + return "Config object"; + } + + @Override + public String toString() { + return "Configuration object '%s' of type '%s' in %s" + .formatted(configurationObject.getName(), configurationObject.getType(), targetInstance.getClass()); + } + + private Predicate> constructorFilter(List args) { + var argNames = args.stream().map(FieldValue::fieldName).toList(); + return ctor -> ctor.getParameterCount() == args.size() && + Arrays.stream(ctor.getParameters()).allMatch(p -> argNames.contains(p.getName())); + + } + + private @NotNull List resolveSettingsFields(ServiceExtensionContext context, Field[] fields) { + return injectionPointsFrom(fields) + .map(ip -> { + var val = ip.resolve(context, null /*the default supplier arg is not used anyway*/); + var fieldName = ip.getTargetField().getName(); + return new FieldValue(fieldName, val); + }) + .toList(); + } + + private @NotNull Stream> injectionPointsFrom(Field[] fields) { + return Arrays.stream(fields) + .filter(f -> f.getAnnotation(Setting.class) != null) + .map(f -> new ValueInjectionPoint<>(null, f, f.getAnnotation(Setting.class), targetInstance.getClass())); + } + + private record FieldValue(String fieldName, Object value) { + } +} diff --git a/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectionContainer.java b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectionContainer.java index 1a90b17530e..db4da9059f7 100644 --- a/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectionContainer.java +++ b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectionContainer.java @@ -22,7 +22,7 @@ /** * Represents one {@link ServiceExtension} with a description of all its auto-injectable fields, which in turn are - * represented by {@link FieldInjectionPoint}s. + * represented by {@link ServiceInjectionPoint}s. */ public class InjectionContainer { private final T injectionTarget; @@ -50,8 +50,8 @@ public List getServiceProviders() { @Override public String toString() { return getClass().getSimpleName() + "{" + - "injectionTarget=" + injectionTarget + - '}'; + "injectionTarget=" + injectionTarget + + '}'; } } diff --git a/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectionPoint.java b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectionPoint.java index 2d61a0d138c..d957a8e46ec 100644 --- a/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectionPoint.java +++ b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectionPoint.java @@ -15,6 +15,12 @@ package org.eclipse.edc.boot.system.injection; import org.eclipse.edc.boot.system.injection.lifecycle.ServiceProvider; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; /** * Represents an auto-injectable property. Possible implementors are field injection points, constructor injection points, etc. @@ -22,15 +28,66 @@ * @param the type of the target object */ public interface InjectionPoint { - T getInstance(); + /** + * The fully constructed object instance that contains the injection point. For example an extension class + */ + T getTargetInstance(); + /** + * The type (=class) of the injected field. For example, this could be the service class for a {@link ServiceInjectionPoint}, or + * a basic datatype for a {@link ValueInjectionPoint}. + */ Class getType(); + /** + * Whether the injection point must be able to be satisfied from the current runtime. In other words, whether the injected field is nullable. + */ boolean isRequired(); - void setTargetValue(Object service) throws IllegalAccessException; + /** + * Assigns the given value to the injected field. + * + * @param value An object instance that is assigned to the injected field. + * @return a successful result if the assignment could be done, a failure result indicating the reason in other cases. + */ + Result setTargetValue(Object value); - ServiceProvider getDefaultServiceProvider(); + /** + * Some injection points such as service injection points may have a default value provider. + * + * @return the default service provider if any, null otherwise + */ + @Nullable ServiceProvider getDefaultServiceProvider(); + /** + * Sets the default service provider. + * + * @param defaultServiceProvider the default service provider + */ void setDefaultServiceProvider(ServiceProvider defaultServiceProvider); + + /** + * Resolves the value for an injected field from either the context or a default service supplier. For some injection points, + * this may also return a (statically declared) default value. + * + * @param context The {@link ServiceExtensionContext} from which the value is resolved. + * @param defaultServiceSupplier Some service dynamically resolve a default value in case the actual value isn't found on the context. + * @return The resolved value, or null if the injected field is not required.. + * @throws EdcInjectionException in case the value could not be resolved + */ + Object resolve(ServiceExtensionContext context, DefaultServiceSupplier defaultServiceSupplier); + + /** + * Determines whether a particular injection can be resolved by a given map of dependencies or the context. + * + * @param dependencyMap a map containing the current dependency list + * @param context the fully constructed {@link ServiceExtensionContext} + * @return successful result containing a (potentially empty) list of injection containers that can provide this service, a failure otherwise. + */ + Result>> getProviders(Map, List>> dependencyMap, ServiceExtensionContext context); + + /** + * A human-readable string indicating the type of injection point, e.g. "Service" or "Config value" + */ + String getTypeString(); } diff --git a/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectionPointScanner.java b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectionPointScanner.java index 9c2c1b3562f..a1c12524ae2 100644 --- a/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectionPointScanner.java +++ b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectionPointScanner.java @@ -14,10 +14,13 @@ package org.eclipse.edc.boot.system.injection; +import org.eclipse.edc.runtime.metamodel.annotation.Configuration; import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; import java.util.Arrays; import java.util.Set; +import java.util.function.Function; import java.util.stream.Stream; /** @@ -30,11 +33,27 @@ public Stream> getInjectionPoints(T instance) { var targetClass = instance.getClass(); - return Arrays.stream(targetClass.getDeclaredFields()) + // scan service injection points + var fields = Arrays.stream(targetClass.getDeclaredFields()) .filter(f -> f.getAnnotation(Inject.class) != null) .map(f -> { var isRequired = f.getAnnotation(Inject.class).required(); - return new FieldInjectionPoint<>(instance, f, isRequired); + return new ServiceInjectionPoint<>(instance, f, isRequired); }); + + // scan value injection points + var values = Arrays.stream(targetClass.getDeclaredFields()) + .filter(f -> f.getAnnotation(Setting.class) != null && !Setting.NULL.equals(f.getAnnotation(Setting.class).key())) + .map(f -> { + var annotation = f.getAnnotation(Setting.class); + return new ValueInjectionPoint<>(instance, f, annotation, targetClass); + }); + + // scan configuration injection points + var configObjects = Arrays.stream(targetClass.getDeclaredFields()) + .filter(f -> f.getAnnotation(Configuration.class) != null) + .map(f -> new ConfigurationInjectionPoint<>(instance, f)); + + return Stream.of(fields, values, configObjects).flatMap(Function.identity()); } } diff --git a/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectorImpl.java b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectorImpl.java index c4664ba4ee7..f843ccc2f5f 100644 --- a/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectorImpl.java +++ b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/InjectorImpl.java @@ -14,7 +14,6 @@ package org.eclipse.edc.boot.system.injection; -import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.system.ServiceExtensionContext; public final class InjectorImpl implements Injector { @@ -37,31 +36,18 @@ public T inject(InjectionContainer container, ServiceExtensionContext con container.getInjectionPoints().forEach(ip -> { try { - var service = resolveService(context, ip); + var service = ip.resolve(context, defaultServiceSupplier); if (service != null) { //can only be if not required ip.setTargetValue(service); } } catch (EdcInjectionException ex) { throw ex; //simply rethrow, do not wrap in another one - } catch (EdcException ex) { //thrown e.g. if the service is not present and is not optional + } catch (Exception ex) { //thrown e.g. if the service is not present and is not optional monitor.warning("Error during injection", ex); throw new EdcInjectionException(ex); - } catch (IllegalAccessException e) { //e.g. when the field is marked "final" - monitor.warning("Could not set injection target", e); - throw new EdcInjectionException(e); } }); return container.getInjectionTarget(); } - - private Object resolveService(ServiceExtensionContext context, InjectionPoint injectionPoint) { - var serviceClass = injectionPoint.getType(); - if (context.hasService(serviceClass)) { - return context.getService(serviceClass, !injectionPoint.isRequired()); - } else { - return defaultServiceSupplier.provideFor(injectionPoint, context); - } - } - } diff --git a/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/FieldInjectionPoint.java b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/ServiceInjectionPoint.java similarity index 50% rename from core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/FieldInjectionPoint.java rename to core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/ServiceInjectionPoint.java index 7f92753dac7..d553b53428a 100644 --- a/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/FieldInjectionPoint.java +++ b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/ServiceInjectionPoint.java @@ -15,11 +15,16 @@ package org.eclipse.edc.boot.system.injection; import org.eclipse.edc.boot.system.injection.lifecycle.ServiceProvider; +import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; import static java.lang.String.format; +import static java.util.Optional.ofNullable; /** * Represents one single auto-injectable field. More specific, it is a tuple consisting of a target, a field, the respective feature string and a flag whether the @@ -27,17 +32,17 @@ *

* Each injectable field of a {@link ServiceExtension} is represented by one InjectionPoint */ -public class FieldInjectionPoint implements InjectionPoint { +public class ServiceInjectionPoint implements InjectionPoint { private final T instance; private final Field injectedField; private final boolean isRequired; private ServiceProvider defaultServiceProvider; - public FieldInjectionPoint(T instance, Field injectedField) { + public ServiceInjectionPoint(T instance, Field injectedField) { this(instance, injectedField, true); } - public FieldInjectionPoint(T instance, Field injectedField, boolean isRequired) { + public ServiceInjectionPoint(T instance, Field injectedField, boolean isRequired) { this.instance = instance; this.injectedField = injectedField; this.injectedField.setAccessible(true); @@ -45,7 +50,7 @@ public FieldInjectionPoint(T instance, Field injectedField, boolean isRequired) } @Override - public T getInstance() { + public T getTargetInstance() { return instance; } @@ -60,8 +65,13 @@ public boolean isRequired() { } @Override - public void setTargetValue(Object service) throws IllegalAccessException { - injectedField.set(instance, service); + public Result setTargetValue(Object value) { + try { + injectedField.set(instance, value); + } catch (IllegalAccessException e) { + return Result.failure("Could not assign value '%s' to field '%s'. Reason: %s".formatted(value, injectedField, e.getMessage())); + } + return Result.success(); } @Override @@ -75,6 +85,36 @@ public void setDefaultServiceProvider(ServiceProvider defaultServiceProvider) { } + @Override + public Object resolve(ServiceExtensionContext context, DefaultServiceSupplier defaultServiceSupplier) { + var serviceClass = getType(); + if (context.hasService(serviceClass)) { + return context.getService(serviceClass, !isRequired()); + } else { + return defaultServiceSupplier.provideFor(this, context); + } + } + + @Override + public Result>> getProviders(Map, List>> dependencyMap, ServiceExtensionContext context) { + var serviceClass = getType(); + var providers = ofNullable(dependencyMap.get(serviceClass)); + if (providers.isPresent()) { + return Result.success(providers.get()); + } else if (context.hasService(serviceClass)) { + return Result.success(List.of()); + } else { + // attempt to interpret the feature name as class name and see if the context has that service + return Result.failure(injectedField.getName() + " of type " + serviceClass); + } + + } + + @Override + public String getTypeString() { + return "Service"; + } + @Override public String toString() { return format("Field \"%s\" of type [%s] required by %s", injectedField.getName(), getType(), instance.getClass().getName()); diff --git a/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/ValueInjectionPoint.java b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/ValueInjectionPoint.java new file mode 100644 index 00000000000..9ef257db2a4 --- /dev/null +++ b/core/common/boot/src/main/java/org/eclipse/edc/boot/system/injection/ValueInjectionPoint.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.boot.system.injection; + +import org.eclipse.edc.boot.system.injection.lifecycle.ServiceProvider; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.system.ServiceExtensionContext; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; + +/** + * Injection point for configuration values ("settings"). Configuration values must be basic data types and be annotated + * with {@link Setting}, for example: + * + *

+ * public class SomeExtension implement ServiceExtension {
+ *   \@Setting(key = "foo.bar.baz", description = "some important config", ...)d
+ *   private String fooBarBaz;
+ * }
+ * 
+ * Currently, only {@link String}, {@link Double}, {@link Integer}, {@link Long} and {@link Boolean} are supported as data types + * for the annotated field. + * + * @param The type of the declaring class. + */ +public class ValueInjectionPoint implements InjectionPoint { + public final List> emptyProviderlist = List.of(); + private final T objectInstance; + private final Field targetField; + private final Setting annotationValue; + private final Class declaringClass; + + /** + * Constructs a new ValueInjectionPoint instance + * + * @param objectInstance The object instance that contains the annotated field. May be null in case the declaring class is not a {@link org.eclipse.edc.spi.system.SystemExtension} + * @param targetField The annotated {@link Field} + * @param annotationValue The concrete annotation instance (needed to obtain its attributes) + * @param declaringClass The class where the annotated field is declared. Usually, this is {@code objectInstance.getClass()}. + */ + public ValueInjectionPoint(T objectInstance, Field targetField, Setting annotationValue, Class declaringClass) { + this.objectInstance = objectInstance; + this.targetField = targetField; + this.declaringClass = declaringClass; + this.targetField.setAccessible(true); + this.annotationValue = annotationValue; + } + + public Field getTargetField() { + return targetField; + } + + @Override + public T getTargetInstance() { + return objectInstance; + } + + @Override + public Class getType() { + return targetField.getType(); + } + + @Override + public boolean isRequired() { + return annotationValue.required(); + } + + @Override + public Result setTargetValue(Object value) { + if (objectInstance != null) { + try { + targetField.set(objectInstance, value); + } catch (IllegalAccessException e) { + return Result.failure("Could not assign value '%s' to field '%s'. Reason: %s".formatted(value, targetField, e.getMessage())); + } + return Result.success(); + } + return Result.failure("Cannot set field, object instance is null"); + } + + /** + * Not used here, always returns null; + * + * @return always {@code null} + */ + @Override + public ServiceProvider getDefaultServiceProvider() { + return null; + } + + /** + * Not used here + * + * @param defaultServiceProvider Ignored + */ + @Override + public void setDefaultServiceProvider(ServiceProvider defaultServiceProvider) { + + } + + @Override + public Object resolve(ServiceExtensionContext context, DefaultServiceSupplier defaultServiceSupplier) { + var config = context.getConfig(); + var type = getType(); + var key = annotationValue.key(); + + // value is found in the config + if (config.hasKey(key)) { + return parseEntry(config.getString(key), type); + } + + // not found in config, but there is a default value + var def = annotationValue.defaultValue(); + if (def != null && !def.trim().equals(Setting.NULL)) { + var msg = "Config value: no setting found for '%s', falling back to default value '%s'".formatted(key, def); + if (annotationValue.warnOnMissingConfig()) { + context.getMonitor().warning(msg); + } else { + context.getMonitor().debug(msg); + } + return parseEntry(def, type); + } + + // neither in config, nor default val + if (annotationValue.required()) { + throw new EdcInjectionException("No config value and no default value found for injected field " + this); + } + return null; + } + + /** + * Determines whether a configuration value is "satisfied by" the given {@link ServiceExtensionContext} (the dependency map is ignored). + * + * @param dependencyMap Ignored + * @param context the {@link ServiceExtensionContext} in which the config is expected to be found. + * @return success if found in the context, a failure otherwise. + */ + @Override + public Result>> getProviders(Map, List>> dependencyMap, ServiceExtensionContext context) { + + if (!annotationValue.required()) { + return Result.success(emptyProviderlist); // optional configs are always satisfied + } + + var defaultVal = annotationValue.defaultValue(); + + if (defaultVal != null && !defaultVal.trim().equals(Setting.NULL)) { + return Result.success(emptyProviderlist); // a default value means the value injection point can always be satisfied + } + + // no default value, the required value may be found in the config + return context.getConfig().hasKey(annotationValue.key()) + ? Result.success(emptyProviderlist) + : Result.failure("%s (property \"%s\")".formatted(targetField.getName(), annotationValue.key())); + } + + @Override + public String getTypeString() { + return "Config value"; + } + + @Override + public String toString() { + return "Configuration value \"%s\" of type %s (config key '%s') in %s".formatted(targetField.getName(), getType(), annotationValue.key(), declaringClass); + } + + private Object parseEntry(String string, Class valueType) { + try { + if (valueType == Long.class) { + return Long.parseLong(string); + } + if (valueType == Integer.class) { + return Integer.parseInt(string); + } + if (valueType == Double.class) { + return Double.parseDouble(string); + } + } catch (NumberFormatException e) { + throw new EdcInjectionException("Config field '%s' is of type '%s', but the value resolved from key '%s' is \"%s\" which cannot be interpreted as %s.".formatted(targetField.getName(), valueType, annotationValue.key(), string, valueType)); + } + if (valueType == Boolean.class) { + return Boolean.parseBoolean(string); + } + + return string; + } + +} diff --git a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/ExtensionLoaderTest.java b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/ExtensionLoaderTest.java index 6d1f3a3624d..be9c07382e4 100644 --- a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/ExtensionLoaderTest.java +++ b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/ExtensionLoaderTest.java @@ -221,7 +221,10 @@ void loadServiceExtensions_dependencyNotSatisfied() { when(serviceLocator.loadImplementors(eq(ServiceExtension.class), anyBoolean())).thenReturn(mutableListOf(depending, someExtension)); - assertThatThrownBy(() -> loader.loadServiceExtensions(context)).isInstanceOf(EdcException.class).hasMessageContaining("The following injected fields were not provided:\nField \"someService\" of type "); + assertThatThrownBy(() -> loader.loadServiceExtensions(context)) + .isInstanceOf(EdcException.class) + .hasMessageContaining("The following injected fields or values were not provided or could not be resolved") + .hasMessageContaining("Service someService of type class org.eclipse.edc.boot.system.ExtensionLoaderTest$SomeObject"); verify(serviceLocator).loadImplementors(eq(ServiceExtension.class), anyBoolean()); } diff --git a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/InjectorImplTest.java b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/InjectorImplTest.java index e48a3761200..790a17b9994 100644 --- a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/InjectorImplTest.java +++ b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/InjectorImplTest.java @@ -16,9 +16,9 @@ import org.eclipse.edc.boot.system.injection.DefaultServiceSupplier; import org.eclipse.edc.boot.system.injection.EdcInjectionException; -import org.eclipse.edc.boot.system.injection.FieldInjectionPoint; import org.eclipse.edc.boot.system.injection.InjectionContainer; import org.eclipse.edc.boot.system.injection.InjectorImpl; +import org.eclipse.edc.boot.system.injection.ServiceInjectionPoint; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.monitor.Monitor; @@ -85,7 +85,7 @@ void templateWithNoInjectionPoints() { void allInjectionPointsSatisfied() throws NoSuchFieldException { var serviceExtension = new TestServiceExtension(); var field = serviceExtension.getClass().getDeclaredField("someObject"); - var template = new InjectionContainer<>(serviceExtension, Set.of(new FieldInjectionPoint<>(serviceExtension, field)), emptyList()); + var template = new InjectionContainer<>(serviceExtension, Set.of(new ServiceInjectionPoint<>(serviceExtension, field)), emptyList()); when(context.hasService(eq(SomeObject.class))).thenReturn(true); when(context.getService(eq(SomeObject.class), anyBoolean())).thenReturn(new SomeObject()); @@ -101,7 +101,7 @@ void allInjectionPointsSatisfied() throws NoSuchFieldException { void defaultInjectionPoint() throws NoSuchFieldException { var serviceExtension = new TestServiceExtension(); var field = serviceExtension.getClass().getDeclaredField("someObject"); - var template = new InjectionContainer<>(serviceExtension, Set.of(new FieldInjectionPoint<>(serviceExtension, field)), emptyList()); + var template = new InjectionContainer<>(serviceExtension, Set.of(new ServiceInjectionPoint<>(serviceExtension, field)), emptyList()); when(context.hasService(SomeObject.class)).thenReturn(false); when(context.getService(SomeObject.class, false)).thenThrow(new EdcException("Service not found")); when(defaultServiceSupplier.provideFor(any(), any())).thenReturn(new SomeObject()); @@ -117,7 +117,7 @@ void defaultInjectionPoint() throws NoSuchFieldException { void notAllInjectionPointsSatisfied_shouldThrowException() throws NoSuchFieldException { var serviceExtension = new TestServiceExtension(); var field = serviceExtension.getClass().getDeclaredField("someObject"); - var template = new InjectionContainer<>(serviceExtension, Set.of(new FieldInjectionPoint<>(serviceExtension, field)), emptyList()); + var template = new InjectionContainer<>(serviceExtension, Set.of(new ServiceInjectionPoint<>(serviceExtension, field)), emptyList()); var rootCauseException = new EdcInjectionException("Service not found"); when(context.hasService(SomeObject.class)).thenReturn(false); when(defaultServiceSupplier.provideFor(any(), any())).thenThrow(rootCauseException); @@ -132,19 +132,19 @@ void notAllInjectionPointsSatisfied_shouldThrowException() throws NoSuchFieldExc @Test @DisplayName("Cannot set value of the injected field") - void cannotSetInjectionPoint_shouldThrowException() throws NoSuchFieldException, IllegalAccessException { + void cannotSetInjectionPoint_shouldThrowException() throws NoSuchFieldException { var serviceExtension = new TestServiceExtension(); var field = serviceExtension.getClass().getDeclaredField("someObject"); - var injectionPoint = spy(new FieldInjectionPoint<>(serviceExtension, field)); + var injectionPoint = spy(new ServiceInjectionPoint<>(serviceExtension, field)); var template = new InjectionContainer<>(serviceExtension, Set.of(injectionPoint), emptyList()); var value = new SomeObject(); when(context.hasService(eq(SomeObject.class))).thenReturn(true); when(context.getService(eq(SomeObject.class), anyBoolean())).thenReturn(value); - doThrow(new IllegalAccessException("test")).when(injectionPoint).setTargetValue(value); + doThrow(new RuntimeException("test")).when(injectionPoint).setTargetValue(value); - assertThatThrownBy(() -> injector.inject(template, context)).isInstanceOf(EdcInjectionException.class).hasCauseInstanceOf(IllegalAccessException.class); + assertThatThrownBy(() -> injector.inject(template, context)).isInstanceOf(EdcInjectionException.class).hasCauseInstanceOf(RuntimeException.class); assertThat(serviceExtension.someObject).isNull(); verify(context).hasService(eq(SomeObject.class)); verify(context).getService(eq(SomeObject.class), anyBoolean()); @@ -156,7 +156,7 @@ void cannotSetInjectionPoint_shouldThrowException() throws NoSuchFieldException, void optionalService_provided() throws NoSuchFieldException { var serviceExtension = new TestServiceExtension(); var field = serviceExtension.getClass().getDeclaredField("someObject"); - var template = new InjectionContainer<>(serviceExtension, Set.of(new FieldInjectionPoint<>(serviceExtension, field, false)), emptyList()); + var template = new InjectionContainer<>(serviceExtension, Set.of(new ServiceInjectionPoint<>(serviceExtension, field, false)), emptyList()); when(context.hasService(eq(SomeObject.class))).thenReturn(true); when(context.getService(any(), anyBoolean())).thenReturn(new SomeObject()); @@ -173,7 +173,7 @@ void optionalService_provided() throws NoSuchFieldException { void optionalService_defaultProvided() throws NoSuchFieldException { var serviceExtension = new TestServiceExtension(); var field = serviceExtension.getClass().getDeclaredField("someObject"); - var template = new InjectionContainer<>(serviceExtension, Set.of(new FieldInjectionPoint<>(serviceExtension, field, false)), emptyList()); + var template = new InjectionContainer<>(serviceExtension, Set.of(new ServiceInjectionPoint<>(serviceExtension, field, false)), emptyList()); when(context.hasService(eq(SomeObject.class))).thenReturn(false); when(defaultServiceSupplier.provideFor(any(), any())).thenReturn(new SomeObject()); @@ -189,7 +189,7 @@ void optionalService_defaultProvided() throws NoSuchFieldException { void optionalService_defaultNotProvided() throws NoSuchFieldException { var serviceExtension = new TestServiceExtension(); var field = serviceExtension.getClass().getDeclaredField("someObject"); - var template = new InjectionContainer<>(serviceExtension, Set.of(new FieldInjectionPoint<>(serviceExtension, field, false)), emptyList()); + var template = new InjectionContainer<>(serviceExtension, Set.of(new ServiceInjectionPoint<>(serviceExtension, field, false)), emptyList()); when(context.hasService(eq(SomeObject.class))).thenReturn(false); when(defaultServiceSupplier.provideFor(any(), any())).thenReturn(null); diff --git a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/TestFunctions.java b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/TestFunctions.java index 6b4a1a133ca..4a563995bf5 100644 --- a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/TestFunctions.java +++ b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/TestFunctions.java @@ -19,7 +19,9 @@ import org.eclipse.edc.boot.system.testextensions.ProviderExtension; import org.eclipse.edc.boot.system.testextensions.RequiredDependentExtension; import org.eclipse.edc.spi.system.ServiceExtension; +import org.jetbrains.annotations.NotNull; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; @@ -37,4 +39,12 @@ public static ServiceExtension createDependentExtension(boolean isRequired) { return isRequired ? new RequiredDependentExtension() : new DependentExtension(); } + + public static @NotNull Field getDeclaredField(Class clazz, String name) { + try { + return clazz.getDeclaredField(name); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } } diff --git a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/injection/ConfigurationInjectionPointTest.java b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/injection/ConfigurationInjectionPointTest.java new file mode 100644 index 00000000000..ca39b9003fe --- /dev/null +++ b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/injection/ConfigurationInjectionPointTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.boot.system.injection; + +import org.eclipse.edc.boot.system.testextensions.ConfigurationObject; +import org.eclipse.edc.boot.system.testextensions.ExtensionWithConfigObject; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.ConfigFactory; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.eclipse.edc.boot.system.TestFunctions.getDeclaredField; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ConfigurationInjectionPointTest { + + private final ExtensionWithConfigObject targetInstance = new ExtensionWithConfigObject(); + private final ConfigurationInjectionPoint injectionPoint = new ConfigurationInjectionPoint<>(targetInstance, getDeclaredField(ExtensionWithConfigObject.class, "configurationObject")); + + @Test + void getTargetInstance() { + assertThat(injectionPoint.getTargetInstance()).isEqualTo(targetInstance); + } + + @Test + void getType() { + assertThat(injectionPoint.getType()).isEqualTo(ConfigurationObject.class); + } + + @Test + void isRequired() { + assertThat(injectionPoint.isRequired()).isTrue(); + var ip = new ConfigurationInjectionPoint<>(targetInstance, getDeclaredField(ExtensionWithConfigObject.class, "optionalConfigurationObject")); + assertThat(ip.isRequired()).isFalse(); + } + + @Test + void setTargetValue() throws IllegalAccessException { + assertThat(injectionPoint.setTargetValue(new ConfigurationObject("foo", 42L, null, 3.14159)).succeeded()) + .isTrue(); + } + + @Test + void getDefaultServiceProvider() { + assertThat(injectionPoint.getDefaultServiceProvider()).isNull(); + } + + @Test + void setDefaultServiceProvider() { + //noop + } + + @Test + void resolve_record_isRequired_notResolved() { + var context = mock(ServiceExtensionContext.class); + when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of())); + assertThatThrownBy(() -> injectionPoint.resolve(context, mock())).isInstanceOf(EdcInjectionException.class); + } + + @Test + void resolve_objectIsRecord() { + var context = mock(ServiceExtensionContext.class); + when(context.getMonitor()).thenReturn(mock()); + when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of("foo.bar.baz", "asdf", + "test.key3", "42"))); + + var res = injectionPoint.resolve(context, mock()); + assertThat(res).isInstanceOf(ConfigurationObject.class); + } + + @Test + void resolve_objectIsClass() { + var ip = new ConfigurationInjectionPoint<>(targetInstance, getDeclaredField(ExtensionWithConfigObject.class, "optionalConfigurationObject")); + var context = mock(ServiceExtensionContext.class); + when(context.getMonitor()).thenReturn(mock()); + when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of("optional.key", "asdf"))); + + var result = ip.resolve(context, mock()); + + assertThat(result).isInstanceOf(ExtensionWithConfigObject.OptionalConfigurationObject.class); + assertThat(((ExtensionWithConfigObject.OptionalConfigurationObject) result).getVal()).isEqualTo("asdf"); + } + + @Test + void getProviders() { + var context = mock(ServiceExtensionContext.class); + when(context.getMonitor()).thenReturn(mock()); + when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of("foo.bar.baz", "asdf", + "test.key3", "42"))); + + assertThat(injectionPoint.getProviders(Map.of(), context).succeeded()).isTrue(); + } + + @Test + void getProviders_hasViolations() { + var context = mock(ServiceExtensionContext.class); + when(context.getMonitor()).thenReturn(mock()); + when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of())); + + var result = injectionPoint.getProviders(Map.of(), context); + assertThat(result.succeeded()).isFalse(); + assertThat(result.getFailureDetail()).isEqualTo("configurationObject (ConfigurationObject) --> [requiredVal (property \"foo.bar.baz\")]"); + } +} \ No newline at end of file diff --git a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/injection/InjectionPointDefaultServiceSupplierTest.java b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/injection/InjectionPointDefaultServiceSupplierTest.java index 2bed28df654..a7f3019253a 100644 --- a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/injection/InjectionPointDefaultServiceSupplierTest.java +++ b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/injection/InjectionPointDefaultServiceSupplierTest.java @@ -30,7 +30,7 @@ class InjectionPointDefaultServiceSupplierTest { @Test void shouldRegisterDefaultService() { - var injectionPoint = new FieldInjectionPoint<>("any", mock()); + var injectionPoint = new ServiceInjectionPoint<>("any", mock()); ServiceProvider serviceProvider = mock(); when(serviceProvider.register(any())).thenReturn("service"); injectionPoint.setDefaultServiceProvider(serviceProvider); @@ -43,7 +43,7 @@ void shouldRegisterDefaultService() { @Test void shouldReturnNull_whenNoDefaultServiceSet() { - var injectionPoint = new FieldInjectionPoint<>("any", mock(), false); + var injectionPoint = new ServiceInjectionPoint<>("any", mock(), false); ServiceExtensionContext context = mock(); var service = supplier.provideFor(injectionPoint, context); @@ -53,7 +53,7 @@ void shouldReturnNull_whenNoDefaultServiceSet() { @Test void shouldThrowException_whenNoDefaultServiceAndIsRequired() { - var injectionPoint = new FieldInjectionPoint<>("any", mock(), true); + var injectionPoint = new ServiceInjectionPoint<>("any", mock(), true); ServiceExtensionContext context = mock(); assertThatThrownBy(() -> supplier.provideFor(injectionPoint, context)).isInstanceOf(EdcInjectionException.class); diff --git a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/injection/ServiceInjectionPointTest.java b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/injection/ServiceInjectionPointTest.java new file mode 100644 index 00000000000..7b13126cedb --- /dev/null +++ b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/injection/ServiceInjectionPointTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.boot.system.injection; + +import org.eclipse.edc.boot.system.TestFunctions; +import org.eclipse.edc.boot.system.TestObject; +import org.eclipse.edc.boot.system.testextensions.DependentExtension; +import org.eclipse.edc.boot.system.testextensions.RequiredDependentExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +class ServiceInjectionPointTest { + + @Test + void getInstance() { + var ip = new ServiceInjectionPoint<>(new RequiredDependentExtension(), TestFunctions.getDeclaredField(RequiredDependentExtension.class, "testObject")); + assertThat(ip.getTargetInstance()).isInstanceOf(RequiredDependentExtension.class); + } + + @Test + void getType() { + var ip = new ServiceInjectionPoint<>(new RequiredDependentExtension(), TestFunctions.getDeclaredField(RequiredDependentExtension.class, "testObject")); + assertThat(ip.getType()).isEqualTo(TestObject.class); + } + + @Test + void isRequired() { + var ip = new ServiceInjectionPoint<>(new RequiredDependentExtension(), TestFunctions.getDeclaredField(RequiredDependentExtension.class, "testObject")); + assertThat(ip.isRequired()).isTrue(); + + var ip2 = new ServiceInjectionPoint<>(new DependentExtension(), TestFunctions.getDeclaredField(DependentExtension.class, "testObject"), false); + assertThat(ip2.isRequired()).isFalse(); + } + + @Test + void setTargetValue() throws IllegalAccessException { + var requiredDependentExtension = new RequiredDependentExtension(); + var ip = new ServiceInjectionPoint<>(requiredDependentExtension, TestFunctions.getDeclaredField(RequiredDependentExtension.class, "testObject")); + var newObject = new TestObject("new object"); + ip.setTargetValue(newObject); + assertThat(requiredDependentExtension.getTestObject()).isEqualTo(newObject); + } + + + @Test + void resolve_providerFoundInContext() { + var ip = new ServiceInjectionPoint<>(new RequiredDependentExtension(), TestFunctions.getDeclaredField(RequiredDependentExtension.class, "testObject")); + var context = mock(ServiceExtensionContext.class); + var defaultServiceSupplier = mock(DefaultServiceSupplier.class); + + var testObject = new TestObject("foo"); + when(context.hasService(eq(TestObject.class))).thenReturn(true); + when(context.getService(eq(TestObject.class), anyBoolean())).thenReturn(testObject); + var result = ip.resolve(context, defaultServiceSupplier); + + assertThat(result).isEqualTo(testObject); + verifyNoInteractions(defaultServiceSupplier); + + } + + @Test + void resolve_defaultProvider() { + var ip = new ServiceInjectionPoint<>(new RequiredDependentExtension(), TestFunctions.getDeclaredField(RequiredDependentExtension.class, "testObject")); + var context = mock(ServiceExtensionContext.class); + var defaultServiceSupplier = mock(DefaultServiceSupplier.class); + + var testObject = new TestObject("foo"); + when(context.hasService(eq(TestObject.class))).thenReturn(false); + when(defaultServiceSupplier.provideFor(eq(ip), eq(context))).thenReturn(testObject); + + var result = ip.resolve(context, defaultServiceSupplier); + + assertThat(result).isEqualTo(testObject); + verify(context).hasService(eq(TestObject.class)); + verify(defaultServiceSupplier).provideFor(eq(ip), eq(context)); + verifyNoMoreInteractions(context, defaultServiceSupplier); + } + + @Test + void resolve_noDefaultProvider() { + var ip = new ServiceInjectionPoint<>(new RequiredDependentExtension(), TestFunctions.getDeclaredField(RequiredDependentExtension.class, "testObject")); + var context = mock(ServiceExtensionContext.class); + var defaultServiceSupplier = mock(DefaultServiceSupplier.class); + + when(context.hasService(eq(TestObject.class))).thenReturn(false); + when(defaultServiceSupplier.provideFor(eq(ip), eq(context))).thenThrow(new EdcInjectionException("foo")); + + assertThatThrownBy(() -> ip.resolve(context, defaultServiceSupplier)).isInstanceOf(EdcInjectionException.class); + + verify(context).hasService(eq(TestObject.class)); + verify(defaultServiceSupplier).provideFor(eq(ip), eq(context)); + verifyNoMoreInteractions(context, defaultServiceSupplier); + + } + + @Test + void getProviders_fromMap() { + var ip = new ServiceInjectionPoint<>(new RequiredDependentExtension(), TestFunctions.getDeclaredField(RequiredDependentExtension.class, "testObject")); + var context = mock(ServiceExtensionContext.class); + + var ic = new InjectionContainer<>(new RequiredDependentExtension(), Set.of(ip), List.of()); + + var result = ip.getProviders(Map.of(TestObject.class, List.of(ic)), context); + assertThat(result.succeeded()).isTrue(); + } + + @Test + void getProviders_fromContext() { + var ip = new ServiceInjectionPoint<>(new RequiredDependentExtension(), TestFunctions.getDeclaredField(RequiredDependentExtension.class, "testObject")); + var context = mock(ServiceExtensionContext.class); + + when(context.hasService(eq(TestObject.class))).thenReturn(true); + + var result = ip.getProviders(Map.of(), context); + assertThat(result.succeeded()).isTrue(); + } + + @Test + void isSatisfiedBy_notSatisfied() { + var ip = new ServiceInjectionPoint<>(new RequiredDependentExtension(), TestFunctions.getDeclaredField(RequiredDependentExtension.class, "testObject")); + var context = mock(ServiceExtensionContext.class); + + when(context.hasService(eq(TestObject.class))).thenReturn(false); + + var result = ip.getProviders(Map.of(), context); + assertThat(result.succeeded()).isFalse(); + } + +} \ No newline at end of file diff --git a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/injection/ValueInjectionPointTest.java b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/injection/ValueInjectionPointTest.java new file mode 100644 index 00000000000..a170461a2a2 --- /dev/null +++ b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/injection/ValueInjectionPointTest.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.boot.system.injection; + +import org.eclipse.edc.boot.system.testextensions.ConfigurationObject; +import org.eclipse.edc.boot.system.testextensions.ExtensionWithConfigValue; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.ConfigFactory; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.eclipse.edc.boot.system.TestFunctions.getDeclaredField; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ValueInjectionPointTest { + + abstract class Tests { + protected abstract ValueInjectionPoint getInjectionPoint(); + + protected abstract Object getTargetObject(); + + @Test + void getType() { + assertThat(getInjectionPoint().getType()).isEqualTo(String.class); + } + + @Test + void isRequired() { + assertThat(getInjectionPoint().isRequired()).isTrue(); + var ip2 = createInjectionPoint(getTargetObject(), "optionalVal", Long.class); + assertThat(ip2.isRequired()).isFalse(); + } + + + @Test + void getDefaultServiceProvider() { + assertThat(getInjectionPoint().getDefaultServiceProvider()).isNull(); + } + + @Test + void setDefaultServiceProvider() { + //noop + } + + @Test + void resolve_whenRequired_andNotFound_andNoDefault_expectException() { + var contextMock = mock(ServiceExtensionContext.class); + var config = ConfigFactory.fromMap(Map.of()); + when(contextMock.getConfig()).thenReturn(config); + + assertThatThrownBy(() -> getInjectionPoint().resolve(contextMock, mock())) + .isInstanceOf(EdcInjectionException.class) + .hasMessageContaining("No config value and no default value found for injected field"); + } + + @Test + void resolve_whenRequired_andNotFound_hasDefault() { + var contextMock = mock(ServiceExtensionContext.class); + var config = ConfigFactory.fromMap(Map.of()); + when(contextMock.getConfig()).thenReturn(config); + when(contextMock.getMonitor()).thenReturn(mock()); + var ip = createInjectionPoint(getInjectionPoint(), "requiredValWithDefault", ExtensionWithConfigValue.class); + assertThat(ip.resolve(contextMock, mock())).isEqualTo(ExtensionWithConfigValue.DEFAULT_VALUE); + } + + @Test + void resolve_whenRequired_andNotFound_hasDefaultOfWrongType() { + var contextMock = mock(ServiceExtensionContext.class); + var config = ConfigFactory.fromMap(Map.of()); + when(contextMock.getConfig()).thenReturn(config); + when(contextMock.getMonitor()).thenReturn(mock()); + var ip = createInjectionPoint(getTargetObject(), "requiredDoubleVal", ExtensionWithConfigValue.class); + assertThatThrownBy(() -> ip.resolve(contextMock, mock())).isInstanceOf(EdcInjectionException.class); + } + + @Test + void resolve_whenRequired_andHasConfig() { + var contextMock = mock(ServiceExtensionContext.class); + var config = ConfigFactory.fromMap(Map.of("test.key", "test.value")); + when(contextMock.getConfig()).thenReturn(config); + assertThat(getInjectionPoint().resolve(contextMock, mock())).isEqualTo("test.value"); + } + + @Test + void resolve_whenRequired_andHasConfigOfWrongType() { + var contextMock = mock(ServiceExtensionContext.class); + var config = ConfigFactory.fromMap(Map.of("test.key3", "this-should-be-a-double")); + when(contextMock.getConfig()).thenReturn(config); + assertThatThrownBy(() -> getInjectionPoint().resolve(contextMock, mock())).isInstanceOf(EdcInjectionException.class); + } + + @Test + void resolve_whenNotRequired_notFound_shouldReturnNull() { + var contextMock = mock(ServiceExtensionContext.class); + var config = ConfigFactory.fromMap(Map.of()); + when(contextMock.getConfig()).thenReturn(config); + var ip = createInjectionPoint(getTargetObject(), "optionalVal", ExtensionWithConfigValue.class); + assertThat(ip.resolve(contextMock, mock())).isNull(); + } + + @Test + void isSatisfiedBy_whenOptional() { + var ip = createInjectionPoint(getTargetObject(), "optionalVal", ExtensionWithConfigValue.class); + var contextMock = mock(ServiceExtensionContext.class); + var config = ConfigFactory.fromMap(Map.of()); + when(contextMock.getConfig()).thenReturn(config); + assertThat(ip.getProviders(Map.of(), contextMock).succeeded()).isTrue(); + } + + @Test + void isSatisfiedBy_whenRequired_satisfiedByDefaultValue() { + var ip = createInjectionPoint(getTargetObject(), "requiredValWithDefault", ExtensionWithConfigValue.class); + var contextMock = mock(ServiceExtensionContext.class); + var config = ConfigFactory.fromMap(Map.of()); + when(contextMock.getConfig()).thenReturn(config); + + assertThat(ip.getProviders(Map.of(), contextMock).succeeded()).isTrue(); + } + + @Test + void isSatisfiedBy_whenRequired_satisfiedByConfig() { + var ip = createInjectionPoint(getTargetObject(), "requiredVal", ExtensionWithConfigValue.class); + var contextMock = mock(ServiceExtensionContext.class); + var config = ConfigFactory.fromMap(Map.of("test.key", "test.value")); + when(contextMock.getConfig()).thenReturn(config); + + assertThat(ip.getProviders(Map.of(), contextMock).succeeded()).isTrue(); + } + + @Test + void isSatisfiedBy_whenRequired_notSatisfied() { + var ip = createInjectionPoint(getTargetObject(), "requiredVal", ExtensionWithConfigValue.class); + var contextMock = mock(ServiceExtensionContext.class); + var config = ConfigFactory.fromMap(Map.of()); + when(contextMock.getConfig()).thenReturn(config); + + assertThat(ip.getProviders(Map.of(), contextMock).succeeded()).isFalse(); + } + } + + @Nested + class DeclaredOnExtension extends Tests { + + private final ExtensionWithConfigValue targetObject = new ExtensionWithConfigValue(); + private final ValueInjectionPoint injectionPoint = createInjectionPoint(targetObject, "requiredVal", targetObject.getClass()); + + @Test + void getTargetInstance() { + assertThat(injectionPoint.getTargetInstance()).isEqualTo(targetObject); + } + + @Test + void setTargetValue() throws IllegalAccessException, NoSuchFieldException { + assertThat(getInjectionPoint().setTargetValue("test").succeeded()).isTrue(); + + var field = getTargetObject().getClass().getDeclaredField("requiredVal"); // yes, it's a bit dirty... + field.setAccessible(true); + assertThat(field.get(getTargetObject())) + .isEqualTo("test"); + } + + @Override + protected ValueInjectionPoint getInjectionPoint() { + return injectionPoint; + } + + @Override + protected Object getTargetObject() { + return targetObject; + } + } + + @Nested + class DeclaredOnConfigObject extends Tests { + private final ValueInjectionPoint injectionPoint = createInjectionPoint(null, "requiredVal", ConfigurationObject.class); + + @Test + void getTargetInstance() { + assertThat(injectionPoint.getTargetInstance()).isNull(); + } + + @Test + void setTargetValue() throws IllegalAccessException { + assertThat(getInjectionPoint().setTargetValue("test").succeeded()).isFalse(); + } + + @Override + protected ValueInjectionPoint getInjectionPoint() { + return injectionPoint; + } + + @Override + protected Object getTargetObject() { + return null; + } + } + + + private ValueInjectionPoint createInjectionPoint(Object targetObject, String fieldName, Class targetClass) { + var field = getDeclaredField(ExtensionWithConfigValue.class, fieldName); + return new ValueInjectionPoint<>(targetObject, field, field.getAnnotation(Setting.class), targetClass); + } + +} \ No newline at end of file diff --git a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/testextensions/ConfigurationObject.java b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/testextensions/ConfigurationObject.java new file mode 100644 index 00000000000..d5c58bf5600 --- /dev/null +++ b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/testextensions/ConfigurationObject.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.boot.system.testextensions; + +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.runtime.metamodel.annotation.Settings; + +import static org.eclipse.edc.boot.system.testextensions.ExtensionWithConfigValue.DEFAULT_VALUE; + +@Settings +public record ConfigurationObject(@Setting(key = "foo.bar.baz") String requiredVal, + @Setting(key = "quizz.quazz", required = false) Long optionalVal, + @Setting(key = "test.key2", defaultValue = DEFAULT_VALUE) String requiredValWithDefault, + @Setting(key = "test.key3", defaultValue = DEFAULT_VALUE) Double requiredDoubleVal) { + +} diff --git a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/testextensions/ExtensionWithConfigObject.java b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/testextensions/ExtensionWithConfigObject.java new file mode 100644 index 00000000000..0afa1bb55fd --- /dev/null +++ b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/testextensions/ExtensionWithConfigObject.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.boot.system.testextensions; + +import org.eclipse.edc.runtime.metamodel.annotation.Configuration; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.runtime.metamodel.annotation.Settings; +import org.eclipse.edc.spi.system.ServiceExtension; + +public class ExtensionWithConfigObject implements ServiceExtension { + + @Configuration + private ConfigurationObject configurationObject; + + @Configuration // is optional because all @Settings within are optionsl + private OptionalConfigurationObject optionalConfigurationObject; + + public ConfigurationObject getConfigurationObject() { + return configurationObject; + } + + @Settings + public static class OptionalConfigurationObject { + @Setting(key = "optional.key", required = false) + private String val; + + public String getVal() { + return val; + } + } +} diff --git a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/testextensions/ExtensionWithConfigValue.java b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/testextensions/ExtensionWithConfigValue.java new file mode 100644 index 00000000000..f8249550252 --- /dev/null +++ b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/testextensions/ExtensionWithConfigValue.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.boot.system.testextensions; + +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.system.ServiceExtension; + +public class ExtensionWithConfigValue implements ServiceExtension { + + public static final String DEFAULT_VALUE = "default-value"; + @Setting(key = "test.key") + private String requiredVal; + + @Setting(key = "test.optional.value", required = false) + private Long optionalVal; + + @Setting(key = "test.key2", defaultValue = DEFAULT_VALUE) + private String requiredValWithDefault; + + @Setting(key = "test.key3", defaultValue = DEFAULT_VALUE) + private Double requiredDoubleVal; +} diff --git a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/testextensions/RequiredDependentExtension.java b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/testextensions/RequiredDependentExtension.java index e31b473ce9b..178caab800b 100644 --- a/core/common/boot/src/test/java/org/eclipse/edc/boot/system/testextensions/RequiredDependentExtension.java +++ b/core/common/boot/src/test/java/org/eclipse/edc/boot/system/testextensions/RequiredDependentExtension.java @@ -19,6 +19,10 @@ import org.eclipse.edc.spi.system.ServiceExtension; public class RequiredDependentExtension implements ServiceExtension { + public TestObject getTestObject() { + return testObject; + } + @Inject private TestObject testObject; } diff --git a/core/common/connector-core/src/main/java/org/eclipse/edc/connector/core/CoreServicesExtension.java b/core/common/connector-core/src/main/java/org/eclipse/edc/connector/core/CoreServicesExtension.java index 9b9313cdd85..b887cc84807 100644 --- a/core/common/connector-core/src/main/java/org/eclipse/edc/connector/core/CoreServicesExtension.java +++ b/core/common/connector-core/src/main/java/org/eclipse/edc/connector/core/CoreServicesExtension.java @@ -58,11 +58,12 @@ public class CoreServicesExtension implements ServiceExtension { public static final String NAME = "Core Services"; + @Setting(description = "The name of the claim key used to determine the participant identity", defaultValue = DEFAULT_IDENTITY_CLAIM_KEY) + public static final String EDC_AGENT_IDENTITY_KEY = "edc.agent.identity.key"; private static final String DEFAULT_EDC_HOSTNAME = "localhost"; - @Setting(value = "Connector hostname, which e.g. is used in referer urls", defaultValue = DEFAULT_EDC_HOSTNAME) public static final String EDC_HOSTNAME = "edc.hostname"; - @Setting(value = "The name of the claim key used to determine the participant identity", defaultValue = DEFAULT_IDENTITY_CLAIM_KEY) - public static final String EDC_AGENT_IDENTITY_KEY = "edc.agent.identity.key"; + @Setting(description = "Connector hostname, which e.g. is used in referer urls", defaultValue = DEFAULT_EDC_HOSTNAME, key = EDC_HOSTNAME, warnOnMissingConfig = true) + public static String hostname; @Inject private EventExecutorServiceContainer eventExecutorServiceContainer; @@ -107,10 +108,6 @@ public TypeManager typeManager() { @Provider public Hostname hostname(ServiceExtensionContext context) { - var hostname = context.getSetting(EDC_HOSTNAME, DEFAULT_EDC_HOSTNAME); - if (DEFAULT_EDC_HOSTNAME.equals(hostname)) { - context.getMonitor().warning(String.format("Settings: No setting found for key '%s'. Using default value '%s'", EDC_HOSTNAME, DEFAULT_EDC_HOSTNAME)); - } return () -> hostname; } diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtensionTest.java b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtensionTest.java index b35a6d25b2c..d3d2fe55da8 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtensionTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtensionTest.java @@ -72,7 +72,7 @@ void assertReaperThreadRunning(IdentityAndTrustExtension extension, ServiceExten extension.initialize(context); extension.start(); - await().atLeast(Duration.ofSeconds(1)) // that's the initial delay + await().atMost(Duration.ofSeconds(2)) .untilAsserted(() -> verify(storeMock, atLeastOnce()).deleteExpired()); } diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-client/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/client/StsRemoteClientConfigurationExtension.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-client/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/client/StsRemoteClientConfigurationExtension.java index 4cbfc6bda0d..3797fdf851d 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-client/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/client/StsRemoteClientConfigurationExtension.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-client/src/main/java/org/eclipse/edc/iam/identitytrust/sts/remote/client/StsRemoteClientConfigurationExtension.java @@ -15,10 +15,12 @@ package org.eclipse.edc.iam.identitytrust.sts.remote.client; import org.eclipse.edc.iam.identitytrust.sts.remote.StsRemoteClientConfiguration; +import org.eclipse.edc.runtime.metamodel.annotation.Configuration; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.runtime.metamodel.annotation.Settings; import org.eclipse.edc.spi.security.Vault; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; @@ -29,20 +31,13 @@ @Extension(StsRemoteClientConfigurationExtension.NAME) public class StsRemoteClientConfigurationExtension implements ServiceExtension { - @Setting(value = "STS OAuth2 endpoint for requesting a token") - public static final String TOKEN_URL = "edc.iam.sts.oauth.token.url"; - - @Setting(value = "STS OAuth2 client id") - public static final String CLIENT_ID = "edc.iam.sts.oauth.client.id"; - - @Setting(value = "Vault alias of STS OAuth2 client secret") - public static final String CLIENT_SECRET_ALIAS = "edc.iam.sts.oauth.client.secret.alias"; - protected static final String NAME = "Sts remote client configuration extension"; - @Inject private Vault vault; + @Configuration + private StsClientConfig clientConfig; + @Override public String name() { return NAME; @@ -50,12 +45,15 @@ public String name() { @Provider public StsRemoteClientConfiguration clientConfiguration(ServiceExtensionContext context) { + return new StsRemoteClientConfiguration(clientConfig.tokenUrl(), clientConfig.clientId(), clientConfig.clientSecretAlias()); + } - var tokenUrl = context.getConfig().getString(TOKEN_URL); - var clientId = context.getConfig().getString(CLIENT_ID); - var clientSecretAlias = context.getConfig().getString(CLIENT_SECRET_ALIAS); - return new StsRemoteClientConfiguration(tokenUrl, clientId, clientSecretAlias); + @Settings + private record StsClientConfig( + @Setting(key = "edc.iam.sts.oauth.token.url", description = "STS OAuth2 endpoint for requesting a token") String tokenUrl, + @Setting(key = "edc.iam.sts.oauth.client.id", description = "STS OAuth2 client id") String clientId, + @Setting(key = "edc.iam.sts.oauth.client.secret.alias", description = "Vault alias of STS OAuth2 client secret") String clientSecretAlias) { } - } + diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-client/src/test/java/org/eclipse/edc/iam/identitytrust/sts/remote/client/StsRemoteClientConfigurationExtensionTest.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-client/src/test/java/org/eclipse/edc/iam/identitytrust/sts/remote/client/StsRemoteClientConfigurationExtensionTest.java index b0f642ea274..efbe79beacb 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-client/src/test/java/org/eclipse/edc/iam/identitytrust/sts/remote/client/StsRemoteClientConfigurationExtensionTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-remote-client/src/test/java/org/eclipse/edc/iam/identitytrust/sts/remote/client/StsRemoteClientConfigurationExtensionTest.java @@ -25,31 +25,31 @@ import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.iam.identitytrust.sts.remote.client.StsRemoteClientConfigurationExtension.CLIENT_ID; -import static org.eclipse.edc.iam.identitytrust.sts.remote.client.StsRemoteClientConfigurationExtension.CLIENT_SECRET_ALIAS; -import static org.eclipse.edc.iam.identitytrust.sts.remote.client.StsRemoteClientConfigurationExtension.TOKEN_URL; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @ExtendWith(DependencyInjectionExtension.class) public class StsRemoteClientConfigurationExtensionTest { + private final String tokenUrl = "http://tokenUrl"; + private final String clientId = "clientId"; + private final String secretAlias = "secretAlias"; + @BeforeEach void setup(ServiceExtensionContext context) { context.registerService(Vault.class, mock()); + + var configMap = Map.of("edc.iam.sts.oauth.token.url", tokenUrl, + "edc.iam.sts.oauth.client.id", clientId, + "edc.iam.sts.oauth.client.secret.alias", secretAlias); + var config = ConfigFactory.fromMap(configMap); + + when(context.getConfig()).thenReturn(config); } @Test void initialize(StsRemoteClientConfigurationExtension extension, ServiceExtensionContext context) { - var tokenUrl = "http://tokenUrl"; - var clientId = "clientId"; - var secretAlias = "secretAlias"; - - var configMap = Map.of(TOKEN_URL, tokenUrl, CLIENT_ID, clientId, CLIENT_SECRET_ALIAS, secretAlias); - var config = ConfigFactory.fromMap(configMap); - - when(context.getConfig()).thenReturn(config); extension.initialize(context); assertThat(extension.clientConfiguration(context)).isNotNull() @@ -59,5 +59,5 @@ void initialize(StsRemoteClientConfigurationExtension extension, ServiceExtensio assertThat(configuration.clientSecretAlias()).isEqualTo(secretAlias); }); } - + }