From 97023aaad6650534c366f08066f122d5ed76afdf Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Sun, 28 Apr 2024 23:27:57 +0200 Subject: [PATCH 01/20] Support for inherited annotations in types. Fixed javadoc in types. Signed-off-by: Tomas Langer --- .../io/helidon/common/types/Annotated.java | 33 ++- .../common/types/AnnotationBlueprint.java | 46 ++++- .../common/types/TypeInfoBlueprint.java | 7 +- .../common/types/TypeNameBlueprint.java | 11 +- .../helidon/common/types/TypeNameSupport.java | 53 ++++- .../io/helidon/common/types/TypeNames.java | 63 +++--- .../types/TypedElementInfoBlueprint.java | 8 +- .../codegen/apt/AptTypeInfoFactory.java | 188 +++++++++++++++++- .../codegen/classmodel/ContentSupport.java | 6 + .../io/helidon/common/types/Annotated.java | 33 ++- .../io/helidon/common/types/TypeInfo.java | 139 +++++++++++-- .../io/helidon/common/types/TypeNames.java | 5 + .../common/types/TypedElementInfo.java | 141 +++++++++++-- 13 files changed, 636 insertions(+), 97 deletions(-) diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/Annotated.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/Annotated.java index 3be2ab6f8ae..07f13ac5c29 100644 --- a/builder/tests/common-types/src/main/java/io/helidon/common/types/Annotated.java +++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/Annotated.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -16,6 +16,7 @@ package io.helidon.common.types; +import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; @@ -27,14 +28,38 @@ */ public interface Annotated { /** - * The list of known annotations for this element. Note that "known" implies that the annotation is visible, which depends - * upon the context in which it was build. + * List of declared and known annotations for this element. + * Note that "known" implies that the annotation is visible, which depends + * upon the context in which it was build (such as the {@link java.lang.annotation.Retention of the annotation}). * - * @return the list of annotations on this element + * @return the list of annotations declared on this element */ @Option.Singular List annotations(); + /** + * List of all inherited annotations for this element. Inherited annotations are annotations declared + * on annotations of this element that are also marked as {@link java.lang.annotation.Inherited}. + *

+ * The returned list does not contain {@link #annotations()}. If a meta-annotation is present on multiple + * annotations, it will be returned once for each such declaration. + * + * @return list of all meta annotations of this element + */ + @Option.Singular + List inheritedAnnotations(); + + /** + * List of all annotations - both {@link #annotations()} and {@link #inheritedAnnotations()}. + * + * @return list of all annotations valid for this element + */ + default List allAnnotations() { + List result = new ArrayList<>(annotations()); + result.addAll(inheritedAnnotations()); + return List.copyOf(result); + } + /** * Find an annotation on this annotated type. * diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/AnnotationBlueprint.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/AnnotationBlueprint.java index 26e86945a7b..3017fdd6526 100644 --- a/builder/tests/common-types/src/main/java/io/helidon/common/types/AnnotationBlueprint.java +++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/AnnotationBlueprint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -58,6 +58,9 @@ @Prototype.Implement("java.lang.Comparable") interface AnnotationBlueprint { + /** + * The "{@code value}" property name. + */ String VALUE_PROPERTY = "value"; /** @@ -88,11 +91,11 @@ default Optional value() { /** * Get a value of an annotation property. * - * @param name name of the annotation property + * @param property name of the annotation property * @return string value of the property */ - default Optional getValue(String name) { - return AnnotationSupport.asString(typeName(), values(), name); + default Optional getValue(String property) { + return AnnotationSupport.asString(typeName(), values(), property); } /** @@ -111,6 +114,7 @@ default Optional objectValue() { * The type can be either String, or any primitive type, or {@link io.helidon.common.types.Annotation}, * or list of these. * + * @param property name of the annotation property * @return object value */ default Optional objectValue(String property) { @@ -129,6 +133,7 @@ default Optional stringValue() { /** * Typed value of a named property. * + * @param property name of the annotation property * @return value if present */ default Optional stringValue(String property) { @@ -149,6 +154,7 @@ default Optional> stringValues() { * Typed values of a property that is defined as an array. * This will also work for a single values property. * + * @param property name of the annotation property * @return list of defined values if present */ default Optional> stringValues(String property) { @@ -167,6 +173,7 @@ default Optional intValue() { /** * Typed value of a named property. * + * @param property name of the annotation property * @return value if present */ default Optional intValue(String property) { @@ -187,6 +194,7 @@ default Optional> intValues() { * Typed values of a property that is defined as an array. * This will also work for a single values property. * + * @param property name of the annotation property * @return list of defined values if present */ default Optional> intValues(String property) { @@ -205,6 +213,7 @@ default Optional longValue() { /** * Typed value of a named property. * + * @param property name of the annotation property * @return value if present */ default Optional longValue(String property) { @@ -225,6 +234,7 @@ default Optional> longValues() { * Typed values of a property that is defined as an array. * This will also work for a single values property. * + * @param property name of the annotation property * @return list of defined values if present */ default Optional> longValues(String property) { @@ -243,6 +253,7 @@ default Optional booleanValue() { /** * Typed value of a named property. * + * @param property name of the annotation property * @return value if present */ default Optional booleanValue(String property) { @@ -263,6 +274,7 @@ default Optional> booleanValues() { * Typed values of a property that is defined as an array. * This will also work for a single values property. * + * @param property name of the annotation property * @return list of defined values if present */ default Optional> booleanValues(String property) { @@ -281,6 +293,7 @@ default Optional byteValue() { /** * Typed value of a named property. * + * @param property name of the annotation property * @return value if present */ default Optional byteValue(String property) { @@ -301,6 +314,7 @@ default Optional> byteValues() { * Typed values of a property that is defined as an array. * This will also work for a single values property. * + * @param property name of the annotation property * @return list of defined values if present */ default Optional> byteValues(String property) { @@ -319,6 +333,7 @@ default Optional charValue() { /** * Typed value of a named property. * + * @param property name of the annotation property * @return value if present */ default Optional charValue(String property) { @@ -339,6 +354,7 @@ default Optional> charValues() { * Typed values of a property that is defined as an array. * This will also work for a single values property. * + * @param property name of the annotation property * @return list of defined values if present */ default Optional> charValues(String property) { @@ -357,6 +373,7 @@ default Optional shortValue() { /** * Typed value of a named property. * + * @param property name of the annotation property * @return value if present */ default Optional shortValue(String property) { @@ -377,6 +394,7 @@ default Optional> shortValues() { * Typed values of a property that is defined as an array. * This will also work for a single values property. * + * @param property name of the annotation property * @return list of defined values if present */ default Optional> shortValues(String property) { @@ -395,6 +413,7 @@ default Optional floatValue() { /** * Typed value of a named property. * + * @param property name of the annotation property * @return value if present */ default Optional floatValue(String property) { @@ -415,6 +434,7 @@ default Optional> floatValues() { * Typed values of a property that is defined as an array. * This will also work for a single values property. * + * @param property name of the annotation property * @return list of defined values if present */ default Optional> floatValues(String property) { @@ -433,6 +453,7 @@ default Optional doubleValue() { /** * Typed value of a named property. * + * @param property name of the annotation property * @return value if present */ default Optional doubleValue(String property) { @@ -453,6 +474,7 @@ default Optional> doubleValues() { * Typed values of a property that is defined as an array. * This will also work for a single values property. * + * @param property name of the annotation property * @return list of defined values if present */ default Optional> doubleValues(String property) { @@ -471,6 +493,7 @@ default Optional> classValue() { /** * Typed value of a named property. * + * @param property name of the annotation property * @return value if present */ default Optional> classValue(String property) { @@ -491,6 +514,7 @@ default Optional>> classValues() { * Typed values of a property that is defined as an array. * This will also work for a single values property. * + * @param property name of the annotation property * @return list of defined values if present */ default Optional>> classValues(String property) { @@ -511,6 +535,7 @@ default Optional typeValue() { * Typed value of a named property. * Alternative for {@link #classValue(String)}. * + * @param property name of the annotation property * @return value if present */ default Optional typeValue(String property) { @@ -533,6 +558,7 @@ default Optional> typeValues() { * This will also work for a single values property. * Alternative for {@link #classValues(String)}. * + * @param property name of the annotation property * @return list of defined values if present */ default Optional> typeValues(String property) { @@ -551,6 +577,7 @@ default Optional annotationValue() { /** * Typed value of a named property. * + * @param property name of the annotation property * @return value if present */ default Optional annotationValue(String property) { @@ -571,6 +598,7 @@ default Optional> annotationValues() { * Typed values of a property that is defined as an array. * This will also work for a single values property. * + * @param property name of the annotation property * @return list of defined values if present */ default Optional> annotationValues(String property) { @@ -591,8 +619,9 @@ default > Optional enumValue(Class type) { /** * Typed value of a named property. * - * @param type class of the enumeration - * @param type of the enumeration + * @param property name of the annotation property + * @param type class of the enumeration + * @param type of the enumeration * @return value if present */ default > Optional enumValue(String property, Class type) { @@ -615,8 +644,9 @@ default > Optional> enumValues(Class type) { * Typed values of a property that is defined as an array. * This will also work for a single values property. * - * @param type class of the enumeration - * @param type of the enumeration + * @param property name of the annotation property + * @param type class of the enumeration + * @param type of the enumeration * @return list of defined values if present */ default > Optional> enumValues(String property, Class type) { diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeInfoBlueprint.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeInfoBlueprint.java index 8820e70fe50..4bb3da96300 100644 --- a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeInfoBlueprint.java +++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeInfoBlueprint.java @@ -58,7 +58,7 @@ interface TypeInfoBlueprint extends Annotated { * * @return the type element kind. * @see io.helidon.common.types.TypeValues#KIND_CLASS and other constants on this class prefixed with {@code TYPE} - * @deprecated use {@link #kind()} instead + * @deprecated use {@link io.helidon.common.types.TypeInfo#kind()} instead */ @Option.Required @Option.Deprecated("kind") @@ -186,7 +186,7 @@ default Optional metaAnnotation(TypeName annotation, TypeName metaAn * * @return element modifiers * @see io.helidon.common.types.TypeValues#MODIFIER_PUBLIC and other constants prefixed with {@code MODIFIER} - * @deprecated use {@link #elementModifiers()} instead + * @deprecated use {@link io.helidon.common.types.TypeInfo#elementModifiers()} instead */ @Option.Singular @Option.Redundant @@ -229,7 +229,8 @@ default Optional metaAnnotation(TypeName annotation, TypeName metaAn Optional originatingElement(); /** - * Uses {@link #referencedModuleNames()} to determine if the module name is known for the given type. + * Uses {@link io.helidon.common.types.TypeInfo#referencedModuleNames()} to determine if the module name is known for the + * given type. * * @param typeName the type name to lookup * @return the module name if it is known diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameBlueprint.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameBlueprint.java index 0db490e65c4..1d9d5fa7118 100644 --- a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameBlueprint.java +++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameBlueprint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -178,6 +178,15 @@ default boolean isOptional() { return TypeNames.OPTIONAL.name().equals(name()); } + /** + * Indicates whether this type is a {@link java.util.function.Supplier}. + * + * @return if this is a supplier + */ + default boolean isSupplier() { + return TypeNames.SUPPLIER.fqName().equals(fqName()); + } + /** * Simple class name with generic declaration (if part of this name). * diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameSupport.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameSupport.java index 72ee4546018..e8a9d38809b 100644 --- a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameSupport.java +++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNameSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -20,14 +20,58 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.stream.Stream; import io.helidon.builder.api.Prototype; -import static io.helidon.common.types.TypeNames.PRIMITIVES; - final class TypeNameSupport { + private static final TypeName PRIMITIVE_BOOLEAN = TypeName.create(boolean.class); + private static final TypeName PRIMITIVE_BYTE = TypeName.create(byte.class); + private static final TypeName PRIMITIVE_SHORT = TypeName.create(short.class); + private static final TypeName PRIMITIVE_INT = TypeName.create(int.class); + private static final TypeName PRIMITIVE_LONG = TypeName.create(long.class); + private static final TypeName PRIMITIVE_CHAR = TypeName.create(char.class); + private static final TypeName PRIMITIVE_FLOAT = TypeName.create(float.class); + private static final TypeName PRIMITIVE_DOUBLE = TypeName.create(double.class); + private static final TypeName PRIMITIVE_VOID = TypeName.create(void.class); + private static final TypeName BOXED_BOOLEAN = TypeName.create(Boolean.class); + private static final TypeName BOXED_BYTE = TypeName.create(Byte.class); + private static final TypeName BOXED_SHORT = TypeName.create(Short.class); + private static final TypeName BOXED_INT = TypeName.create(Integer.class); + private static final TypeName BOXED_LONG = TypeName.create(Long.class); + private static final TypeName BOXED_CHAR = TypeName.create(Character.class); + private static final TypeName BOXED_FLOAT = TypeName.create(Float.class); + private static final TypeName BOXED_DOUBLE = TypeName.create(Double.class); + private static final TypeName BOXED_VOID = TypeName.create(Void.class); + + // as type names need this class to be initialized, let's have a copy of these + private static final Map PRIMITIVES = Map.of( + "boolean", PRIMITIVE_BOOLEAN, + "byte", PRIMITIVE_BYTE, + "short", PRIMITIVE_SHORT, + "int", PRIMITIVE_INT, + "long", PRIMITIVE_LONG, + "char", PRIMITIVE_CHAR, + "float", PRIMITIVE_FLOAT, + "double", PRIMITIVE_DOUBLE, + "void", PRIMITIVE_VOID + ); + + private static final Map BOXED_TYPES = Map.of( + PRIMITIVE_BOOLEAN, BOXED_BOOLEAN, + PRIMITIVE_BYTE, BOXED_BYTE, + PRIMITIVE_SHORT, BOXED_SHORT, + PRIMITIVE_INT, BOXED_INT, + PRIMITIVE_LONG, BOXED_LONG, + PRIMITIVE_CHAR, BOXED_CHAR, + PRIMITIVE_FLOAT, BOXED_FLOAT, + PRIMITIVE_DOUBLE, BOXED_DOUBLE, + PRIMITIVE_VOID, BOXED_VOID + ); + private TypeNameSupport() { } @@ -55,7 +99,8 @@ static int compareTo(TypeName typeName, TypeName o) { */ @Prototype.PrototypeMethod static TypeName boxed(TypeName original) { - return TypeNames.boxed(original); + return Optional.ofNullable(BOXED_TYPES.get(original)) + .orElse(original); } @Prototype.PrototypeMethod diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNames.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNames.java index 20e860c9093..62fe59da5a3 100644 --- a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNames.java +++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypeNames.java @@ -16,6 +16,8 @@ package io.helidon.common.types; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; import java.time.Duration; import java.util.Collection; import java.util.List; @@ -24,6 +26,9 @@ import java.util.Set; import java.util.function.Supplier; +import io.helidon.common.Generated; +import io.helidon.common.GenericType; + /** * Commonly used type names. */ @@ -64,6 +69,15 @@ public final class TypeNames { * Type name for {@link java.time.Duration}. */ public static final TypeName DURATION = TypeName.create(Duration.class); + /** + * Type name for {@link java.lang.annotation.Retention}. + */ + public static final TypeName RETENTION = TypeName.create(Retention.class); + /** + * Type name for {@link java.lang.annotation.Inherited}. + */ + public static final TypeName INHERITED = TypeName.create(Inherited.class); + /* Primitive types and their boxed counterparts */ @@ -151,36 +165,27 @@ public final class TypeNames { * Type name of typed element info. */ public static final TypeName TYPED_ELEMENT_INFO = TypeName.create(TypedElementInfo.class); - - static final Map PRIMITIVES = Map.of( - "boolean", PRIMITIVE_BOOLEAN, - "byte", PRIMITIVE_BYTE, - "short", PRIMITIVE_SHORT, - "int", PRIMITIVE_INT, - "long", PRIMITIVE_LONG, - "char", PRIMITIVE_CHAR, - "float", PRIMITIVE_FLOAT, - "double", PRIMITIVE_DOUBLE, - "void", PRIMITIVE_VOID - ); - - private static final Map BOXED_TYPES = Map.of( - PRIMITIVE_BOOLEAN, BOXED_BOOLEAN, - PRIMITIVE_BYTE, BOXED_BYTE, - PRIMITIVE_SHORT, BOXED_SHORT, - PRIMITIVE_INT, BOXED_INT, - PRIMITIVE_LONG, BOXED_LONG, - PRIMITIVE_CHAR, BOXED_CHAR, - PRIMITIVE_FLOAT, BOXED_FLOAT, - PRIMITIVE_DOUBLE, BOXED_DOUBLE, - PRIMITIVE_VOID, BOXED_VOID - ); + /** + * Helidon annotation type. + */ + public static final TypeName ANNOTATION = TypeName.create(Annotation.class); + /** + * Helidon element kind (enum). + */ + public static final TypeName ELEMENT_KIND = TypeName.create(ElementKind.class); + /** + * Helidon access modifier (enum). + */ + public static final TypeName ACCESS_MODIFIER = TypeName.create(AccessModifier.class); + /** + * Helidon Generated annotation type. + */ + public static final TypeName GENERATED = TypeName.create(Generated.class); + /** + * Helidon {@link io.helidon.common.GenericType}. + */ + public static final TypeName GENERIC_TYPE = TypeName.create(GenericType.class); private TypeNames() { } - - static TypeName boxed(TypeName original) { - return Optional.ofNullable(BOXED_TYPES.get(original)) - .orElse(original); - } } diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypedElementInfoBlueprint.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypedElementInfoBlueprint.java index e5a57c7f048..b52ba88a4bd 100644 --- a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypedElementInfoBlueprint.java +++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypedElementInfoBlueprint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -59,7 +59,7 @@ interface TypedElementInfoBlueprint extends Annotated { * * @return the element kind * @see io.helidon.common.types.TypeInfo - * @deprecated use {@link #kind()} instead + * @deprecated use {@link io.helidon.common.types.TypedElementInfo#kind()} instead */ @Option.Required @Option.Deprecated("kind") @@ -84,7 +84,7 @@ interface TypedElementInfoBlueprint extends Annotated { Optional defaultValue(); /** - * The list of known annotations on the type name referenced by {@link #typeName()}. + * The list of known annotations on the type name referenced by {@link io.helidon.common.types.TypedElementInfo#typeName()}. * * @return the list of annotations on this element's (return) type. */ @@ -104,7 +104,7 @@ interface TypedElementInfoBlueprint extends Annotated { * * @return element modifiers * @see io.helidon.common.types.TypeInfo - * @deprecated use {@link #elementModifiers()} instead + * @deprecated use {@link io.helidon.common.types.TypedElementInfo#elementModifiers()} instead */ @Option.Singular @Option.Redundant diff --git a/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeInfoFactory.java b/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeInfoFactory.java index 4203495ee7d..e3e83f8e950 100644 --- a/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeInfoFactory.java +++ b/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeInfoFactory.java @@ -152,11 +152,124 @@ public static Optional create(AptContext ctx, * {@link io.helidon.common.types.ElementKind#CONSTRUCTOR}, or {@link io.helidon.common.types.ElementKind#PARAMETER} * then this method may return empty. * + * @param ctx annotation processing context + * @param processedType the type that is being processed, to avoid infinite loop when looking for inherited annotations + * @param v the element (from annotation processing) + * @param elements the elements + * @return the created instance + */ + public static Optional createTypedElementInfoFromElement(AptContext ctx, + TypeName processedType, + Element v, + Elements elements) { + TypeName type = AptTypeFactory.createTypeName(v).orElse(null); + TypeMirror typeMirror = null; + String defaultValue = null; + List params = List.of(); + List componentTypeNames = List.of(); + List elementTypeAnnotations = List.of(); + + Set modifierNames = v.getModifiers() + .stream() + .map(Modifier::toString) + .collect(Collectors.toSet()); + Set thrownChecked = Set.of(); + + if (v instanceof ExecutableElement ee) { + typeMirror = Objects.requireNonNull(ee.getReturnType()); + params = ee.getParameters().stream() + .map(it -> createTypedElementInfoFromElement(ctx, processedType, it, elements).orElseThrow(() -> { + return new CodegenException("Failed to create element info for parameter: " + it + ", either it uses " + + "invalid type, or it was removed by an element mapper. This would" + + " result in an invalid TypeInfo model.", + it); + })) + .toList(); + AnnotationValue annotationValue = ee.getDefaultValue(); + defaultValue = (annotationValue == null) ? null + : String.valueOf(annotationValue.accept(new ToAnnotationValueVisitor(elements) + .mapBooleanToNull(true) + .mapVoidToNull(true) + .mapBlankArrayToNull(true) + .mapEmptyStringToNull(true) + .mapToSourceDeclaration(true), null)); + + thrownChecked = ee.getThrownTypes() + .stream() + .filter(it -> isCheckedException(ctx, it)) + .flatMap(it -> AptTypeFactory.createTypeName(it).stream()) + .collect(Collectors.toSet()); + } else if (v instanceof VariableElement ve) { + typeMirror = Objects.requireNonNull(ve.asType()); + } + + if (typeMirror != null) { + if (type == null) { + Element element = ctx.aptEnv().getTypeUtils().asElement(typeMirror); + if (element instanceof TypeElement typeElement) { + type = AptTypeFactory.createTypeName(typeElement, typeMirror) + .orElse(createFromGenericDeclaration(typeMirror.toString())); + } else { + type = AptTypeFactory.createTypeName(typeMirror) + .orElse(createFromGenericDeclaration(typeMirror.toString())); + } + } + if (typeMirror instanceof DeclaredType) { + List args = ((DeclaredType) typeMirror).getTypeArguments(); + componentTypeNames = args.stream() + .map(AptTypeFactory::createTypeName) + .filter(Optional::isPresent) + .map(Optional::orElseThrow) + .collect(Collectors.toList()); + elementTypeAnnotations = + createAnnotations(ctx, ((DeclaredType) typeMirror).asElement(), elements); + } + } + String javadoc = ctx.aptEnv().getElementUtils().getDocComment(v); + javadoc = javadoc == null || javadoc.isBlank() ? "" : javadoc; + + List annotations = createAnnotations(ctx, v, elements); + List inheritedAnnotations = createInheritedAnnotations(ctx, processedType.genericTypeName(), annotations); + + TypedElementInfo.Builder builder = TypedElementInfo.builder() + .description(javadoc) + .typeName(type) + .componentTypes(componentTypeNames) + .elementName(v.getSimpleName().toString()) + .kind(kind(v.getKind())) + .annotations(annotations) + .elementTypeAnnotations(elementTypeAnnotations) + .inheritedAnnotations(inheritedAnnotations) + .elementModifiers(modifiers(ctx, modifierNames)) + .accessModifier(accessModifier(modifierNames)) + .throwsChecked(thrownChecked) + .parameterArguments(params) + .originatingElement(v); + AptTypeFactory.createTypeName(v.getEnclosingElement()).ifPresent(builder::enclosingType); + Optional.ofNullable(defaultValue).ifPresent(builder::defaultValue); + + return mapElement(ctx, builder.build()); + } + + /** + * Creates an instance of a {@link io.helidon.common.types.TypedElementInfo} given its type and variable element from + * annotation processing. If the passed in element is not a {@link io.helidon.common.types.ElementKind#FIELD}, + * {@link io.helidon.common.types.ElementKind#METHOD}, + * {@link io.helidon.common.types.ElementKind#CONSTRUCTOR}, or {@link io.helidon.common.types.ElementKind#PARAMETER} + * then this method may return empty. + *

+ * This method does not include inherited annotations. + * * @param ctx annotation processing context * @param v the element (from annotation processing) * @param elements the elements * @return the created instance + * @deprecated use + * {@link #createTypedElementInfoFromElement(AptContext, io.helidon.common.types.TypeName, + * javax.lang.model.element.Element, javax.lang.model.util.Elements)} + * instead */ + @Deprecated(since = "4.0.10", forRemoval = true) public static Optional createTypedElementInfoFromElement(AptContext ctx, Element v, Elements elements) { @@ -289,10 +402,11 @@ private static Optional create(AptContext ctx, Elements elementUtils = ctx.aptEnv().getElementUtils(); try { - List annotations = - List.copyOf(createAnnotations(ctx, - elementUtils.getTypeElement(genericTypeName.resolvedName()), - elementUtils)); + List annotations = createAnnotations(ctx, + elementUtils.getTypeElement(genericTypeName.resolvedName()), + elementUtils); + List inheritedAnnotations = createInheritedAnnotations(ctx, genericTypeName, annotations); + Set annotationsOnTypeOrElements = new HashSet<>(); annotations.stream() .map(Annotation::typeName) @@ -302,7 +416,7 @@ private static Optional create(AptContext ctx, List otherElements = new ArrayList<>(); typeElement.getEnclosedElements() .stream() - .flatMap(it -> createTypedElementInfoFromElement(ctx, it, elementUtils).stream()) + .flatMap(it -> createTypedElementInfoFromElement(ctx, genericTypeName, it, elementUtils).stream()) .forEach(it -> { if (elementPredicate.test(it)) { elementsWeCareAbout.add(it); @@ -326,6 +440,7 @@ private static Optional create(AptContext ctx, .typeName(typeName) .kind(kind(typeElement.getKind())) .annotations(annotations) + .inheritedAnnotations(inheritedAnnotations) .elementModifiers(modifiers(ctx, modifiers)) .accessModifier(accessModifier(modifiers)) .elementInfo(elementsWeCareAbout) @@ -436,6 +551,69 @@ private static List createAnnotations(AptContext ctx, Element elemen .toList(); } + private static List createInheritedAnnotations(AptContext ctx, + TypeName processedType, + List elementAnnotations) { + List result = new ArrayList<>(); + Set processedTypes = new HashSet<>(); + + // for each annotation on this type, find its type info, and collect all meta annotations marked as @Inherited + for (Annotation elementAnnotation : elementAnnotations) { + if (processedType.equals(elementAnnotation.typeName())) { + // self reference + continue; + } + addInherited(ctx, result, processedTypes, elementAnnotation, false); + } + return result; + } + + private static void addInherited(AptContext ctx, + List result, + Set processedTypes, + Annotation annotation, + boolean shouldAdd) { + TypeName annotationType = annotation.typeName(); + if (!processedTypes.add(annotationType)) { + return; + } + + if (!annotationFilter(annotation)) { + return; + } + + if (annotationType.equals(TypeNames.INHERITED)) { + return; + } + + Optional found = create(ctx, annotationType, it -> false); + + if (found.isEmpty()) { + ctx.logger().log(System.Logger.Level.INFO, "Annotation " + annotationType + + " not available, cannot obtain inherited annotations"); + return; + } + + TypeInfo annoTypeInfo = found.get(); + + if (annoTypeInfo.hasAnnotation(TypeNames.INHERITED)) { + // first level annotations should not be added as inherited + if (shouldAdd) { + // add this annotation + result.add(annotation); + } + + // check my meta annotations + for (Annotation metaAnnotation : annoTypeInfo.annotations()) { + // if self annotated, ignore + if (annotationType.equals(metaAnnotation.typeName())) { + continue; + } + addInherited(ctx, result, processedTypes, metaAnnotation, true); + } + } + } + /** * Converts the provided modifiers to the corresponding set of modifier names. * diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ContentSupport.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ContentSupport.java index d8725fab180..58b513935c3 100644 --- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ContentSupport.java +++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/ContentSupport.java @@ -66,6 +66,12 @@ static void addCreateElement(ContentBuilder contentBuilder, TypedElementInfo .addContentLine(")"); } + for (Annotation annotation : element.inheritedAnnotations()) { + contentBuilder.addContent(".addInheritedAnnotation(") + .addContentCreate(annotation) + .addContentLine(")"); + } + AccessModifier accessModifier = element.accessModifier(); if (accessModifier != AccessModifier.PACKAGE_PRIVATE) { contentBuilder.addContent(".accessModifier(") diff --git a/common/types/src/main/java/io/helidon/common/types/Annotated.java b/common/types/src/main/java/io/helidon/common/types/Annotated.java index 3be2ab6f8ae..07f13ac5c29 100644 --- a/common/types/src/main/java/io/helidon/common/types/Annotated.java +++ b/common/types/src/main/java/io/helidon/common/types/Annotated.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -16,6 +16,7 @@ package io.helidon.common.types; +import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; @@ -27,14 +28,38 @@ */ public interface Annotated { /** - * The list of known annotations for this element. Note that "known" implies that the annotation is visible, which depends - * upon the context in which it was build. + * List of declared and known annotations for this element. + * Note that "known" implies that the annotation is visible, which depends + * upon the context in which it was build (such as the {@link java.lang.annotation.Retention of the annotation}). * - * @return the list of annotations on this element + * @return the list of annotations declared on this element */ @Option.Singular List annotations(); + /** + * List of all inherited annotations for this element. Inherited annotations are annotations declared + * on annotations of this element that are also marked as {@link java.lang.annotation.Inherited}. + *

+ * The returned list does not contain {@link #annotations()}. If a meta-annotation is present on multiple + * annotations, it will be returned once for each such declaration. + * + * @return list of all meta annotations of this element + */ + @Option.Singular + List inheritedAnnotations(); + + /** + * List of all annotations - both {@link #annotations()} and {@link #inheritedAnnotations()}. + * + * @return list of all annotations valid for this element + */ + default List allAnnotations() { + List result = new ArrayList<>(annotations()); + result.addAll(inheritedAnnotations()); + return List.copyOf(result); + } + /** * Find an annotation on this annotated type. * diff --git a/common/types/src/main/java/io/helidon/common/types/TypeInfo.java b/common/types/src/main/java/io/helidon/common/types/TypeInfo.java index 2a7c36c4667..d47b9f04df1 100644 --- a/common/types/src/main/java/io/helidon/common/types/TypeInfo.java +++ b/common/types/src/main/java/io/helidon/common/types/TypeInfo.java @@ -66,6 +66,7 @@ static TypeInfo.Builder builder(TypeInfo instance) { abstract class BuilderBase, PROTOTYPE extends TypeInfo> implements Prototype.Builder { private final List annotations = new ArrayList<>(); + private final List inheritedAnnotations = new ArrayList<>(); private final List interfaceTypeInfo = new ArrayList<>(); private final List elementInfo = new ArrayList<>(); private final List otherElementInfo = new ArrayList<>(); @@ -111,6 +112,7 @@ public BUILDER from(TypeInfo prototype) { module(prototype.module()); originatingElement(prototype.originatingElement()); addAnnotations(prototype.annotations()); + addInheritedAnnotations(prototype.inheritedAnnotations()); return self(); } @@ -137,6 +139,7 @@ public BUILDER from(TypeInfo.BuilderBase builder) { builder.module().ifPresent(this::module); builder.originatingElement().ifPresent(this::originatingElement); addAnnotations(builder.annotations()); + addInheritedAnnotations(builder.inheritedAnnotations()); return self(); } @@ -731,10 +734,11 @@ public BUILDER originatingElement(Object originatingElement) { } /** - * The list of known annotations for this element. Note that "known" implies that the annotation is visible, which depends - * upon the context in which it was build. + * List of declared and known annotations for this element. + * Note that "known" implies that the annotation is visible, which depends + * upon the context in which it was build (such as the {@link java.lang.annotation.Retention of the annotation}). * - * @param annotations the list of annotations on this element + * @param annotations the list of annotations declared on this element * @return updated builder instance * @see #annotations() */ @@ -746,10 +750,11 @@ public BUILDER annotations(List annotations) { } /** - * The list of known annotations for this element. Note that "known" implies that the annotation is visible, which depends - * upon the context in which it was build. + * List of declared and known annotations for this element. + * Note that "known" implies that the annotation is visible, which depends + * upon the context in which it was build (such as the {@link java.lang.annotation.Retention of the annotation}). * - * @param annotations the list of annotations on this element + * @param annotations the list of annotations declared on this element * @return updated builder instance * @see #annotations() */ @@ -760,10 +765,11 @@ public BUILDER addAnnotations(List annotations) { } /** - * The list of known annotations for this element. Note that "known" implies that the annotation is visible, which depends - * upon the context in which it was build. + * List of declared and known annotations for this element. + * Note that "known" implies that the annotation is visible, which depends + * upon the context in which it was build (such as the {@link java.lang.annotation.Retention of the annotation}). * - * @param annotation the list of annotations on this element + * @param annotation the list of annotations declared on this element * @return updated builder instance * @see #annotations() */ @@ -774,10 +780,11 @@ public BUILDER addAnnotation(Annotation annotation) { } /** - * The list of known annotations for this element. Note that "known" implies that the annotation is visible, which depends - * upon the context in which it was build. + * List of declared and known annotations for this element. + * Note that "known" implies that the annotation is visible, which depends + * upon the context in which it was build (such as the {@link java.lang.annotation.Retention of the annotation}). * - * @param consumer the list of annotations on this element + * @param consumer the list of annotations declared on this element * @return updated builder instance * @see #annotations() */ @@ -789,6 +796,77 @@ public BUILDER addAnnotation(Consumer consumer) { return self(); } + /** + * List of all inherited annotations for this element. Inherited annotations are annotations declared + * on annotations of this element that are also marked as {@link java.lang.annotation.Inherited}. + *

+ * The returned list does not contain {@link #annotations()}. If a meta-annotation is present on multiple + * annotations, it will be returned once for each such declaration. + * + * @param inheritedAnnotations list of all meta annotations of this element + * @return updated builder instance + * @see #inheritedAnnotations() + */ + public BUILDER inheritedAnnotations(List inheritedAnnotations) { + Objects.requireNonNull(inheritedAnnotations); + this.inheritedAnnotations.clear(); + this.inheritedAnnotations.addAll(inheritedAnnotations); + return self(); + } + + /** + * List of all inherited annotations for this element. Inherited annotations are annotations declared + * on annotations of this element that are also marked as {@link java.lang.annotation.Inherited}. + *

+ * The returned list does not contain {@link #annotations()}. If a meta-annotation is present on multiple + * annotations, it will be returned once for each such declaration. + * + * @param inheritedAnnotations list of all meta annotations of this element + * @return updated builder instance + * @see #inheritedAnnotations() + */ + public BUILDER addInheritedAnnotations(List inheritedAnnotations) { + Objects.requireNonNull(inheritedAnnotations); + this.inheritedAnnotations.addAll(inheritedAnnotations); + return self(); + } + + /** + * List of all inherited annotations for this element. Inherited annotations are annotations declared + * on annotations of this element that are also marked as {@link java.lang.annotation.Inherited}. + *

+ * The returned list does not contain {@link #annotations()}. If a meta-annotation is present on multiple + * annotations, it will be returned once for each such declaration. + * + * @param inheritedAnnotation list of all meta annotations of this element + * @return updated builder instance + * @see #inheritedAnnotations() + */ + public BUILDER addInheritedAnnotation(Annotation inheritedAnnotation) { + Objects.requireNonNull(inheritedAnnotation); + this.inheritedAnnotations.add(inheritedAnnotation); + return self(); + } + + /** + * List of all inherited annotations for this element. Inherited annotations are annotations declared + * on annotations of this element that are also marked as {@link java.lang.annotation.Inherited}. + *

+ * The returned list does not contain {@link #annotations()}. If a meta-annotation is present on multiple + * annotations, it will be returned once for each such declaration. + * + * @param consumer list of all meta annotations of this element + * @return updated builder instance + * @see #inheritedAnnotations() + */ + public BUILDER addInheritedAnnotation(Consumer consumer) { + Objects.requireNonNull(consumer); + var builder = Annotation.builder(); + consumer.accept(builder); + this.inheritedAnnotations.add(builder.build()); + return self(); + } + /** * The type name. * @@ -955,8 +1033,9 @@ public Optional originatingElement() { } /** - * The list of known annotations for this element. Note that "known" implies that the annotation is visible, which depends - * upon the context in which it was build. + * List of declared and known annotations for this element. + * Note that "known" implies that the annotation is visible, which depends + * upon the context in which it was build (such as the {@link java.lang.annotation.Retention of the annotation}). * * @return the annotations */ @@ -964,6 +1043,19 @@ public List annotations() { return annotations; } + /** + * List of all inherited annotations for this element. Inherited annotations are annotations declared + * on annotations of this element that are also marked as {@link java.lang.annotation.Inherited}. + *

+ * The returned list does not contain {@link #annotations()}. If a meta-annotation is present on multiple + * annotations, it will be returned once for each such declaration. + * + * @return the inherited annotations + */ + public List inheritedAnnotations() { + return inheritedAnnotations; + } + @Override public String toString() { return "TypeInfoBuilder{" @@ -974,7 +1066,8 @@ public String toString() { + "elementModifiers=" + elementModifiers + "," + "accessModifier=" + accessModifier + "," + "module=" + module + "," - + "annotations=" + annotations + + "annotations=" + annotations + "," + + "inheritedAnnotations=" + inheritedAnnotations + "}"; } @@ -1067,6 +1160,7 @@ protected static class TypeInfoImpl implements TypeInfo { private final AccessModifier accessModifier; private final ElementKind kind; private final List annotations; + private final List inheritedAnnotations; private final List interfaceTypeInfo; private final List elementInfo; private final List otherElementInfo; @@ -1104,6 +1198,7 @@ protected TypeInfoImpl(TypeInfo.BuilderBase builder) { this.module = builder.module(); this.originatingElement = builder.originatingElement(); this.annotations = List.copyOf(builder.annotations()); + this.inheritedAnnotations = List.copyOf(builder.inheritedAnnotations()); } @Override @@ -1186,6 +1281,11 @@ public List annotations() { return annotations; } + @Override + public List inheritedAnnotations() { + return inheritedAnnotations; + } + @Override public String toString() { return "TypeInfo{" @@ -1196,7 +1296,8 @@ public String toString() { + "elementModifiers=" + elementModifiers + "," + "accessModifier=" + accessModifier + "," + "module=" + module + "," - + "annotations=" + annotations + + "annotations=" + annotations + "," + + "inheritedAnnotations=" + inheritedAnnotations + "}"; } @@ -1215,7 +1316,8 @@ public boolean equals(Object o) { && Objects.equals(elementModifiers, other.elementModifiers()) && Objects.equals(accessModifier, other.accessModifier()) && Objects.equals(module, other.module()) - && Objects.equals(annotations, other.annotations()); + && Objects.equals(annotations, other.annotations()) + && Objects.equals(inheritedAnnotations, other.inheritedAnnotations()); } @Override @@ -1227,7 +1329,8 @@ public int hashCode() { elementModifiers, accessModifier, module, - annotations); + annotations, + inheritedAnnotations); } } diff --git a/common/types/src/main/java/io/helidon/common/types/TypeNames.java b/common/types/src/main/java/io/helidon/common/types/TypeNames.java index f4599281b45..62fe59da5a3 100644 --- a/common/types/src/main/java/io/helidon/common/types/TypeNames.java +++ b/common/types/src/main/java/io/helidon/common/types/TypeNames.java @@ -16,6 +16,7 @@ package io.helidon.common.types; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.time.Duration; import java.util.Collection; @@ -72,6 +73,10 @@ public final class TypeNames { * Type name for {@link java.lang.annotation.Retention}. */ public static final TypeName RETENTION = TypeName.create(Retention.class); + /** + * Type name for {@link java.lang.annotation.Inherited}. + */ + public static final TypeName INHERITED = TypeName.create(Inherited.class); /* Primitive types and their boxed counterparts diff --git a/common/types/src/main/java/io/helidon/common/types/TypedElementInfo.java b/common/types/src/main/java/io/helidon/common/types/TypedElementInfo.java index 87be9327536..905b540268f 100644 --- a/common/types/src/main/java/io/helidon/common/types/TypedElementInfo.java +++ b/common/types/src/main/java/io/helidon/common/types/TypedElementInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -72,6 +72,7 @@ abstract class BuilderBase annotations = new ArrayList<>(); private final List elementTypeAnnotations = new ArrayList<>(); + private final List inheritedAnnotations = new ArrayList<>(); private final List componentTypes = new ArrayList<>(); private final List parameterArguments = new ArrayList<>(); private final Set throwsChecked = new LinkedHashSet<>(); @@ -116,6 +117,7 @@ public BUILDER from(TypedElementInfo prototype) { addThrowsChecked(prototype.throwsChecked()); originatingElement(prototype.originatingElement()); addAnnotations(prototype.annotations()); + addInheritedAnnotations(prototype.inheritedAnnotations()); return self(); } @@ -142,6 +144,7 @@ public BUILDER from(TypedElementInfo.BuilderBase builder) { addThrowsChecked(builder.throwsChecked()); builder.originatingElement().ifPresent(this::originatingElement); addAnnotations(builder.annotations()); + addInheritedAnnotations(builder.inheritedAnnotations()); return self(); } @@ -596,10 +599,11 @@ public BUILDER originatingElement(Object originatingElement) { } /** - * The list of known annotations for this element. Note that "known" implies that the annotation is visible, which depends - * upon the context in which it was build. + * List of declared and known annotations for this element. + * Note that "known" implies that the annotation is visible, which depends + * upon the context in which it was build (such as the {@link java.lang.annotation.Retention of the annotation}). * - * @param annotations the list of annotations on this element + * @param annotations the list of annotations declared on this element * @return updated builder instance * @see #annotations() */ @@ -611,10 +615,11 @@ public BUILDER annotations(List annotations) { } /** - * The list of known annotations for this element. Note that "known" implies that the annotation is visible, which depends - * upon the context in which it was build. + * List of declared and known annotations for this element. + * Note that "known" implies that the annotation is visible, which depends + * upon the context in which it was build (such as the {@link java.lang.annotation.Retention of the annotation}). * - * @param annotations the list of annotations on this element + * @param annotations the list of annotations declared on this element * @return updated builder instance * @see #annotations() */ @@ -625,10 +630,11 @@ public BUILDER addAnnotations(List annotations) { } /** - * The list of known annotations for this element. Note that "known" implies that the annotation is visible, which depends - * upon the context in which it was build. + * List of declared and known annotations for this element. + * Note that "known" implies that the annotation is visible, which depends + * upon the context in which it was build (such as the {@link java.lang.annotation.Retention of the annotation}). * - * @param annotation the list of annotations on this element + * @param annotation the list of annotations declared on this element * @return updated builder instance * @see #annotations() */ @@ -639,10 +645,11 @@ public BUILDER addAnnotation(Annotation annotation) { } /** - * The list of known annotations for this element. Note that "known" implies that the annotation is visible, which depends - * upon the context in which it was build. + * List of declared and known annotations for this element. + * Note that "known" implies that the annotation is visible, which depends + * upon the context in which it was build (such as the {@link java.lang.annotation.Retention of the annotation}). * - * @param consumer the list of annotations on this element + * @param consumer the list of annotations declared on this element * @return updated builder instance * @see #annotations() */ @@ -654,6 +661,77 @@ public BUILDER addAnnotation(Consumer consumer) { return self(); } + /** + * List of all inherited annotations for this element. Inherited annotations are annotations declared + * on annotations of this element that are also marked as {@link java.lang.annotation.Inherited}. + *

+ * The returned list does not contain {@link #annotations()}. If a meta-annotation is present on multiple + * annotations, it will be returned once for each such declaration. + * + * @param inheritedAnnotations list of all meta annotations of this element + * @return updated builder instance + * @see #inheritedAnnotations() + */ + public BUILDER inheritedAnnotations(List inheritedAnnotations) { + Objects.requireNonNull(inheritedAnnotations); + this.inheritedAnnotations.clear(); + this.inheritedAnnotations.addAll(inheritedAnnotations); + return self(); + } + + /** + * List of all inherited annotations for this element. Inherited annotations are annotations declared + * on annotations of this element that are also marked as {@link java.lang.annotation.Inherited}. + *

+ * The returned list does not contain {@link #annotations()}. If a meta-annotation is present on multiple + * annotations, it will be returned once for each such declaration. + * + * @param inheritedAnnotations list of all meta annotations of this element + * @return updated builder instance + * @see #inheritedAnnotations() + */ + public BUILDER addInheritedAnnotations(List inheritedAnnotations) { + Objects.requireNonNull(inheritedAnnotations); + this.inheritedAnnotations.addAll(inheritedAnnotations); + return self(); + } + + /** + * List of all inherited annotations for this element. Inherited annotations are annotations declared + * on annotations of this element that are also marked as {@link java.lang.annotation.Inherited}. + *

+ * The returned list does not contain {@link #annotations()}. If a meta-annotation is present on multiple + * annotations, it will be returned once for each such declaration. + * + * @param inheritedAnnotation list of all meta annotations of this element + * @return updated builder instance + * @see #inheritedAnnotations() + */ + public BUILDER addInheritedAnnotation(Annotation inheritedAnnotation) { + Objects.requireNonNull(inheritedAnnotation); + this.inheritedAnnotations.add(inheritedAnnotation); + return self(); + } + + /** + * List of all inherited annotations for this element. Inherited annotations are annotations declared + * on annotations of this element that are also marked as {@link java.lang.annotation.Inherited}. + *

+ * The returned list does not contain {@link #annotations()}. If a meta-annotation is present on multiple + * annotations, it will be returned once for each such declaration. + * + * @param consumer list of all meta annotations of this element + * @return updated builder instance + * @see #inheritedAnnotations() + */ + public BUILDER addInheritedAnnotation(Consumer consumer) { + Objects.requireNonNull(consumer); + var builder = Annotation.builder(); + consumer.accept(builder); + this.inheritedAnnotations.add(builder.build()); + return self(); + } + /** * Description, such as javadoc, if available. * @@ -811,8 +889,9 @@ public Optional originatingElement() { } /** - * The list of known annotations for this element. Note that "known" implies that the annotation is visible, which depends - * upon the context in which it was build. + * List of declared and known annotations for this element. + * Note that "known" implies that the annotation is visible, which depends + * upon the context in which it was build (such as the {@link java.lang.annotation.Retention of the annotation}). * * @return the annotations */ @@ -820,6 +899,19 @@ public List annotations() { return annotations; } + /** + * List of all inherited annotations for this element. Inherited annotations are annotations declared + * on annotations of this element that are also marked as {@link java.lang.annotation.Inherited}. + *

+ * The returned list does not contain {@link #annotations()}. If a meta-annotation is present on multiple + * annotations, it will be returned once for each such declaration. + * + * @return the inherited annotations + */ + public List inheritedAnnotations() { + return inheritedAnnotations; + } + /** * Handles providers and decorators. */ @@ -916,6 +1008,7 @@ protected static class TypedElementInfoImpl implements TypedElementInfo { private final ElementKind kind; private final List annotations; private final List elementTypeAnnotations; + private final List inheritedAnnotations; private final List componentTypes; private final List parameterArguments; private final Optional enclosingType; @@ -951,6 +1044,7 @@ protected TypedElementInfoImpl(TypedElementInfo.BuilderBase builder) { this.throwsChecked = Collections.unmodifiableSet(new LinkedHashSet<>(builder.throwsChecked())); this.originatingElement = builder.originatingElement(); this.annotations = List.copyOf(builder.annotations()); + this.inheritedAnnotations = List.copyOf(builder.inheritedAnnotations()); } @Override @@ -1043,6 +1137,11 @@ public List annotations() { return annotations; } + @Override + public List inheritedAnnotations() { + return inheritedAnnotations; + } + @Override public boolean equals(Object o) { if (o == this) { @@ -1057,12 +1156,20 @@ public boolean equals(Object o) { && Objects.equals(enclosingType, other.enclosingType()) && Objects.equals(parameterArguments, other.parameterArguments()) && Objects.equals(throwsChecked, other.throwsChecked()) - && Objects.equals(annotations, other.annotations()); + && Objects.equals(annotations, other.annotations()) + && Objects.equals(inheritedAnnotations, other.inheritedAnnotations()); } @Override public int hashCode() { - return Objects.hash(typeName, elementName, kind, enclosingType, parameterArguments, throwsChecked, annotations); + return Objects.hash(typeName, + elementName, + kind, + enclosingType, + parameterArguments, + throwsChecked, + annotations, + inheritedAnnotations); } } From d0c9765d5a30c05d9bfc16f5298b908768b571db Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Sun, 28 Apr 2024 23:37:10 +0200 Subject: [PATCH 02/20] Support for text resources in CodegenFiler, that supports replacing content in annotation processor (to augment or replace resources). Signed-off-by: Tomas Langer --- .../java/io/helidon/codegen/apt/AptFiler.java | 24 +++++ .../codegen/apt/FilerTextResourceImpl.java | 96 +++++++++++++++++++ .../java/io/helidon/codegen/CodegenFiler.java | 67 +++++++++---- .../io/helidon/codegen/FilerTextResource.java | 44 +++++++++ 4 files changed, 212 insertions(+), 19 deletions(-) create mode 100644 codegen/apt/src/main/java/io/helidon/codegen/apt/FilerTextResourceImpl.java create mode 100644 codegen/codegen/src/main/java/io/helidon/codegen/FilerTextResource.java diff --git a/codegen/apt/src/main/java/io/helidon/codegen/apt/AptFiler.java b/codegen/apt/src/main/java/io/helidon/codegen/apt/AptFiler.java index 801cd1baa66..84a7bbf2fab 100644 --- a/codegen/apt/src/main/java/io/helidon/codegen/apt/AptFiler.java +++ b/codegen/apt/src/main/java/io/helidon/codegen/apt/AptFiler.java @@ -16,7 +16,9 @@ package io.helidon.codegen.apt; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Writer; import java.nio.file.Path; @@ -33,9 +35,12 @@ import io.helidon.codegen.CodegenException; import io.helidon.codegen.CodegenFiler; import io.helidon.codegen.CodegenOptions; +import io.helidon.codegen.FilerTextResource; import io.helidon.codegen.IndentType; import io.helidon.codegen.classmodel.ClassModel; +import static java.nio.charset.StandardCharsets.UTF_8; + class AptFiler implements CodegenFiler { private final Filer filer; private final String indent; @@ -83,6 +88,25 @@ public Path writeResource(byte[] resource, String location, Object... originatin } } + @Override + public FilerTextResource textResource(String location, Object... originatingElements) { + + try { + var resource = filer.getResource(StandardLocation.CLASS_OUTPUT, "", location); + List lines = new ArrayList<>(); + + try (var br = new BufferedReader(new InputStreamReader(resource.openInputStream(), UTF_8))) { + String line; + while ((line = br.readLine()) != null) { + lines.add(line); + } + } + return new FilerTextResourceImpl(filer, location, toElements(originatingElements), resource, lines); + } catch (IOException e) { + return new FilerTextResourceImpl(filer, location, toElements(originatingElements)); + } + } + private Object originatingElement(Element[] elements, Object alternative) { if (elements.length == 0) { return alternative; diff --git a/codegen/apt/src/main/java/io/helidon/codegen/apt/FilerTextResourceImpl.java b/codegen/apt/src/main/java/io/helidon/codegen/apt/FilerTextResourceImpl.java new file mode 100644 index 00000000000..f0cfb8ed4f6 --- /dev/null +++ b/codegen/apt/src/main/java/io/helidon/codegen/apt/FilerTextResourceImpl.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 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.codegen.apt; + +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.processing.Filer; +import javax.lang.model.element.Element; +import javax.tools.FileObject; +import javax.tools.StandardLocation; + +import io.helidon.codegen.CodegenException; +import io.helidon.codegen.FilerTextResource; + +import static java.nio.charset.StandardCharsets.UTF_8; + +class FilerTextResourceImpl implements FilerTextResource { + private final Filer filer; + private final String location; + private final Element[] originatingElements; + private final FileObject originalResource; // may be null + private final List currentLines; + + private boolean modified; + + FilerTextResourceImpl(Filer filer, String location, Element[] originatingElements) { + this.filer = filer; + this.location = location; + this.originatingElements = originatingElements; + this.originalResource = null; + this.currentLines = new ArrayList<>(); + } + + FilerTextResourceImpl(Filer filer, + String location, + Element[] originatingElements, + FileObject originalResource, + List existingLines) { + this.filer = filer; + this.location = location; + this.originatingElements = originatingElements; + this.originalResource = originalResource; + this.currentLines = new ArrayList<>(existingLines); + } + + @Override + public List lines() { + return List.copyOf(currentLines); + } + + @Override + public void lines(List newLines) { + currentLines.clear(); + currentLines.addAll(newLines); + modified = true; + } + + @Override + public void write() { + if (modified) { + if (originalResource != null) { + originalResource.delete(); + } + try { + FileObject newResource = filer.createResource(StandardLocation.CLASS_OUTPUT, + "", + location, + originatingElements); + try (var pw = new PrintWriter(new OutputStreamWriter(newResource.openOutputStream(), UTF_8))) { + for (String line : currentLines) { + pw.println(line); + } + } + } catch (Exception e) { + throw new CodegenException("Failed to create resource: " + location, e); + } + } + } +} diff --git a/codegen/codegen/src/main/java/io/helidon/codegen/CodegenFiler.java b/codegen/codegen/src/main/java/io/helidon/codegen/CodegenFiler.java index a54669f0c83..f4b2ab46ac2 100644 --- a/codegen/codegen/src/main/java/io/helidon/codegen/CodegenFiler.java +++ b/codegen/codegen/src/main/java/io/helidon/codegen/CodegenFiler.java @@ -16,10 +16,12 @@ package io.helidon.codegen; -import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import io.helidon.codegen.classmodel.ClassModel; @@ -45,11 +47,23 @@ public interface CodegenFiler { * * @param resource bytes of the resource file * @param location location to write to in the classes output directory - * @param originatingElements elements that caused this type to be generated + * @param originatingElements elements that caused this file to be generated * @return written path, we expect to always run on local file system */ Path writeResource(byte[] resource, String location, Object... originatingElements); + /** + * A text resource that can be updated in the output. + * Note that the resource can only be written once per processing round. + * + * @param location location to read/write to in the classes output directory + * @param originatingElements elements that caused this file to be generated + * @return the resource that can be used to update the file + */ + default FilerTextResource textResource(String location, Object... originatingElements) { + throw new UnsupportedOperationException("Method textResource not implemented yet on " + getClass().getName()); + } + /** * Write a {@code META-INF/services} file for a specific provider interface and implementation(s). * @@ -66,25 +80,40 @@ default void services(TypeName generator, Objects.requireNonNull(providerInterface); Objects.requireNonNull(providers); - String location = "META-INF/services/" + providerInterface.fqName(); if (providers.isEmpty()) { - throw new CodegenException("List of providers is empty, cannot generate " + location); + // do not write services file if there is no provider added + return; + } + + String location = "META-INF/services/" + providerInterface.fqName(); + + var resource = textResource(location, originatingElements); + + List lines = new ArrayList<>(resource.lines()); + Set existingServices = lines.stream() + .map(String::trim) + .filter(Predicate.not(it -> it.startsWith("#"))) + .map(TypeName::create) + .collect(Collectors.toSet()); + + if (lines.isEmpty()) { + // @Generated + lines.add("# " + GeneratedAnnotationHandler.create(generator, + providers.getFirst(), + TypeName.create( + "MetaInfServicesModuleComponent"), + "1", + "")); + } + + for (TypeName provider : providers) { + if (existingServices.add(provider)) { + // only add the provider if it does not yet exist + lines.add(provider.fqName()); + } } - byte[] resourceBytes = ( - "# " + GeneratedAnnotationHandler.create(generator, - providers.getFirst(), - TypeName.create( - "MetaInfServicesModuleComponent"), - "1", - "") - + "\n" - + providers.stream() - .map(TypeName::declaredName) - .collect(Collectors.joining("\n"))) - .getBytes(StandardCharsets.UTF_8); - writeResource(resourceBytes, - location, - originatingElements); + resource.lines(lines); + resource.write(); } } diff --git a/codegen/codegen/src/main/java/io/helidon/codegen/FilerTextResource.java b/codegen/codegen/src/main/java/io/helidon/codegen/FilerTextResource.java new file mode 100644 index 00000000000..bee2e8f007f --- /dev/null +++ b/codegen/codegen/src/main/java/io/helidon/codegen/FilerTextResource.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 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.codegen; + +import java.util.List; + +/** + * A resource from output (such as {@code target/META-INF/helidon}) that can have existing + * values, and may be replaced with a new value. + */ +public interface FilerTextResource { + /** + * Existing lines of the resource. Returns an empty list if the resource does not exist. + * + * @return list of lines, immutable collection + */ + List lines(); + + /** + * New lines of the resource. + * + * @param newLines new lines to {@link #write()} to the resource file + */ + void lines(List newLines); + + /** + * Writes the new lines to the output. This operation can only be called once per codegen round. + */ + void write(); +} From 2d4e2523aa3576ddd81e5022b9f1e1fedd370b6d Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Mon, 29 Apr 2024 00:48:40 +0200 Subject: [PATCH 03/20] Introduction of Helidon Service Registry. Signed-off-by: Tomas Langer --- all/pom.xml | 8 + bom/pom.xml | 12 + .../graal/native-image-extension/pom.xml | 4 + .../extension/HelidonReflectionFeature.java | 72 +- .../nativeimage/extension/NativeUtil.java | 21 +- .../src/main/java/module-info.java | 3 +- pom.xml | 1 + service/README.md | 93 ++ service/codegen/pom.xml | 55 ++ .../service/codegen/DescriptorClassCode.java | 55 ++ .../codegen/DescriptorClassCodeImpl.java | 28 + .../codegen/GenerateServiceDescriptor.java | 801 ++++++++++++++++++ .../codegen/HelidonMetaInfServices.java | 186 ++++ .../service/codegen/ParamDefinition.java | 70 ++ .../codegen/RegistryCodegenContext.java | 133 +++ .../codegen/RegistryCodegenContextImpl.java | 134 +++ .../codegen/RegistryCodegenExtension.java | 35 + .../service/codegen/RegistryRoundContext.java | 61 ++ .../service/codegen/RoundContextImpl.java | 89 ++ .../service/codegen/ServiceCodegenTypes.java | 58 ++ .../service/codegen/ServiceExtension.java | 46 + .../service/codegen/ServiceOptions.java | 35 + .../ServiceRegistryCodegenExtension.java | 281 ++++++ .../ServiceRegistryCodegenProvider.java | 72 ++ .../io/helidon/service/codegen/SuperType.java | 36 + .../service/codegen/TypedElements.java | 95 +++ .../helidon/service/codegen/package-info.java | 20 + .../codegen/src/main/java/module-info.java | 31 + service/pom.xml | 63 ++ service/registry/etc/spotbugs/exclude.xml | 31 + service/registry/pom.xml | 105 +++ .../registry/CoreServiceDiscovery.java | 294 +++++++ .../service/registry/CoreServiceRegistry.java | 303 +++++++ .../registry/CoreServiceRegistryManager.java | 72 ++ .../service/registry/DependencyBlueprint.java | 89 ++ .../service/registry/DependencyContext.java | 48 ++ .../registry/DependencyContextImpl.java | 33 + .../service/registry/DescriptorMetadata.java | 68 ++ .../service/registry/GeneratedService.java | 45 + .../registry/GlobalServiceRegistry.java | 118 +++ .../io/helidon/service/registry/Service.java | 149 ++++ .../service/registry/ServiceDiscovery.java | 74 ++ .../helidon/service/registry/ServiceInfo.java | 72 ++ .../ServiceLoader__ServiceDescriptor.java | 129 +++ .../service/registry/ServiceMetadata.java | 30 + .../service/registry/ServiceRegistry.java | 175 ++++ .../ServiceRegistryConfigBlueprint.java | 86 ++ .../ServiceRegistryConfigSupport.java | 115 +++ .../registry/ServiceRegistryException.java | 41 + .../registry/ServiceRegistryManager.java | 57 ++ .../ServiceRegistryManagerDiscovery.java | 46 + .../ServiceRegistry__ServiceDescriptor.java | 53 ++ .../service/registry/package-info.java | 23 + .../spi/ServiceRegistryManagerProvider.java | 42 + .../service/registry/spi/package-info.java | 20 + .../registry/src/main/java/module-info.java | 29 + .../native-image.properties | 19 + .../resource-config.json | 10 + service/tests/codegen/pom.xml | 64 ++ .../codegen/src/main/java/module-info.java | 21 + .../codegen/ServiceCodegenTypesTest.java | 94 ++ .../codegen/src/test/java/module-info.java | 28 + service/tests/pom.xml | 51 ++ service/tests/registry/pom.xml | 110 +++ .../service/test/registry/MyContract.java | 24 + .../service/test/registry/MyService.java | 33 + .../service/test/registry/MyService2.java | 38 + .../registry/src/main/java/module-info.java | 21 + .../test/registry/CyclicDependencyTest.java | 148 ++++ .../test/registry/RegistryConfigTest.java | 61 ++ .../service/test/registry/RegistryTest.java | 82 ++ 71 files changed, 5744 insertions(+), 5 deletions(-) create mode 100644 service/README.md create mode 100644 service/codegen/pom.xml create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/DescriptorClassCode.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/DescriptorClassCodeImpl.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/GenerateServiceDescriptor.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/HelidonMetaInfServices.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/ParamDefinition.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/RegistryCodegenContext.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/RegistryCodegenContextImpl.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/RegistryCodegenExtension.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/RegistryRoundContext.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/RoundContextImpl.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/ServiceCodegenTypes.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/ServiceExtension.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/ServiceOptions.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/ServiceRegistryCodegenExtension.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/ServiceRegistryCodegenProvider.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/SuperType.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/TypedElements.java create mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/package-info.java create mode 100644 service/codegen/src/main/java/module-info.java create mode 100644 service/pom.xml create mode 100644 service/registry/etc/spotbugs/exclude.xml create mode 100644 service/registry/pom.xml create mode 100644 service/registry/src/main/java/io/helidon/service/registry/CoreServiceDiscovery.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistryManager.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/DependencyBlueprint.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/DependencyContext.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/DependencyContextImpl.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/DescriptorMetadata.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/GeneratedService.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/GlobalServiceRegistry.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/Service.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/ServiceDiscovery.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/ServiceInfo.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/ServiceLoader__ServiceDescriptor.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/ServiceMetadata.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryConfigBlueprint.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryConfigSupport.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryException.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryManager.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryManagerDiscovery.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry__ServiceDescriptor.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/package-info.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/spi/ServiceRegistryManagerProvider.java create mode 100644 service/registry/src/main/java/io/helidon/service/registry/spi/package-info.java create mode 100644 service/registry/src/main/java/module-info.java create mode 100644 service/registry/src/main/resources/META-INF/native-image/io.helidon.service/helidon-service-registry/native-image.properties create mode 100644 service/registry/src/main/resources/META-INF/native-image/io.helidon.service/helidon-service-registry/resource-config.json create mode 100644 service/tests/codegen/pom.xml create mode 100644 service/tests/codegen/src/main/java/module-info.java create mode 100644 service/tests/codegen/src/test/java/io/helidon/service/tests/codegen/ServiceCodegenTypesTest.java create mode 100644 service/tests/codegen/src/test/java/module-info.java create mode 100644 service/tests/pom.xml create mode 100644 service/tests/registry/pom.xml create mode 100644 service/tests/registry/src/main/java/io/helidon/service/test/registry/MyContract.java create mode 100644 service/tests/registry/src/main/java/io/helidon/service/test/registry/MyService.java create mode 100644 service/tests/registry/src/main/java/io/helidon/service/test/registry/MyService2.java create mode 100644 service/tests/registry/src/main/java/module-info.java create mode 100644 service/tests/registry/src/test/java/io/helidon/service/test/registry/CyclicDependencyTest.java create mode 100644 service/tests/registry/src/test/java/io/helidon/service/test/registry/RegistryConfigTest.java create mode 100644 service/tests/registry/src/test/java/io/helidon/service/test/registry/RegistryTest.java diff --git a/all/pom.xml b/all/pom.xml index f97cd9e2c7c..50a532aa89e 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -1054,6 +1054,14 @@ io.helidon.inject.configdriven helidon-inject-configdriven-processor + + io.helidon.service + helidon-service-registry + + + io.helidon.service + helidon-service-codegen + io.helidon.integrations.oci.sdk helidon-integrations-oci-sdk-processor diff --git a/bom/pom.xml b/bom/pom.xml index 52fd63482cc..324eda72f5a 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -1390,6 +1390,18 @@ ${helidon.version} + + + io.helidon.service + helidon-service-registry + ${helidon.version} + + + io.helidon.service + helidon-service-codegen + ${helidon.version} + + io.helidon.integrations.oci.sdk diff --git a/integrations/graal/native-image-extension/pom.xml b/integrations/graal/native-image-extension/pom.xml index 5885534f48f..b2a94d48320 100644 --- a/integrations/graal/native-image-extension/pom.xml +++ b/integrations/graal/native-image-extension/pom.xml @@ -49,6 +49,10 @@ io.helidon.common.features helidon-common-features + + io.helidon.service + helidon-service-registry + org.eclipse.parsson parsson diff --git a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java index 74309f99ffb..eb95dd04bf5 100644 --- a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java +++ b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023 Oracle and/or its affiliates. + * Copyright (c) 2019, 2024 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. @@ -33,7 +33,12 @@ import io.helidon.common.Reflected; import io.helidon.common.features.HelidonFeatures; +import io.helidon.common.types.TypeName; import io.helidon.logging.common.LogConfig; +import io.helidon.service.registry.DescriptorMetadata; +import io.helidon.service.registry.ServiceDiscovery; +import io.helidon.service.registry.ServiceLoader__ServiceDescriptor; +import io.helidon.service.registry.ServiceRegistryConfig; import io.github.classgraph.ClassGraph; import io.github.classgraph.ScanResult; @@ -51,6 +56,7 @@ public class HelidonReflectionFeature implements Feature { private static final String AT_ENTITY = "jakarta.persistence.Entity"; private static final String AT_MAPPED_SUPERCLASS = "jakarta.persistence.MappedSuperclass"; + private static final String REGISTRY_DESCRIPTOR = "io.helidon.service.registry.GeneratedService$Descriptor"; private final NativeTrace tracer = new NativeTrace(); private NativeUtil util; @@ -109,6 +115,9 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { // process each configured class config.classes().forEach(it -> addSingleClass(context, it)); + // Service descriptors + processServiceDescriptors(context); + // JPA Entity registration processEntity(context); @@ -173,6 +182,65 @@ private void processSubClasses(BeforeAnalysisContext context, Class aClass) { processClasses(context, subclasses); } + private void processServiceDescriptors(BeforeAnalysisContext context) { + ServiceDiscovery sd = ServiceDiscovery.create(ServiceRegistryConfig.builder() + .discoverServices(false) + .discoverServicesFromServiceLoader(true) + .build()); + + sd.allMetadata() + .stream() + .map(DescriptorMetadata::descriptor) + .filter(it -> it instanceof ServiceLoader__ServiceDescriptor) + .map(it -> (ServiceLoader__ServiceDescriptor) it) + .map(ServiceLoader__ServiceDescriptor::serviceType) + .forEach(it -> processServiceLoaderDescriptor(context, it)); + + Class classByName = context.access().findClassByName(REGISTRY_DESCRIPTOR); + tracer.parsing(() -> "Discovering service descriptors. Top level type: " + classByName); + if (classByName != null) { + processServiceDescriptors(context, classByName); + } + } + + private void processServiceLoaderDescriptor(BeforeAnalysisContext context, TypeName serviceImpl) { + Class classByName = context.access().findClassByName(serviceImpl.fqName()); + if (classByName == null) { + tracer.parsing(() -> " " + serviceImpl.fqName()); + tracer.parsing(() -> " service implementation is not on classpath"); + return; + } + tracer.parsing(() -> " " + classByName.getName()); + tracer.parsing(() -> " Added for registration"); + + try { + Constructor constructor = classByName.getConstructor(); + context.register(classByName).add(constructor); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Cannot find default constructor for provider implementation class " + classByName, + e); + } + } + + private void processServiceDescriptors(BeforeAnalysisContext context, Class clazz) { + Set> subclasses = util.findSubclasses(clazz.getName()); + + for (Class aClass : subclasses) { + if (context.process(aClass)) { + tracer.parsing(() -> " " + aClass.getName()); + tracer.parsing(() -> " Added for registration"); + + try { + Field field = aClass.getDeclaredField("INSTANCE"); + context.register(aClass).add(field); + } catch (NoSuchFieldException ignored) { + // do not register, as this is not accessible via reflection + } + processServiceDescriptors(context, aClass); + } + } + } + private void addSingleClass(BeforeAnalysisContext context, Class theClass) { if (context.process(theClass)) { @@ -241,8 +309,6 @@ private void processEntity(BeforeAnalysisContext context) { }); } - - private void registerForReflection(BeforeAnalysisContext context) { Collection toRegister = context.toRegister(); diff --git a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/NativeUtil.java b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/NativeUtil.java index 0028429b640..6c67e80b602 100644 --- a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/NativeUtil.java +++ b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/NativeUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023 Oracle and/or its affiliates. + * Copyright (c) 2021, 2024 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. @@ -16,9 +16,11 @@ package io.helidon.integrations.graal.nativeimage.extension; +import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -39,6 +41,8 @@ import io.github.classgraph.FieldInfo; import io.github.classgraph.MethodParameterInfo; import io.github.classgraph.ReferenceTypeSignature; +import io.github.classgraph.Resource; +import io.github.classgraph.ResourceList; import io.github.classgraph.ScanResult; import io.github.classgraph.TypeArgument; import io.github.classgraph.TypeSignature; @@ -265,6 +269,21 @@ public Set> findInterfaces(Class aClass) { return result; } + List findResources(String name) { + ResourceList allResources = scan.getResourcesWithPath(name); + List list = new ArrayList<>(); + for (Resource resource : allResources) { + String contentAsString = null; + try { + contentAsString = resource.getContentAsString(); + } catch (IOException e) { + tracer.parsing(() -> "Failed to load resource " + resource.getPath(), e); + } + list.add(contentAsString); + } + return list; + } + void processAnnotatedFields(String annotation, BiConsumer, Field> fieldProcessor) { InclusionFilter inclusionFilter = new InclusionFilter(tracer, exclusion, "field annotated by " + annotation); ClassResolverMapper mapper = new ClassResolverMapper(tracer, classResolver, "field annotated by " + annotation); diff --git a/integrations/graal/native-image-extension/src/main/java/module-info.java b/integrations/graal/native-image-extension/src/main/java/module-info.java index 31e610fa891..8e841236606 100644 --- a/integrations/graal/native-image-extension/src/main/java/module-info.java +++ b/integrations/graal/native-image-extension/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -23,6 +23,7 @@ requires io.helidon.common.features; requires io.helidon.config; requires io.helidon.logging.common; + requires io.helidon.service.registry; requires jakarta.json; requires transitive org.graalvm.nativeimage; diff --git a/pom.xml b/pom.xml index 293c7120b01..db3d6bfeb05 100644 --- a/pom.xml +++ b/pom.xml @@ -224,6 +224,7 @@ webserver websocket codegen + service diff --git a/service/README.md b/service/README.md new file mode 100644 index 00000000000..806af08a5a7 --- /dev/null +++ b/service/README.md @@ -0,0 +1,93 @@ +Helidon Service Registry +---- + +# Core Service Registry + +Provides a replacement for Java ServiceLoader with basic inversion of control mechanism. +Each service may have constructor parameters that expect instances of other services. + +The constructor dependency types can be as follows (`Contract` is used as the contract the service implements) + +- `Contract` - simply get an instance of another service +- `Optional` - get an instance of another service, the other service may not be available +- `List` - get instances of all services that are available +- `Supplier`, `Supplier>`, `Supplier>` - equivalent methods but the value is resolved + when `Supplier.get()` is called, to allow more control + +Equivalent behavior can be achieved programmatically through `io.helidon.service.registry.ServiceRegistry` instance. This can be +obtained from a `ServiceRegistryManager`. + +## Declare a service + +Use `io.helidon.service.registry.Service.Provider` annotation on your service provider type (implementation of a contract). +Use `io.helidon.service.registry.Service.Contract` on your contract interface (if not annotated, such an interface would not be +considered a contract and will not be discoverable using the registry - configurable). +Use `io.helidon.service.registry.Service.ExternalContracts` on your service provider type to +add other types as contracts, even if not annotated with `Contract` (i.e. to support third party libraries). + +Use `io.helidon.service.registry.Service.Descriptor` to create a hand-crafted service descriptor (see below "Behind the scenes") + +Service example: + +```java +import io.helidon.service.registry.Service; + +@Service.Provider +class MyService implements MyContract { + MyService() { + } + + @Override + public String message() { + return "MyService"; + } +} +``` + +Service with dependency example: + +```java +import io.helidon.service.registry.Service; + +@Service.Provider +class MyService2 implements MyContract2 { + private final MyContract dependency; + + MyService2(MyContract dependency) { + this.dependency = dependency; + } + + @Override + public String message() { + return dependency.message(); + } +} +``` + +## Behind the scenes + +For each service, Helidon generates a service descriptor (`ServiceProvider__ServiceDescriptor`). +This descriptor is discovered at runtime and used to instantiate a service without the need to use reflection. + +Reflection is used only to obtain an instance of the service descriptor (by using its public `INSTANCE` singleton field). As both +the descriptor and the `INSTANCE` field are always public, there is no need to add `opens` to `module-info.java`. +Support for GraalVM native image is handled in Helidon native image extension, by registering all service descriptors for +reflection (the class, and the field). + +### Registry file format + +The service registry uses a `service.registry` in `META-INF/helidon` directory to store the main metadata of +the service. This is to allow proper ordering of services (Service weight is one of the information stored) and +lazy loading of services (which is the approach chosen in the core service registry). + +The format is as follows: + +``` +registry-type:service-descriptor-type:weight(double):contracts(comma separated) +``` + +Example: + +``` +core:io.helidon.ContractImpl__ServiceDescriptor:101.3:io.helidon.Contract1,io.helidon.Contract2 +``` \ No newline at end of file diff --git a/service/codegen/pom.xml b/service/codegen/pom.xml new file mode 100644 index 00000000000..ce6fe0e463a --- /dev/null +++ b/service/codegen/pom.xml @@ -0,0 +1,55 @@ + + + + + + io.helidon.service + helidon-service-project + 4.0.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + helidon-service-codegen + Helidon Service Codegen + + Code generation implementation for Helidon Service, to be used from annotation processing and from maven plugin + + + + + io.helidon.common + helidon-common-types + + + io.helidon.common + helidon-common + + + io.helidon.codegen + helidon-codegen + + + io.helidon.codegen + helidon-codegen-class-model + + + diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/DescriptorClassCode.java b/service/codegen/src/main/java/io/helidon/service/codegen/DescriptorClassCode.java new file mode 100644 index 00000000000..d9e5e06f406 --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/DescriptorClassCode.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 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.service.codegen; + +import java.util.Set; + +import io.helidon.codegen.ClassCode; +import io.helidon.common.types.TypeName; + +/** + * New service descriptor metadata with its class code. + */ +interface DescriptorClassCode { + /** + * New source code information. + * + * @return class code + */ + ClassCode classCode(); + + /** + * Type of registry of this descriptor. + * + * @return registry type + */ + String registryType(); + + /** + * Weight of the new descriptor. + * + * @return weight + */ + double weight(); + + /** + * Contracts the described service implements/provides. + * + * @return contracts of the service + */ + Set contracts(); +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/DescriptorClassCodeImpl.java b/service/codegen/src/main/java/io/helidon/service/codegen/DescriptorClassCodeImpl.java new file mode 100644 index 00000000000..fc78e2781a4 --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/DescriptorClassCodeImpl.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 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.service.codegen; + +import java.util.Set; + +import io.helidon.codegen.ClassCode; +import io.helidon.common.types.TypeName; + +record DescriptorClassCodeImpl(ClassCode classCode, + String registryType, + double weight, + Set contracts) implements DescriptorClassCode { +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/GenerateServiceDescriptor.java b/service/codegen/src/main/java/io/helidon/service/codegen/GenerateServiceDescriptor.java new file mode 100644 index 00000000000..96715a37c14 --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/GenerateServiceDescriptor.java @@ -0,0 +1,801 @@ +/* + * Copyright (c) 2024 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.service.codegen; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import io.helidon.codegen.CodegenException; +import io.helidon.codegen.CodegenUtil; +import io.helidon.codegen.ElementInfoPredicates; +import io.helidon.codegen.classmodel.ClassModel; +import io.helidon.codegen.classmodel.Javadoc; +import io.helidon.codegen.classmodel.Method; +import io.helidon.codegen.classmodel.TypeArgument; +import io.helidon.common.Weight; +import io.helidon.common.Weighted; +import io.helidon.common.types.AccessModifier; +import io.helidon.common.types.Annotation; +import io.helidon.common.types.Annotations; +import io.helidon.common.types.ElementKind; +import io.helidon.common.types.Modifier; +import io.helidon.common.types.TypeInfo; +import io.helidon.common.types.TypeName; +import io.helidon.common.types.TypeNames; +import io.helidon.common.types.TypedElementInfo; + +import static io.helidon.service.codegen.ServiceCodegenTypes.SERVICE_ANNOTATION_PROVIDER; +import static java.util.function.Predicate.not; + +/** + * Generates a service descriptor. + */ +class GenerateServiceDescriptor { + static final TypeName SET_OF_TYPES = TypeName.builder(TypeNames.SET) + .addTypeArgument(TypeNames.TYPE_NAME) + .build(); + private static final TypeName LIST_OF_DEPENDENCIES = TypeName.builder(TypeNames.LIST) + .addTypeArgument(ServiceCodegenTypes.SERVICE_DEPENDENCY) + .build(); + private static final TypeName DESCRIPTOR_TYPE = TypeName.builder(ServiceCodegenTypes.SERVICE_DESCRIPTOR) + .addTypeArgument(TypeName.create("T")) + .build(); + private static final TypedElementInfo DEFAULT_CONSTRUCTOR = TypedElementInfo.builder() + .typeName(TypeNames.OBJECT) + .accessModifier(AccessModifier.PUBLIC) + .kind(ElementKind.CONSTRUCTOR) + .build(); + private static final TypeName ANY_GENERIC_TYPE = TypeName.builder(TypeNames.GENERIC_TYPE) + .addTypeArgument(TypeName.create("?")) + .build(); + + private final TypeName generator; + private final RegistryCodegenContext ctx; + private final Collection services; + private final TypeInfo typeInfo; + private final boolean autoAddContracts; + + private GenerateServiceDescriptor(TypeName generator, + RegistryCodegenContext ctx, + Collection allServices, + TypeInfo service) { + this.generator = generator; + this.ctx = ctx; + this.services = allServices; + this.typeInfo = service; + this.autoAddContracts = ServiceOptions.AUTO_ADD_NON_CONTRACT_INTERFACES.value(ctx.options()); + } + + /** + * Generate a service descriptor for the provided service type info. + * + * @param generator type of the generator responsible for this event + * @param ctx context of code generation + * @param allServices all services processed in this round of processing + * @param service service to create a descriptor for + * @return class model builder of the service descriptor + */ + static ClassModel.Builder generate(TypeName generator, + RegistryCodegenContext ctx, + Collection allServices, + TypeInfo service) { + return new GenerateServiceDescriptor(generator, ctx, allServices, service) + .generate(); + } + + static List declareCtrParamsAndGetThem(Method.Builder method, List params) { + List constructorParams = params.stream() + .filter(it -> it.kind() == ElementKind.CONSTRUCTOR) + .toList(); + + // for each parameter, obtain its value from context + for (ParamDefinition param : constructorParams) { + method.addContent(param.declaredType()) + .addContent(" ") + .addContent(param.ipParamName()) + .addContent(" = ") + .update(it -> param.assignmentHandler().accept(it)) + .addContentLine(";"); + } + if (!params.isEmpty()) { + method.addContentLine(""); + } + return constructorParams; + } + + private ClassModel.Builder generate() { + TypeName serviceType = typeInfo.typeName(); + + if (typeInfo.kind() == ElementKind.INTERFACE) { + throw new CodegenException("We can only generated service descriptors for classes, interface was requested: ", + typeInfo.originatingElement().orElse(serviceType)); + } + boolean isAbstractClass = typeInfo.elementModifiers().contains(Modifier.ABSTRACT) + && typeInfo.kind() == ElementKind.CLASS; + + SuperType superType = superType(typeInfo, services); + + // this must result in generating a service descriptor file + TypeName descriptorType = ctx.descriptorType(serviceType); + + List params = params(typeInfo, constructor(typeInfo)); + + ClassModel.Builder classModel = ClassModel.builder() + .copyright(CodegenUtil.copyright(generator, + serviceType, + descriptorType)) + .addAnnotation(CodegenUtil.generatedAnnotation(generator, + serviceType, + descriptorType, + "1", + "")) + .type(descriptorType) + .addGenericArgument(TypeArgument.create("T extends " + serviceType.fqName())) + .javadoc(Javadoc.builder() + .add("Service descriptor for {@link " + serviceType.fqName() + "}.") + .addGenericArgument("T", "type of the service, for extensibility") + .build()) + // we need to keep insertion order, as constants may depend on each other + .sortStaticFields(false); + + Map genericTypes = genericTypes(classModel, params); + Set contracts = new HashSet<>(); + Set collectedFullyQualifiedContracts = new HashSet<>(); + contracts(typeInfo, autoAddContracts, contracts, collectedFullyQualifiedContracts); + + // declare the class + + if (superType.hasSupertype()) { + classModel.superType(superType.superDescriptorType()); + } else { + classModel.addInterface(DESCRIPTOR_TYPE); + } + + // Fields + singletonInstanceField(classModel, serviceType, descriptorType); + serviceTypeFields(classModel, serviceType, descriptorType); + + // public fields are last, so they do not intersect with private fields (it is not as nice to read) + // they cannot be first, as they require some of the private fields + dependencyFields(classModel, typeInfo, genericTypes, params); + // dependencies require IP IDs, so they really must be last + dependenciesField(classModel, params); + + // add protected constructor + classModel.addConstructor(constructor -> constructor.description("Constructor with no side effects") + .accessModifier(AccessModifier.PROTECTED)); + + // methods (some methods define fields as well) + serviceTypeMethod(classModel); + descriptorTypeMethod(classModel); + contractsMethod(classModel, contracts); + dependenciesMethod(classModel, params, superType); + isAbstractMethod(classModel, superType, isAbstractClass); + instantiateMethod(classModel, serviceType, params, isAbstractClass); + weightMethod(typeInfo, classModel, superType); + + // service type is an implicit contract + Set allContracts = new HashSet<>(contracts); + allContracts.add(serviceType); + + ctx.addDescriptor("core", + serviceType, + descriptorType, + classModel, + weight(typeInfo).orElse(Weighted.DEFAULT_WEIGHT), + allContracts, + typeInfo.originatingElement().orElseGet(typeInfo::typeName)); + + return classModel; + } + + private SuperType superType(TypeInfo typeInfo, Collection services) { + // find super type if it is also a service (or has a service descriptor) + + // check if the super type is part of current annotation processing + Optional superTypeInfoOptional = typeInfo.superTypeInfo(); + if (superTypeInfoOptional.isEmpty()) { + return SuperType.noSuperType(); + } + TypeInfo superType = superTypeInfoOptional.get(); + TypeName expectedSuperDescriptor = ctx.descriptorType(superType.typeName()); + TypeName superTypeToExtend = TypeName.builder(expectedSuperDescriptor) + .addTypeArgument(TypeName.create("T")) + .build(); + boolean isCore = superType.hasAnnotation(SERVICE_ANNOTATION_PROVIDER); + if (!isCore) { + throw new CodegenException("Service annotated with @Service.Provider extends invalid supertype," + + " the super type must also be a @Service.Provider. Type: " + + typeInfo.typeName().fqName() + ", super type: " + + superType.typeName().fqName()); + } + for (TypeInfo service : services) { + if (service.typeName().equals(superType.typeName())) { + return new SuperType(true, superTypeToExtend, service, true); + } + } + // if not found in current list, try checking existing types + return ctx.typeInfo(expectedSuperDescriptor) + .map(it -> new SuperType(true, superTypeToExtend, superType, true)) + .orElseGet(SuperType::noSuperType); + } + + // there must be none, or one non-private constructor (actually there may be more, we just use the first) + private TypedElementInfo constructor(TypeInfo typeInfo) { + return typeInfo.elementInfo() + .stream() + .filter(it -> it.kind() == ElementKind.CONSTRUCTOR) + .filter(not(ElementInfoPredicates::isPrivate)) + .findFirst() + // or default constructor + .orElse(DEFAULT_CONSTRUCTOR); + } + + private List params( + TypeInfo service, + TypedElementInfo constructor) { + AtomicInteger paramCounter = new AtomicInteger(); + + return constructor.parameterArguments() + .stream() + .map(param -> { + String constantName = "PARAM_" + paramCounter.getAndIncrement(); + RegistryCodegenContext.Assignment assignment = translateParameter(param.typeName(), constantName); + return new ParamDefinition(constructor, + null, + param, + constantName, + param.typeName(), + assignment.usedType(), + assignment.codeGenerator(), + ElementKind.CONSTRUCTOR, + constructor.elementName(), + param.elementName(), + param.elementName(), + false, + param.annotations(), + Set.of(), + contract(service.typeName() + .fqName() + " Constructor parameter: " + param.elementName(), + assignment.usedType()), + constructor.accessModifier(), + ""); + }) + .toList(); + } + + private TypeName contract(String description, TypeName typeName) { + /* + get the contract expected for this dependency + IP may be: + - Optional + - List + - ServiceProvider + - Supplier + - Optional + - Optional + - List + - List + */ + + if (typeName.isOptional()) { + if (typeName.typeArguments().isEmpty()) { + throw new IllegalArgumentException("Dependency with Optional type must have a declared type argument: " + + description); + } + return contract(description, typeName.typeArguments().getFirst()); + } + if (typeName.isList()) { + if (typeName.typeArguments().isEmpty()) { + throw new IllegalArgumentException("Dependency with List type must have a declared type argument: " + + description); + } + return contract(description, typeName.typeArguments().getFirst()); + } + if (typeName.isSupplier()) { + if (typeName.typeArguments().isEmpty()) { + throw new IllegalArgumentException("Dependency with Supplier type must have a declared type argument: " + + description); + } + return contract(description, typeName.typeArguments().getFirst()); + } + + return typeName; + } + + private Map genericTypes(ClassModel.Builder classModel, + List params) { + // we must use map by string (as type name is equal if the same class, not full generic declaration) + Map result = new LinkedHashMap<>(); + AtomicInteger counter = new AtomicInteger(); + + for (ParamDefinition param : params) { + result.computeIfAbsent(param.translatedType().resolvedName(), + type -> { + var response = + new GenericTypeDeclaration("TYPE_" + counter.getAndIncrement(), + param.declaredType()); + addTypeConstant(classModel, param.translatedType(), response); + return response; + }); + result.computeIfAbsent(param.contract().fqName(), + type -> { + var response = + new GenericTypeDeclaration("TYPE_" + counter.getAndIncrement(), + param.declaredType()); + addTypeConstant(classModel, param.contract(), response); + return response; + }); + } + + return result; + } + + private void addTypeConstant(ClassModel.Builder classModel, + TypeName typeName, + GenericTypeDeclaration generic) { + String stringType = typeName.resolvedName(); + // constants for dependency parameter types (used by next section) + classModel.addField(field -> field + .accessModifier(AccessModifier.PRIVATE) + .isStatic(true) + .isFinal(true) + .type(TypeNames.TYPE_NAME) + .name(generic.constantName()) + .update(it -> { + if (stringType.indexOf('.') < 0) { + // there is no package, we must use class (if this is a generic type, we have a problem) + it.addContent(TypeNames.TYPE_NAME) + .addContent(".create(") + .addContent(typeName) + .addContent(".class)"); + } else { + it.addContentCreate(typeName); + } + })); + classModel.addField(field -> field + .accessModifier(AccessModifier.PRIVATE) + .isStatic(true) + .isFinal(true) + .type(ANY_GENERIC_TYPE) + .name("G" + generic.constantName()) + .update(it -> { + if (typeName.primitive()) { + it.addContent(TypeNames.GENERIC_TYPE) + .addContent(".create(") + .addContent(typeName.className()) + .addContent(".class)"); + } else { + it.addContent("new ") + .addContent(TypeNames.GENERIC_TYPE) + .addContent("<") + .addContent(typeName) + .addContent(">() {}"); + } + }) + ); + } + + private void contracts(TypeInfo typeInfo, + boolean contractEligible, + Set collectedContracts, + Set collectedFullyQualified) { + TypeName typeName = typeInfo.typeName(); + + boolean addedThisContract = false; + if (contractEligible) { + collectedContracts.add(typeName); + addedThisContract = true; + if (!collectedFullyQualified.add(typeName.resolvedName())) { + // let us go no further, this type was already processed + return; + } + } + + if (typeName.isSupplier()) { + // this may be the interface itself, and then it does not have a type argument + if (!typeName.typeArguments().isEmpty()) { + // provider must have a type argument (and the type argument is an automatic contract + TypeName providedType = typeName.typeArguments().getFirst(); + if (!providedType.generic()) { + Optional providedTypeInfo = ctx.typeInfo(providedType); + if (providedTypeInfo.isPresent()) { + contracts(providedTypeInfo.get(), + true, + collectedContracts, + collectedFullyQualified + ); + } else { + collectedContracts.add(providedType); + if (!collectedFullyQualified.add(providedType.resolvedName())) { + // let us go no further, this type was already processed + return; + } + } + } + } + + // provider itself is a contract + if (!addedThisContract) { + collectedContracts.add(typeName); + if (!collectedFullyQualified.add(typeName.resolvedName())) { + // let us go no further, this type was already processed + return; + } + } + } + + // add contracts from interfaces and types annotated as @Contract + typeInfo.findAnnotation(ServiceCodegenTypes.SERVICE_ANNOTATION_CONTRACT) + .ifPresent(it -> collectedContracts.add(typeInfo.typeName())); + + // add contracts from @ExternalContracts + typeInfo.findAnnotation(ServiceCodegenTypes.SERVICE_ANNOTATION_EXTERNAL_CONTRACTS) + .ifPresent(it -> collectedContracts.addAll(it.typeValues().orElseGet(List::of))); + + // go through hierarchy + typeInfo.superTypeInfo().ifPresent(it -> contracts(it, + contractEligible, + collectedContracts, + collectedFullyQualified + )); + // interfaces are considered contracts by default + typeInfo.interfaceTypeInfo().forEach(it -> contracts(it, + contractEligible, + collectedContracts, + collectedFullyQualified + )); + } + + private void singletonInstanceField(ClassModel.Builder classModel, TypeName serviceType, TypeName descriptorType) { + // singleton instance of the descriptor + classModel.addField(instance -> instance.description("Global singleton instance for this descriptor.") + .accessModifier(AccessModifier.PUBLIC) + .isStatic(true) + .isFinal(true) + .type(descriptorInstanceType(serviceType, descriptorType)) + .name("INSTANCE") + .defaultValueContent("new " + descriptorType.className() + "<>()")); + } + + private void serviceTypeFields(ClassModel.Builder classModel, TypeName serviceType, TypeName descriptorType) { + classModel.addField(field -> field + .isStatic(true) + .isFinal(true) + .accessModifier(AccessModifier.PRIVATE) + .type(TypeNames.TYPE_NAME) + .name("SERVICE_TYPE") + .addContentCreate(serviceType.genericTypeName())); + + classModel.addField(field -> field + .isStatic(true) + .isFinal(true) + .accessModifier(AccessModifier.PRIVATE) + .type(TypeNames.TYPE_NAME) + .name("DESCRIPTOR_TYPE") + .addContentCreate(descriptorType.genericTypeName())); + } + + private void dependencyFields(ClassModel.Builder classModel, + TypeInfo service, + Map genericTypes, + List params) { + // constant for dependency + for (ParamDefinition param : params) { + classModel.addField(field -> field + .accessModifier(AccessModifier.PUBLIC) + .isStatic(true) + .isFinal(true) + .type(ServiceCodegenTypes.SERVICE_DEPENDENCY) + .name(param.constantName()) + .description(dependencyDescription(service, param)) + .update(it -> { + it.addContent(ServiceCodegenTypes.SERVICE_DEPENDENCY) + .addContentLine(".builder()") + .increaseContentPadding() + .increaseContentPadding() + .addContent(".typeName(") + .addContent(genericTypes.get(param.translatedType().resolvedName()).constantName()) + .addContentLine(")") + .update(maybeElementKind -> { + if (param.kind() != ElementKind.CONSTRUCTOR) { + // constructor is default and does not need to be defined + maybeElementKind.addContent(".elementKind(") + .addContent(TypeNames.ELEMENT_KIND) + .addContent(".") + .addContent(param.kind().name()) + .addContentLine(")"); + } + }) + .update(maybeMethod -> { + if (param.kind() == ElementKind.METHOD) { + maybeMethod.addContent(".method(") + .addContent(param.methodConstantName()) + .addContentLine(")"); + } + }) + .addContent(".name(\"") + .addContent(param.fieldId()) + .addContentLine("\")") + .addContentLine(".service(SERVICE_TYPE)") + .addContentLine(".descriptor(DESCRIPTOR_TYPE)") + .addContent(".descriptorConstant(\"") + .addContent(param.constantName()) + .addContentLine("\")") + .addContent(".contract(") + .addContent(genericTypes.get(param.contract().fqName()).constantName()) + .addContentLine(")") + .addContent(".contractType(G") + .addContent(genericTypes.get(param.contract().fqName()).constantName()) + .addContentLine(")"); + if (param.access() != AccessModifier.PACKAGE_PRIVATE) { + it.addContent(".access(") + .addContent(TypeNames.ACCESS_MODIFIER) + .addContent(".") + .addContent(param.access().name()) + .addContentLine(")"); + } + + if (param.isStatic()) { + it.addContentLine(".isStatic(true)"); + } + + if (!param.qualifiers().isEmpty()) { + for (Annotation qualifier : param.qualifiers()) { + it.addContent(".addQualifier(qualifier -> qualifier.typeName(") + .addContentCreate(qualifier.typeName().genericTypeName()) + .addContent(")"); + qualifier.value().ifPresent(q -> it.addContent(".value(\"") + .addContent(q) + .addContent("\")")); + it.addContentLine(")"); + } + } + + it.addContent(".build()") + .decreaseContentPadding() + .decreaseContentPadding(); + })); + } + } + + private String dependencyDescription(TypeInfo service, ParamDefinition param) { + TypeName serviceType = service.typeName(); + StringBuilder result = new StringBuilder("Dependency for "); + boolean servicePublic = service.accessModifier() == AccessModifier.PUBLIC; + boolean elementPublic = param.owningElement().accessModifier() == AccessModifier.PUBLIC; + + if (servicePublic) { + result.append("{@link ") + .append(serviceType.fqName()); + if (!elementPublic) { + result.append("}"); + } + } else { + result.append(serviceType.classNameWithEnclosingNames()); + } + + if (servicePublic && elementPublic) { + // full javadoc reference + result + .append("#") + .append(serviceType.className()) + .append("(") + .append(toDescriptionSignature(param.owningElement(), true)) + .append(")") + .append("}"); + } else { + // just text + result.append("(") + .append(toDescriptionSignature(param.owningElement(), false)) + .append(")"); + } + + result + .append(", parameter ") + .append(param.elementInfo().elementName()) + .append("."); + return result.toString(); + } + + private String toDescriptionSignature(TypedElementInfo method, boolean javadoc) { + if (javadoc) { + return method.parameterArguments() + .stream() + .map(it -> it.typeName().fqName()) + .collect(Collectors.joining(", ")); + } else { + return method.parameterArguments() + .stream() + .map(it -> it.typeName().classNameWithEnclosingNames() + " " + it.elementName()) + .collect(Collectors.joining(", ")); + } + } + + private void dependenciesField(ClassModel.Builder classModel, List params) { + classModel.addField(dependencies -> dependencies + .isStatic(true) + .isFinal(true) + .name("DEPENDENCIES") + .type(LIST_OF_DEPENDENCIES) + .addContent(List.class) + .addContent(".of(") + .update(it -> { + Iterator iterator = params.iterator(); + while (iterator.hasNext()) { + it.addContent(iterator.next().constantName()); + if (iterator.hasNext()) { + it.addContent(", "); + } + } + }) + .addContent(")")); + } + + private void serviceTypeMethod(ClassModel.Builder classModel) { + // TypeName serviceType() + classModel.addMethod(method -> method.addAnnotation(Annotations.OVERRIDE) + .returnType(TypeNames.TYPE_NAME) + .name("serviceType") + .addContentLine("return SERVICE_TYPE;")); + } + + private void descriptorTypeMethod(ClassModel.Builder classModel) { + // TypeName descriptorType() + classModel.addMethod(method -> method.addAnnotation(Annotations.OVERRIDE) + .returnType(TypeNames.TYPE_NAME) + .name("descriptorType") + .addContentLine("return DESCRIPTOR_TYPE;")); + } + + private void contractsMethod(ClassModel.Builder classModel, Set contracts) { + if (contracts.isEmpty()) { + return; + } + classModel.addField(contractsField -> contractsField + .isStatic(true) + .isFinal(true) + .name("CONTRACTS") + .type(SET_OF_TYPES) + .addContent(Set.class) + .addContent(".of(") + .update(it -> { + Iterator iterator = contracts.iterator(); + while (iterator.hasNext()) { + it.addContentCreate(iterator.next().genericTypeName()); + if (iterator.hasNext()) { + it.addContent(", "); + } + } + }) + .addContent(")")); + + // Set> contracts() + classModel.addMethod(method -> method.addAnnotation(Annotations.OVERRIDE) + .name("contracts") + .returnType(SET_OF_TYPES) + .addContentLine("return CONTRACTS;")); + } + + private void dependenciesMethod(ClassModel.Builder classModel, List params, SuperType superType) { + // List dependencies() + boolean hasSuperType = superType.hasSupertype(); + if (hasSuperType || !params.isEmpty()) { + classModel.addMethod(method -> method.addAnnotation(Annotations.OVERRIDE) + .returnType(LIST_OF_DEPENDENCIES) + .name("dependencies") + .update(it -> { + if (hasSuperType) { + it.addContentLine("return combineDependencies(DEPENDENCIES, super.dependencies());"); + } else { + it.addContentLine("return DEPENDENCIES;"); + } + })); + } + } + + private void instantiateMethod(ClassModel.Builder classModel, + TypeName serviceType, + List params, + boolean isAbstractClass) { + if (isAbstractClass) { + return; + } + + // T instantiate(DependencyContext ctx__helidonRegistry) + classModel.addMethod(method -> method.addAnnotation(Annotations.OVERRIDE) + .returnType(serviceType) + .name("instantiate") + .addParameter(ctxParam -> ctxParam.type(ServiceCodegenTypes.SERVICE_DEPENDENCY_CONTEXT) + .name("ctx__helidonRegistry")) + .update(it -> createInstantiateBody(serviceType, it, params))); + } + + private void createInstantiateBody(TypeName serviceType, + Method.Builder method, + List params) { + List constructorParams = declareCtrParamsAndGetThem(method, params); + String paramsDeclaration = constructorParams.stream() + .map(ParamDefinition::ipParamName) + .collect(Collectors.joining(", ")); + + // return new MyImpl(parameter, parameter2) + method.addContent("return new ") + .addContent(serviceType.genericTypeName()) + .addContent("(") + .addContent(paramsDeclaration) + .addContentLine(");"); + } + + private void isAbstractMethod(ClassModel.Builder classModel, SuperType superType, boolean isAbstractClass) { + if (!isAbstractClass && !superType.hasSupertype()) { + return; + } + // only override for abstract types (and subtypes, where we do not want to check if super is abstract), default is false + classModel.addMethod(isAbstract -> isAbstract + .name("isAbstract") + .returnType(TypeNames.PRIMITIVE_BOOLEAN) + .addAnnotation(Annotations.OVERRIDE) + .addContentLine("return " + isAbstractClass + ";")); + } + + private void weightMethod(TypeInfo typeInfo, ClassModel.Builder classModel, SuperType superType) { + boolean hasSuperType = superType.hasSupertype(); + // double weight() + Optional weight = weight(typeInfo); + + if (!hasSuperType && weight.isEmpty()) { + return; + } + double usedWeight = weight.orElse(Weighted.DEFAULT_WEIGHT); + if (!hasSuperType && usedWeight == Weighted.DEFAULT_WEIGHT) { + return; + } + + classModel.addMethod(weightMethod -> weightMethod.name("weight") + .addAnnotation(Annotations.OVERRIDE) + .returnType(TypeNames.PRIMITIVE_DOUBLE) + .addContentLine("return " + usedWeight + ";")); + } + + private Optional weight(TypeInfo typeInfo) { + return typeInfo.findAnnotation(TypeName.create(Weight.class)) + .flatMap(Annotation::doubleValue); + } + + private RegistryCodegenContext.Assignment translateParameter(TypeName typeName, String constantName) { + return ctx.assignment(typeName, "ctx__helidonRegistry.dependency(" + constantName + ")"); + } + + private TypeName descriptorInstanceType(TypeName serviceType, TypeName descriptorType) { + return TypeName.builder(descriptorType) + .addTypeArgument(serviceType) + .build(); + } + + private record GenericTypeDeclaration(String constantName, + TypeName typeName) { + } +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/HelidonMetaInfServices.java b/service/codegen/src/main/java/io/helidon/service/codegen/HelidonMetaInfServices.java new file mode 100644 index 00000000000..363d41c2aa7 --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/HelidonMetaInfServices.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2024 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.service.codegen; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.helidon.codegen.CodegenException; +import io.helidon.codegen.CodegenFiler; +import io.helidon.codegen.FilerTextResource; +import io.helidon.common.types.Annotation; +import io.helidon.common.types.TypeName; + +import static io.helidon.codegen.CodegenUtil.generatedAnnotation; + +/** + * Support for reading and writing Helidon services to the resource. + *

+ * Helidon replacement for Java {@link java.util.ServiceLoader}. + * Each service annotated with appropriate annotation + * ({@link io.helidon.service.codegen.ServiceCodegenTypes#SERVICE_ANNOTATION_PROVIDER}) + * will have a service descriptor generated at build time. + *

+ * The service descriptor is then discoverable at runtime through our own resource in {@value #SERVICES_RESOURCE}. + */ +class HelidonMetaInfServices { + /** + * Location of the Helidon meta services file. + */ + static final String SERVICES_RESOURCE = "META-INF/helidon/service.registry"; + + private final FilerTextResource services; + private final List comments; + private final Set descriptors; + + private HelidonMetaInfServices(FilerTextResource services, List comments, Set descriptors) { + this.services = services; + this.comments = comments; + this.descriptors = descriptors; + } + + /** + * Create new instance from the current filer. + * + * @param filer filer to find the file, and to write it + * @param generator generator used in generated comment if we are creating a new file + * @param trigger trigger that caused this file to be created (can be same as generator) + * @param moduleName module that is being built + * @return a new instance of the service metadata manager + */ + static HelidonMetaInfServices create(CodegenFiler filer, TypeName generator, TypeName trigger, String moduleName) { + FilerTextResource services = filer.textResource(SERVICES_RESOURCE); + + List comments = new ArrayList<>(); + Set descriptors = new TreeSet<>(Comparator.comparing(DescriptorMeta::descriptor)); + + for (String line : services.lines()) { + String trimmedLine = line.trim(); + if (trimmedLine.startsWith("#")) { + comments.add(line); + } else if (trimmedLine.isEmpty()) { + // ignore empty lines + continue; + } else { + descriptors.add(DescriptorMeta.parse(trimmedLine)); + } + } + + if (comments.isEmpty()) { + // @Generated + comments.add("# Generated list of service descriptors in module " + moduleName); + comments.add("# " + toAnnotationText(generatedAnnotation(generator, + trigger, + TypeName.create("io.helidon.services.ServicesMeta"), + "1", + ""))); + } + return new HelidonMetaInfServices(services, comments, descriptors); + } + + /** + * Add all descriptors to the file. This never produces duplicate records. + * Descriptor type name is always unique within a file. + * + * @param services service descriptor metadata to add + */ + void addAll(Collection services) { + services.forEach(this::add); + } + + /** + * Add a single descriptors to the file. This never produces duplicate records. + * Descriptor type name is always unique within a file. + * + * @param service service descriptor metadata to add + */ + void add(DescriptorMeta service) { + // if it is the same descriptor class, remove it + descriptors.removeIf(it -> it.descriptor().equals(service.descriptor())); + + // always add the new descriptor (either it does not exist, or it was deleted) + descriptors.add(service); + } + + /** + * Write the file to output. + */ + void write() { + List lines = new ArrayList<>(comments); + descriptors.stream() + .map(DescriptorMeta::toLine) + .forEach(lines::add); + services.lines(lines); + services.write(); + } + + private static String toAnnotationText(Annotation annotation) { + List valuePairs = new ArrayList<>(); + Map annotationValues = annotation.values(); + annotationValues.forEach((key, value) -> valuePairs.add(key + "=\"" + value + "\"")); + return "@" + annotation.typeName().fqName() + "(" + String.join(", ", valuePairs) + ")"; + } + + /** + * Metadata of a single service descriptor. + * This information is stored within the Helidon specific {code META-INF} services file. + * + * @param registryType type of registry, such as {@code core} + * @param descriptor descriptor type + * @param weight weight of the service + * @param contracts contracts the service implements/provides + */ + record DescriptorMeta(String registryType, TypeName descriptor, double weight, Set contracts) { + private static DescriptorMeta parse(String line) { + String[] elements = line.split(":"); + if (elements.length < 4) { + throw new CodegenException("Failed to parse line from existing META-INF/helidon/service.registry: " + + line + ", expecting registry-type:serviceDescriptor:weight:contracts"); + } + Set contracts = Stream.of(elements[3].split(",")) + .map(String::trim) + .map(TypeName::create) + .collect(Collectors.toSet()); + + try { + return new DescriptorMeta(elements[0], + TypeName.create(elements[1]), + Double.parseDouble(elements[2]), + contracts); + } catch (NumberFormatException e) { + throw new CodegenException("Unexpected line structure in service.registry. Third element should be weight " + + "(double). Got: " + line + ", message: " + e.getMessage(), e); + } + } + + private String toLine() { + return registryType + ":" + + descriptor.fqName() + ":" + + weight + ":" + + contracts.stream() + .map(TypeName::fqName) + .collect(Collectors.joining(",")); + } + } +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/ParamDefinition.java b/service/codegen/src/main/java/io/helidon/service/codegen/ParamDefinition.java new file mode 100644 index 00000000000..28b0f0fd2c4 --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/ParamDefinition.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 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.service.codegen; + +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +import io.helidon.codegen.classmodel.ContentBuilder; +import io.helidon.common.types.AccessModifier; +import io.helidon.common.types.Annotation; +import io.helidon.common.types.ElementKind; +import io.helidon.common.types.TypeName; +import io.helidon.common.types.TypedElementInfo; + +/** + * Definition of a single parameter (probably a dependency). + * + * @param owningElement if this is an argument, the constructor or method this belongs to + * @param methodConstantName in case this param belongs to a method, the constant of the method, otherwise null + * @param elementInfo element info of field or argument + * @param constantName name of the constant that holds the IpId of this parameter + * @param declaredType type of the field as required by the dependency + * @param translatedType type used for fulfilling this dependency (e.g. using Supplier where #type uses Provider), + * same instance as #type if not translated + * @param assignmentHandler to provide source for assigning the result from dependency context + * @param kind kind of the owning element (field, method, constructor) + * @param ipName name of the field or method + * @param ipParamName name of the field or parameter + * @param fieldId unique identification of this param within the type (field name, methodid + param name) + * @param isStatic whether the field is static + * @param annotations annotations on this dependency + * @param qualifiers qualifiers of this dependency + * @param contract contract expected for this dependency (ignoring list, supplier, optional etc.) + * @param access access modifier of this param + * @param methodId id of the method (unique identification of method within the class) + */ +record ParamDefinition(TypedElementInfo owningElement, + String methodConstantName, + TypedElementInfo elementInfo, + String constantName, + TypeName declaredType, + TypeName translatedType, + Consumer> assignmentHandler, + ElementKind kind, + String ipName, + String ipParamName, + String fieldId, + boolean isStatic, + List annotations, + Set qualifiers, + TypeName contract, + AccessModifier access, + String methodId) { + +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/RegistryCodegenContext.java b/service/codegen/src/main/java/io/helidon/service/codegen/RegistryCodegenContext.java new file mode 100644 index 00000000000..6066e8f90a6 --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/RegistryCodegenContext.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2023, 2024 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.service.codegen; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; + +import io.helidon.codegen.ClassCode; +import io.helidon.codegen.CodegenContext; +import io.helidon.codegen.classmodel.ClassModel; +import io.helidon.codegen.classmodel.ContentBuilder; +import io.helidon.common.types.TypeName; + +/** + * Codegen context adding methods suitable for Helidon Service Registry code generation. + */ +interface RegistryCodegenContext extends CodegenContext { + /** + * Create a new instance from an existing code generation context. + * + * @param context code generation context of the current code generation session + * @return a new Helidon Service Registry code generation context + */ + static RegistryCodegenContext create(CodegenContext context) { + return new RegistryCodegenContextImpl(context); + } + + /** + * Service descriptor of a type that is already created. This allows extensions with lower weight to update + * the code generated descriptor after it was generated. + * + * @param serviceType type of the service (the implementation class we generate descriptor for) + * @return the builder of class model, if the service has a descriptor + */ + Optional descriptor(TypeName serviceType); + + /** + * Add a new service descriptor. + * + * @param registryType service registry this descriptor is designed for (core is the "top" level) + * @param serviceType type of the service (the implementation class we generate descriptor for) + * @param descriptorType type of the service descriptor + * @param descriptor descriptor class model + * @param weight weight of this service descriptor + * @param contracts contracts of this service descriptor + * @param originatingElements possible originating elements (such as Element in APT, or ClassInfo in classpath scanning) + * @throws java.lang.IllegalStateException if an attempt is done to register a new descriptor for the same type + */ + void addDescriptor(String registryType, + TypeName serviceType, + TypeName descriptorType, + ClassModel.Builder descriptor, + double weight, + Set contracts, + Object... originatingElements); + + /** + * Add a new class to be code generated. + * + * @param type type of the new class + * @param newClass builder of the new class + * @param mainTrigger a type that caused this, may be the processor itself, if not bound to any type + * @param originatingElements possible originating elements (such as Element in APT, or ClassInfo in classpath scanning) + */ + void addType(TypeName type, ClassModel.Builder newClass, TypeName mainTrigger, Object... originatingElements); + + /** + * Class for a type. + * + * @param type type of the generated type + * @return class model of the new type if any + */ + Optional type(TypeName type); + + /** + * Create a descriptor type for a service. + * + * @param serviceType type of the service + * @return type of the service descriptor to be generated + */ + TypeName descriptorType(TypeName serviceType); + + /** + * All newly generated types. + * + * @return list of types and their source class model + */ + List types(); + + /** + * All newly generated descriptors. + * + * @return list of descriptors and their source class model + */ + List descriptors(); + + /** + * This provides support for replacements of types. + * + * @param typeName type name as required by the dependency ("injection point") + * @param valueSource code with the source of the parameter as Helidon provides it (such as Supplier of type) + * @return assignment to use for this instance, what type to use in Helidon registry, and code generator to transform to + * desired type + */ + Assignment assignment(TypeName typeName, String valueSource); + + /** + * Assignment for code generation. The original intended purpose is to support {@code Provider} from javax and jakarta + * without a dependency (or need to understand it) in the generator code. + * + * @param usedType type to use as the dependency type using only Helidon supported types + * (i.e. {@link java.util.function.Supplier} instead of jakarta {@code Provider} + * @param codeGenerator code generator that creates appropriate type required by the target + */ + record Assignment(TypeName usedType, Consumer> codeGenerator) { + } +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/RegistryCodegenContextImpl.java b/service/codegen/src/main/java/io/helidon/service/codegen/RegistryCodegenContextImpl.java new file mode 100644 index 00000000000..ad7cfda1a0e --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/RegistryCodegenContextImpl.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2023, 2024 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.service.codegen; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import io.helidon.codegen.ClassCode; +import io.helidon.codegen.CodegenContext; +import io.helidon.codegen.CodegenContextDelegate; +import io.helidon.codegen.classmodel.ClassModel; +import io.helidon.common.types.TypeName; + +class RegistryCodegenContextImpl extends CodegenContextDelegate implements RegistryCodegenContext { + private final List descriptors = new ArrayList<>(); + private final List nonDescriptors = new ArrayList<>(); + + RegistryCodegenContextImpl(CodegenContext context) { + super(context); + } + + @Override + public Optional descriptor(TypeName serviceType) { + Objects.requireNonNull(serviceType); + + for (DescriptorClassCode descriptor : descriptors) { + ClassCode classCode = descriptor.classCode(); + if (classCode.mainTrigger().equals(serviceType)) { + return Optional.of(classCode.classModel()); + } + } + return Optional.empty(); + } + + @Override + public void addDescriptor(String registryType, + TypeName serviceType, + TypeName descriptorType, + ClassModel.Builder descriptor, + double weight, + Set contracts, + Object... originatingElements) { + Objects.requireNonNull(registryType); + Objects.requireNonNull(serviceType); + Objects.requireNonNull(descriptorType); + Objects.requireNonNull(descriptor); + Objects.requireNonNull(contracts); + Objects.requireNonNull(originatingElements); + + descriptors.add(new DescriptorClassCodeImpl(new ClassCode(descriptorType, descriptor, serviceType, originatingElements), + registryType, + weight, + contracts)); + } + + @Override + public void addType(TypeName type, ClassModel.Builder newClass, TypeName mainTrigger, Object... originatingElements) { + nonDescriptors.add(new ClassCode(type, newClass, mainTrigger, originatingElements)); + } + + @Override + public Optional type(TypeName type) { + for (ClassCode classCode : nonDescriptors) { + if (classCode.newType().equals(type)) { + return Optional.of(classCode.classModel()); + } + } + for (DescriptorClassCode descriptor : descriptors) { + ClassCode classCode = descriptor.classCode(); + if (classCode.newType().equals(type)) { + return Optional.of(classCode.classModel()); + } + } + return Optional.empty(); + } + + @Override + public TypeName descriptorType(TypeName serviceType) { + // type is generated in the same package with a name suffix + + return TypeName.builder() + .packageName(serviceType.packageName()) + .className(descriptorClassName(serviceType)) + .build(); + } + + @Override + public List types() { + return nonDescriptors; + } + + @Override + public List descriptors() { + return descriptors; + } + + @Override + public Assignment assignment(TypeName typeName, String valueSource) { + return new Assignment(typeName, it -> it.addContent(valueSource)); + } + + private static String descriptorClassName(TypeName typeName) { + // for MyType.MyService -> MyType_MyService__ServiceDescriptor + + List enclosing = typeName.enclosingNames(); + String namePrefix; + if (enclosing.isEmpty()) { + namePrefix = ""; + } else { + namePrefix = String.join("_", enclosing) + "_"; + } + return namePrefix + + typeName.className() + + "__ServiceDescriptor"; + } + +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/RegistryCodegenExtension.java b/service/codegen/src/main/java/io/helidon/service/codegen/RegistryCodegenExtension.java new file mode 100644 index 00000000000..5414ed3cd00 --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/RegistryCodegenExtension.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023, 2024 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.service.codegen; + +/** + * Code generation extension for Helidon Service REgistry. + */ +interface RegistryCodegenExtension { + /** + * Process a single round. + * + * @param roundContext round context + */ + void process(RegistryRoundContext roundContext); + + /** + * Called when the processing is over, and there will not be an additional processing round. + */ + default void processingOver() { + } +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/RegistryRoundContext.java b/service/codegen/src/main/java/io/helidon/service/codegen/RegistryRoundContext.java new file mode 100644 index 00000000000..b7d50143182 --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/RegistryRoundContext.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023, 2024 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.service.codegen; + +import java.util.Collection; + +import io.helidon.common.types.TypeInfo; +import io.helidon.common.types.TypeName; +import io.helidon.common.types.TypedElementInfo; + +/** + * Context of a single round of code generation. + * For example the first round may generate types, that require additional code generation. + */ +interface RegistryRoundContext { + /** + * Available annotations for this provider. + * + * @return annotation types + */ + Collection availableAnnotations(); + + /** + * All types for processing in this round. + * + * @return all type infos + */ + + Collection types(); + + /** + * All types annotated with a specific annotation. + * + * @param annotationType annotation type + * @return type infos annotated with the provided annotation + */ + + Collection annotatedTypes(TypeName annotationType); + + /** + * All elements annotated with a specific annotation. + * + * @param annotationType annotation type + * @return elements annotated with the provided annotation + */ + Collection annotatedElements(TypeName annotationType); +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/RoundContextImpl.java b/service/codegen/src/main/java/io/helidon/service/codegen/RoundContextImpl.java new file mode 100644 index 00000000000..7e1c6bf8a7d --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/RoundContextImpl.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023, 2024 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.service.codegen; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.helidon.common.types.TypeInfo; +import io.helidon.common.types.TypeName; +import io.helidon.common.types.TypedElementInfo; + +class RoundContextImpl implements RegistryRoundContext { + private final Map> annotationToTypes; + private final List types; + private final Collection annotations; + + RoundContextImpl(Set annotations, + Map> annotationToTypes, + List types) { + + this.annotations = annotations; + this.annotationToTypes = annotationToTypes; + this.types = types; + } + + @Override + public Collection availableAnnotations() { + return annotations; + } + + @Override + public Collection types() { + return types; + } + + @Override + public Collection annotatedElements(TypeName annotationType) { + List typeInfos = annotationToTypes.get(annotationType); + if (typeInfos == null) { + return Set.of(); + } + + List result = new ArrayList<>(); + + for (TypeInfo typeInfo : typeInfos) { + typeInfo.elementInfo() + .stream() + .filter(it -> it.hasAnnotation(annotationType)) + .forEach(result::add); + } + + return result; + } + + @Override + public Collection annotatedTypes(TypeName annotationType) { + List typeInfos = annotationToTypes.get(annotationType); + if (typeInfos == null) { + return Set.of(); + } + + List result = new ArrayList<>(); + + for (TypeInfo typeInfo : typeInfos) { + if (typeInfo.hasAnnotation(annotationType)) { + result.add(typeInfo); + } + } + + return result; + } +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/ServiceCodegenTypes.java b/service/codegen/src/main/java/io/helidon/service/codegen/ServiceCodegenTypes.java new file mode 100644 index 00000000000..785d9567cf0 --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/ServiceCodegenTypes.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023, 2024 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.service.codegen; + +import io.helidon.common.types.TypeName; + +/** + * Types used in code generation of Helidon Service. + */ +public final class ServiceCodegenTypes { + /** + * {@link io.helidon.common.types.TypeName} for {@code io.helidon.service.registry.Service.Provider}. + */ + public static final TypeName SERVICE_ANNOTATION_PROVIDER = TypeName.create("io.helidon.service.registry.Service.Provider"); + /** + * {@link io.helidon.common.types.TypeName} for {@code io.helidon.service.registry.Service.Contract}. + */ + public static final TypeName SERVICE_ANNOTATION_CONTRACT = TypeName.create("io.helidon.service.registry.Service.Contract"); + /** + * {@link io.helidon.common.types.TypeName} for {@code io.helidon.service.registry.Service.ExternalContracts}. + */ + public static final TypeName SERVICE_ANNOTATION_EXTERNAL_CONTRACTS = TypeName.create("io.helidon.service.registry.Service" + + ".ExternalContracts"); + /** + * {@link io.helidon.common.types.TypeName} for {@code io.helidon.service.registry.Service.Descriptor}. + */ + public static final TypeName SERVICE_ANNOTATION_DESCRIPTOR = + TypeName.create("io.helidon.service.registry.Service.Descriptor"); + /** + * {@link io.helidon.common.types.TypeName} for {@code io.helidon.service.registry.GeneratedService.Descriptor}. + */ + public static final TypeName SERVICE_DESCRIPTOR = TypeName.create("io.helidon.service.registry.GeneratedService.Descriptor"); + /** + * {@link io.helidon.common.types.TypeName} for {@code io.helidon.service.registry.Dependency}. + */ + public static final TypeName SERVICE_DEPENDENCY = TypeName.create("io.helidon.service.registry.Dependency"); + /** + * {@link io.helidon.common.types.TypeName} for {@code io.helidon.service.registry.DependencyContext}. + */ + public static final TypeName SERVICE_DEPENDENCY_CONTEXT = TypeName.create("io.helidon.service.registry.DependencyContext"); + + private ServiceCodegenTypes() { + } +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/ServiceExtension.java b/service/codegen/src/main/java/io/helidon/service/codegen/ServiceExtension.java new file mode 100644 index 00000000000..7328c4112aa --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/ServiceExtension.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023, 2024 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.service.codegen; + +import java.util.Collection; + +import io.helidon.common.types.TypeInfo; +import io.helidon.common.types.TypeName; + +class ServiceExtension implements RegistryCodegenExtension { + private static final TypeName GENERATOR = TypeName.create(ServiceExtension.class); + + private final RegistryCodegenContext ctx; + + ServiceExtension(RegistryCodegenContext codegenContext) { + this.ctx = codegenContext; + } + + @Override + public void process(RegistryRoundContext roundContext) { + Collection descriptorsRequired = roundContext.types(); + + for (TypeInfo typeInfo : descriptorsRequired) { + generateDescriptor(descriptorsRequired, typeInfo); + } + } + + private void generateDescriptor(Collection services, + TypeInfo typeInfo) { + GenerateServiceDescriptor.generate(GENERATOR, ctx, services, typeInfo); + } +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/ServiceOptions.java b/service/codegen/src/main/java/io/helidon/service/codegen/ServiceOptions.java new file mode 100644 index 00000000000..dae3314baf4 --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/ServiceOptions.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023, 2024 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.service.codegen; + +import io.helidon.codegen.Option; + +/** + * Supported options specific to Helidon Service Registry. + */ +final class ServiceOptions { + /** + * Treat all super types as a contract for a given service type being added. + */ + public static final Option AUTO_ADD_NON_CONTRACT_INTERFACES = + Option.create("helidon.registry.autoAddNonContractInterfaces", + "Treat all super types as a contract for a given service type being added.", + false); + + private ServiceOptions() { + } +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/ServiceRegistryCodegenExtension.java b/service/codegen/src/main/java/io/helidon/service/codegen/ServiceRegistryCodegenExtension.java new file mode 100644 index 00000000000..0b60de1a369 --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/ServiceRegistryCodegenExtension.java @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2023, 2024 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.service.codegen; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; + +import io.helidon.codegen.ClassCode; +import io.helidon.codegen.CodegenContext; +import io.helidon.codegen.CodegenFiler; +import io.helidon.codegen.CodegenOptions; +import io.helidon.codegen.ModuleInfo; +import io.helidon.codegen.classmodel.ClassModel; +import io.helidon.codegen.spi.CodegenExtension; +import io.helidon.common.Weighted; +import io.helidon.common.types.Annotation; +import io.helidon.common.types.TypeInfo; +import io.helidon.common.types.TypeName; +import io.helidon.common.types.TypeNames; +import io.helidon.common.types.TypedElementInfo; +import io.helidon.service.codegen.HelidonMetaInfServices.DescriptorMeta; + +import static io.helidon.service.codegen.ServiceCodegenTypes.SERVICE_ANNOTATION_DESCRIPTOR; + +/** + * Handles processing of all extensions, creates context and writes types. + */ +class ServiceRegistryCodegenExtension implements CodegenExtension { + private static final TypeName TYPE = TypeName.create(ServiceRegistryCodegenExtension.class); + + private final Map> typeToExtensions = new HashMap<>(); + private final Map> extensionPredicates = new IdentityHashMap<>(); + private final Set generatedServiceDescriptors = new HashSet<>(); + private final TypeName generator; + private final RegistryCodegenContext ctx; + private final List extensions; + private final String module; + + private ServiceRegistryCodegenExtension(CodegenContext ctx, TypeName generator) { + this.ctx = RegistryCodegenContext.create(ctx); + this.generator = generator; + this.module = ctx.moduleName().orElse(null); + + ServiceExtension serviceExtension = new ServiceExtension(this.ctx); + this.extensions = List.of(serviceExtension); + this.typeToExtensions.put(ServiceCodegenTypes.SERVICE_ANNOTATION_PROVIDER, List.of(serviceExtension)); + } + + static ServiceRegistryCodegenExtension create(CodegenContext ctx, TypeName generator) { + return new ServiceRegistryCodegenExtension(ctx, generator); + } + + @Override + public void process(io.helidon.codegen.RoundContext roundContext) { + Collection allTypes = roundContext.types(); + if (allTypes.isEmpty()) { + extensions.forEach(it -> it.process(createRoundContext(List.of(), it))); + return; + } + + // type info list will contain all mapped annotations, so this is the state we can do annotation processing on + List annotatedTypes = annotatedTypes(allTypes); + + // and now for each extension, we discover types that contain annotations supported by that extension + // and create a new round context for each extension + + // for each extension, create a RoundContext with just the stuff it wants + for (RegistryCodegenExtension extension : extensions) { + extension.process(createRoundContext(annotatedTypes, extension)); + } + + writeNewTypes(); + + for (TypeInfo typeInfo : roundContext.annotatedTypes(SERVICE_ANNOTATION_DESCRIPTOR)) { + // add each declared descriptor in source code + Annotation descriptorAnnot = typeInfo.annotation(SERVICE_ANNOTATION_DESCRIPTOR); + + double weight = descriptorAnnot.doubleValue("weight").orElse(Weighted.DEFAULT_WEIGHT); + Set contracts = descriptorAnnot.typeValues("contracts") + .map(Set::copyOf) + .orElseGet(Set::of); + + String registryType = descriptorAnnot.stringValue("registryType").orElse("core"); + + // predefined service descriptor + generatedServiceDescriptors.add(new DescriptorMeta(registryType, + typeInfo.typeName(), + weight, + contracts)); + } + + if (roundContext.availableAnnotations().size() == 1 && roundContext.availableAnnotations() + .contains(TypeNames.GENERATED)) { + + // no other types generated by Helidon annotation processors, we can generate module component (unless already done) + if (!generatedServiceDescriptors.isEmpty()) { + addDescriptorsToServiceMeta(); + generatedServiceDescriptors.clear(); + } + } + } + + @Override + public void processingOver(io.helidon.codegen.RoundContext roundContext) { + // do processing over in each extension + extensions.forEach(RegistryCodegenExtension::processingOver); + + // if there was any type generated, write it out (will not trigger next round) + writeNewTypes(); + + if (!generatedServiceDescriptors.isEmpty()) { + // re-check, maybe we run from a tool that does not generate anything except for the module component, + // so let's create it now anyway (if created above, the set of descriptors is empty, so it is not attempted again + // if somebody adds a service descriptor when processingOver, than it is wrong anyway + addDescriptorsToServiceMeta(); + generatedServiceDescriptors.clear(); + } + } + + private void addDescriptorsToServiceMeta() { + // and write the module component + Optional currentModule = ctx.module(); + + // generate module + String moduleName = this.module == null ? currentModule.map(ModuleInfo::name).orElse(null) : module; + String packageName = CodegenOptions.CODEGEN_PACKAGE.findValue(ctx.options()) + .orElseGet(() -> topLevelPackage(generatedServiceDescriptors)); + boolean hasModule = moduleName != null && !"unnamed module".equals(moduleName); + if (!hasModule) { + moduleName = "unnamed/" + packageName + (ctx.scope().isProduction() ? "" : "/" + ctx.scope().name()); + } + HelidonMetaInfServices services = HelidonMetaInfServices.create(ctx.filer(), + TYPE, + TYPE, + moduleName); + + services.addAll(generatedServiceDescriptors); + services.write(); + } + + private void writeNewTypes() { + // after each round, write all generated types + CodegenFiler filer = ctx.filer(); + + // generate all code + var descriptors = ctx.descriptors(); + for (var descriptor : descriptors) { + ClassCode classCode = descriptor.classCode(); + ClassModel classModel = classCode.classModel().build(); + generatedServiceDescriptors.add(new DescriptorMeta(descriptor.registryType(), + classCode.newType(), + descriptor.weight(), + descriptor.contracts())); + filer.writeSourceFile(classModel, classCode.originatingElements()); + } + descriptors.clear(); + + var otherTypes = ctx.types(); + for (var classCode : otherTypes) { + ClassModel classModel = classCode.classModel().build(); + filer.writeSourceFile(classModel, classCode.originatingElements()); + } + otherTypes.clear(); + } + + private List annotatedTypes(Collection allTypes) { + List result = new ArrayList<>(); + + for (TypeInfo typeInfo : allTypes) { + result.add(new TypeInfoAndAnnotations(typeInfo, annotations(typeInfo))); + } + return result; + } + + private RegistryRoundContext createRoundContext(List annotatedTypes, + RegistryCodegenExtension extension) { + Set extAnnots = new HashSet<>(); + Map> extAnnotToType = new HashMap<>(); + Map extTypes = new HashMap<>(); + + for (TypeInfoAndAnnotations annotatedType : annotatedTypes) { + for (TypeName typeName : annotatedType.annotations()) { + boolean added = false; + List validExts = this.typeToExtensions.get(typeName); + if (validExts != null) { + for (RegistryCodegenExtension validExt : validExts) { + if (validExt == extension) { + extAnnots.add(typeName); + extAnnotToType.computeIfAbsent(typeName, key -> new ArrayList<>()) + .add(annotatedType.typeInfo()); + extTypes.put(annotatedType.typeInfo().typeName(), annotatedType.typeInfo); + added = true; + } + } + } + if (!added) { + Predicate predicate = this.extensionPredicates.get(extension); + if (predicate != null && predicate.test(typeName)) { + extAnnots.add(typeName); + extAnnotToType.computeIfAbsent(typeName, key -> new ArrayList<>()) + .add(annotatedType.typeInfo()); + extTypes.put(annotatedType.typeInfo().typeName(), annotatedType.typeInfo); + } + } + } + } + + return new RoundContextImpl( + Set.copyOf(extAnnots), + Map.copyOf(extAnnotToType), + List.copyOf(extTypes.values())); + } + + private Set annotations(TypeInfo theTypeInfo) { + Set result = new HashSet<>(); + + // on type + theTypeInfo.annotations() + .stream() + .map(Annotation::typeName) + .forEach(result::add); + + // on fields, methods etc. + theTypeInfo.elementInfo() + .stream() + .map(TypedElementInfo::annotations) + .flatMap(List::stream) + .map(Annotation::typeName) + .forEach(result::add); + + // on parameters + theTypeInfo.elementInfo() + .stream() + .map(TypedElementInfo::parameterArguments) + .flatMap(List::stream) + .map(TypedElementInfo::annotations) + .flatMap(List::stream) + .map(Annotation::typeName) + .forEach(result::add); + + return result; + } + + private String topLevelPackage(Set typeNames) { + String thePackage = typeNames.iterator().next().descriptor().packageName(); + + for (DescriptorMeta typeName : typeNames) { + String nextPackage = typeName.descriptor().packageName(); + if (nextPackage.length() < thePackage.length()) { + thePackage = nextPackage; + } + } + + return thePackage; + } + + private record TypeInfoAndAnnotations(TypeInfo typeInfo, Set annotations) { + } +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/ServiceRegistryCodegenProvider.java b/service/codegen/src/main/java/io/helidon/service/codegen/ServiceRegistryCodegenProvider.java new file mode 100644 index 00000000000..de39791ea3d --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/ServiceRegistryCodegenProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023, 2024 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.service.codegen; + +import java.util.Set; + +import io.helidon.codegen.CodegenContext; +import io.helidon.codegen.Option; +import io.helidon.codegen.spi.CodegenExtension; +import io.helidon.codegen.spi.CodegenExtensionProvider; +import io.helidon.common.types.TypeName; +import io.helidon.common.types.TypeNames; + +/** + * A {@link java.util.ServiceLoader} provider implementation for {@link io.helidon.codegen.spi.CodegenExtensionProvider} + * that handles Helidon Service Registry code generation. + */ +public class ServiceRegistryCodegenProvider implements CodegenExtensionProvider { + private static final Set> SUPPORTED_OPTIONS = Set.of( + ServiceOptions.AUTO_ADD_NON_CONTRACT_INTERFACES + ); + + private static final Set SUPPORTED_ANNOTATIONS = Set.of( + TypeNames.GENERATED, + ServiceCodegenTypes.SERVICE_ANNOTATION_DESCRIPTOR, + ServiceCodegenTypes.SERVICE_ANNOTATION_PROVIDER + ); + private static final Set SUPPORTED_ANNOTATION_PACKAGES = Set.of(); + + /** + * Required default constructor. + * + * @deprecated required by {@link java.util.ServiceLoader} + */ + @Deprecated + public ServiceRegistryCodegenProvider() { + } + + @Override + public Set> supportedOptions() { + return SUPPORTED_OPTIONS; + } + + @Override + public Set supportedAnnotations() { + return SUPPORTED_ANNOTATIONS; + } + + @Override + public Set supportedAnnotationPackages() { + return SUPPORTED_ANNOTATION_PACKAGES; + } + + @Override + public CodegenExtension create(CodegenContext ctx, TypeName generatorType) { + return ServiceRegistryCodegenExtension.create(ctx, generatorType); + } +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/SuperType.java b/service/codegen/src/main/java/io/helidon/service/codegen/SuperType.java new file mode 100644 index 00000000000..ac189297b54 --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/SuperType.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 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.service.codegen; + +import io.helidon.common.types.TypeInfo; +import io.helidon.common.types.TypeName; + +/** + * Definition of a super type (if any). + * + * @param hasSupertype whether there is a super type (declared through {@code extends SuperType}) + * @param superDescriptorType type name of the service descriptor of the super type + * @param superType type info of the super type + */ +record SuperType(boolean hasSupertype, + TypeName superDescriptorType, + TypeInfo superType, + boolean superTypeIsCore) { + static SuperType noSuperType() { + return new SuperType(false, null, null, false); + } +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/TypedElements.java b/service/codegen/src/main/java/io/helidon/service/codegen/TypedElements.java new file mode 100644 index 00000000000..f05eebab293 --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/TypedElements.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024 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.service.codegen; + +import java.util.ArrayList; +import java.util.List; + +import io.helidon.codegen.ElementInfoPredicates; +import io.helidon.common.types.AccessModifier; +import io.helidon.common.types.ElementKind; +import io.helidon.common.types.TypeInfo; +import io.helidon.common.types.TypeName; +import io.helidon.common.types.TypeNames; +import io.helidon.common.types.TypedElementInfo; + +import static java.util.function.Predicate.not; + +final class TypedElements { + static final ElementMeta DEFAULT_CONSTRUCTOR = new ElementMeta(TypedElementInfo.builder() + .typeName(TypeNames.OBJECT) + .accessModifier(AccessModifier.PUBLIC) + .kind(ElementKind.CONSTRUCTOR) + .build()); + + private TypedElements() { + } + + static List gatherElements(TypeInfo typeInfo) { + List result = new ArrayList<>(); + + List declaredElements = typeInfo.elementInfo() + .stream() + .toList(); + + for (TypedElementInfo declaredMethod : declaredElements) { + List interfaceMethods = new ArrayList<>(); + + if (declaredMethod.kind() == ElementKind.METHOD) { + // now find the same method on any interface (if declared there) + for (TypeInfo info : typeInfo.interfaceTypeInfo()) { + info.elementInfo() + .stream() + .filter(ElementInfoPredicates::isMethod) + .filter(not(ElementInfoPredicates::isStatic)) + .filter(not(ElementInfoPredicates::isPrivate)) + .filter(it -> signatureMatches(declaredMethod, it)) + .findFirst() + .ifPresent(it -> interfaceMethods.add(new TypedElements.DeclaredElement(info, it))); + } + } + result.add(new TypedElements.ElementMeta(declaredMethod, interfaceMethods)); + } + + return result; + } + + private static boolean signatureMatches(TypedElementInfo method, TypedElementInfo interfaceMethod) { + // if the method has the same name and same parameter types, it is our candidate (return type MUST be the same, + // as otherwise this could not be compiled + if (!ElementInfoPredicates.elementName(method.elementName()).test(interfaceMethod)) { + return false; + } + List expectedParams = method.parameterArguments() + .stream() + .map(TypedElementInfo::typeName) + .toList(); + + return ElementInfoPredicates.hasParams(expectedParams).test(interfaceMethod); + } + + record ElementMeta(TypedElementInfo element, + List interfaceMethods) { + ElementMeta(TypedElementInfo element) { + this(element, List.of()); + } + } + + record DeclaredElement(TypeInfo iface, + TypedElementInfo element) { + } +} diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/package-info.java b/service/codegen/src/main/java/io/helidon/service/codegen/package-info.java new file mode 100644 index 00000000000..7a1f66cfe26 --- /dev/null +++ b/service/codegen/src/main/java/io/helidon/service/codegen/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023, 2024 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. + */ + +/** + * Code generation for Helidon Service Registry. + */ +package io.helidon.service.codegen; diff --git a/service/codegen/src/main/java/module-info.java b/service/codegen/src/main/java/module-info.java new file mode 100644 index 00000000000..58bbe69d2ad --- /dev/null +++ b/service/codegen/src/main/java/module-info.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 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. + */ + +import io.helidon.service.codegen.ServiceRegistryCodegenProvider; + +/** + * Code generation for Helidon Service Registry. + */ +module io.helidon.service.codegen { + requires transitive io.helidon.builder.api; + requires transitive io.helidon.codegen.classmodel; + requires transitive io.helidon.codegen; + + exports io.helidon.service.codegen; + + provides io.helidon.codegen.spi.CodegenExtensionProvider + with ServiceRegistryCodegenProvider; +} \ No newline at end of file diff --git a/service/pom.xml b/service/pom.xml new file mode 100644 index 00000000000..412833b6902 --- /dev/null +++ b/service/pom.xml @@ -0,0 +1,63 @@ + + + + + 4.0.0 + + io.helidon + helidon-project + 4.0.0-SNAPSHOT + + + io.helidon.service + helidon-service-project + Helidon Service Project + + Service declaration and registry support. + + + pom + + + true + + + + codegen + registry + tests + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + check-dependencies + verify + + + + + + diff --git a/service/registry/etc/spotbugs/exclude.xml b/service/registry/etc/spotbugs/exclude.xml new file mode 100644 index 00000000000..bafc73c84e1 --- /dev/null +++ b/service/registry/etc/spotbugs/exclude.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/service/registry/pom.xml b/service/registry/pom.xml new file mode 100644 index 00000000000..0e9379c3835 --- /dev/null +++ b/service/registry/pom.xml @@ -0,0 +1,105 @@ + + + + + + io.helidon.service + helidon-service-project + 4.0.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + helidon-service-registry + Helidon Service Registry + + Service registry (a replacement for service loader that allows dependencies on other services), + and API necessary to declare services. + + + + etc/spotbugs/exclude.xml + + + + + io.helidon.common + helidon-common + + + io.helidon.common + helidon-common-types + + + io.helidon.builder + helidon-builder-api + + + io.helidon.common + helidon-common-config + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.helidon.codegen + helidon-codegen-apt + ${helidon.version} + + + io.helidon.builder + helidon-builder-codegen + ${helidon.version} + + + io.helidon.codegen + helidon-codegen-helidon-copyright + ${helidon.version} + + + + + + io.helidon.codegen + helidon-codegen-apt + ${helidon.version} + + + io.helidon.builder + helidon-builder-codegen + ${helidon.version} + + + io.helidon.codegen + helidon-codegen-helidon-copyright + ${helidon.version} + + + + + + diff --git a/service/registry/src/main/java/io/helidon/service/registry/CoreServiceDiscovery.java b/service/registry/src/main/java/io/helidon/service/registry/CoreServiceDiscovery.java new file mode 100644 index 00000000000..66ce6106a3b --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/CoreServiceDiscovery.java @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.System.Logger.Level; +import java.lang.reflect.Field; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.helidon.common.LazyValue; +import io.helidon.common.Weighted; +import io.helidon.common.Weights; +import io.helidon.common.types.TypeName; +import io.helidon.service.registry.GeneratedService.Descriptor; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.function.Predicate.not; + +class CoreServiceDiscovery implements ServiceDiscovery { + private static final System.Logger LOGGER = System.getLogger(CoreServiceDiscovery.class.getName()); + + private final List allDescriptors; + + private CoreServiceDiscovery(ServiceRegistryConfig config) { + Map allDescriptors = new LinkedHashMap<>(); + + ClassLoader classLoader = classLoader(); + + // each line is a type:service-descriptor:weight:contract,contract + if (config.discoverServices()) { + classLoader.resources(SERVICES_RESOURCE) + .flatMap(CoreServiceDiscovery::loadLines) + .filter(not(Line::isEmpty)) + .filter(not(Line::isComment)) + .flatMap(DescriptorMeta::parse) + .forEach(it -> allDescriptors.putIfAbsent(it.descriptorType(), it)); + } + + List result = new ArrayList<>(allDescriptors.values()); + + if (config.discoverServicesFromServiceLoader()) { + // each line is a provider type name (and may have zero or more implementations) + classLoader.resources(SERVICES_LOADER_RESOURCE) + .flatMap(CoreServiceDiscovery::loadLines) + .filter(not(Line::isEmpty)) + .filter(not(Line::isComment)) + .flatMap(it -> DescriptorMeta.parseServiceProvider(classLoader, it)) + .forEach(result::add); + } + + this.allDescriptors = List.copyOf(result); + } + + static ServiceDiscovery create(ServiceRegistryConfig config) { + return new CoreServiceDiscovery(config); + } + + static ServiceDiscovery noop() { + return NoopServiceDiscovery.INSTANCE; + } + + static Object instantiateServiceLoaded(TypeName typeName) { + // service loaded is a public class with a public no-args constructor + // in Helidon, we always export it; in user's libraries, they would need to open the module + // to this module, or export it + Class clazz = toClass(typeName); + + try { + return clazz.getConstructor() + .newInstance(); + } catch (ReflectiveOperationException e) { + throw new ServiceRegistryException("Could not obtain the instance of service provider implementation " + + typeName.fqName() + ", please either export the package it defines it," + + " or open it to io.helidon.service.registry module", + e); + } + } + + @Override + public List allMetadata() { + return allDescriptors; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private static Class toClass(TypeName descriptorType) { + try { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + cl = (cl == null) ? CoreServiceDiscovery.class.getClassLoader() : cl; + + return (Class) cl.loadClass(descriptorType.fqName()); + } catch (ClassNotFoundException e) { + throw new ServiceRegistryException("Resolution of service descriptor \"" + descriptorType.fqName() + + "\" to class failed.", + e); + } + + } + + private static Descriptor getDescriptorInstance(TypeName descriptorType) { + Class clazz = toClass(descriptorType); + + try { + Field field = clazz.getField("INSTANCE"); + return (Descriptor) field.get(null); + } catch (ReflectiveOperationException e) { + throw new ServiceRegistryException("Could not obtain the instance of service descriptor " + + descriptorType.fqName(), + e); + } + } + + private static ClassLoader classLoader() { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl == null) { + return CoreServiceDiscovery.class.getClassLoader(); + } + return cl; + } + + private static Stream loadLines(URL url) { + + try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) { + List lines = new ArrayList<>(); + + String line; + int lineNumber = 0; + + while ((line = br.readLine()) != null) { + lineNumber++; // we want to start with 1 + lines.add(new Line(url.toString(), line, lineNumber)); + } + + return lines.stream(); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Failed to read services from " + url, e); + return Stream.of(); + } + } + + private static DescriptorMeta createServiceProviderDescriptor(TypeName providerType, TypeName providerImpl) { + Class serviceClass = toClass(providerImpl); + double weight = Weights.find(serviceClass, Weighted.DEFAULT_WEIGHT); + + if (LOGGER.isLoggable(Level.TRACE)) { + LOGGER.log(Level.TRACE, + "Discovered service provider for type %s: %s, weight: %s".formatted(providerType.fqName(), + providerImpl.fqName(), + weight)); + } + + Descriptor descriptor = ServiceLoader__ServiceDescriptor.create(providerType, providerImpl, weight); + return new DescriptorMeta("core", + descriptor.descriptorType(), + weight, + descriptor.contracts(), + LazyValue.create(descriptor)); + } + + private static boolean isJavaModule(Module module) { + String name = module.getName(); + if (name == null || name.isEmpty()) { + return true; + } + return name.startsWith("java.") || name.startsWith("jdk."); + } + + private record Line(String source, String line, int lineNumber) { + boolean isEmpty() { + return line.isEmpty(); + } + + boolean isComment() { + return line.startsWith("#"); + } + } + + private record DescriptorMeta(String registryType, + TypeName descriptorType, + double weight, + Set contracts, + LazyValue> descriptorSupplier) implements DescriptorMetadata { + DescriptorMeta(String registryType, TypeName descriptorType, double weight, Set contract) { + this(registryType, descriptorType, weight, contract, LazyValue.create(() -> getDescriptorInstance(descriptorType))); + } + + @Override + public Descriptor descriptor() { + return descriptorSupplier.get(); + } + + private static Stream parseServiceProvider(ClassLoader classLoader, Line line) { + // io.helidon.config.ConfigSource + TypeName providerType = TypeName.create(line.line.trim()); + String providerInterface = providerType.fqName(); + + // each line is a service implementation + Set implementations = new HashSet<>(); + + // all discovered through META-INF/services + classLoader.resources("META-INF/services/" + providerInterface) + .flatMap(CoreServiceDiscovery::loadLines) + .filter(not(Line::isEmpty)) + .filter(not(Line::isComment)) + .map(Line::line) + .map(TypeName::create) + .forEach(implementations::add); + + // and all from modules + ModuleLayer.boot() + .modules() + .stream() + .filter(not(CoreServiceDiscovery::isJavaModule)) + .map(Module::getDescriptor) + .flatMap(it -> it.provides().stream()) + .filter(it -> it.service().equals(providerInterface)) + .flatMap(it -> it.providers().stream()) + .map(TypeName::create) + .forEach(implementations::add); + + return implementations.stream() + .map(it -> CoreServiceDiscovery.createServiceProviderDescriptor(providerType, it)); + } + + private static Stream parse(Line line) { + // core:io.helidon.ContractImpl__ServiceDescriptor:101.3:io.helidon.Contract,io.helidon.Contract2 + // inject:io.helidon.ContractImpl__ServiceDescriptor:101.3:io.helidon.Contract,io.helidon.Contract2 + String[] components = line.line().split(":"); + if (components.length < 4) { + // allow more, if we need more info in the future, to be backward compatible for libraries + LOGGER.log(Level.WARNING, + "Line " + line.lineNumber() + " of " + line.source() + + " is invalid, should be registry-type:service-descriptor:weight:contracts"); + } + try { + String registryType = components[0]; + TypeName descriptor = TypeName.create(components[1]); + double weight = Double.parseDouble(components[2]); + + if (LOGGER.isLoggable(Level.TRACE)) { + LOGGER.log(Level.TRACE, + "Discovered service descriptor %s, weight: %s".formatted(descriptor.fqName(), + weight)); + } + + Set contracts = Stream.of(components[3].split(",")) + .map(String::trim) + .map(TypeName::create) + .collect(Collectors.toSet()); + + return Stream.of(new DescriptorMeta(registryType, descriptor, weight, contracts)); + } catch (RuntimeException e) { + LOGGER.log(Level.WARNING, + "Line " + line.lineNumber() + " of " + line.source() + + " is invalid, should be service-descriptor:weight:contracts", + e); + return Stream.empty(); + } + } + } + + static class NoopServiceDiscovery implements ServiceDiscovery { + private static final ServiceDiscovery INSTANCE = new NoopServiceDiscovery(); + + @Override + public List allMetadata() { + return List.of(); + } + } +} + diff --git a/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java b/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java new file mode 100644 index 00000000000..91684248441 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.lang.System.Logger.Level; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import io.helidon.common.LazyValue; +import io.helidon.common.types.TypeName; +import io.helidon.service.registry.GeneratedService.Descriptor; + +/** + * Basic implementation of the service registry with simple dependency support. + */ +class CoreServiceRegistry implements ServiceRegistry { + private static final System.Logger LOGGER = System.getLogger(CoreServiceRegistry.class.getName()); + + private static final Comparator PROVIDER_COMPARATOR = + Comparator.comparing(ServiceProvider::weight).reversed() + .thenComparing(ServiceProvider::descriptorType); + + private final Map> providersByContract; + + CoreServiceRegistry(ServiceRegistryConfig config, ServiceDiscovery serviceDiscovery) { + Map> providers = new HashMap<>(); + + // each just once + Set processedDescriptorTypes = new HashSet<>(); + + // add me + ServiceRegistry__ServiceDescriptor registryDescriptor = ServiceRegistry__ServiceDescriptor.INSTANCE; + processedDescriptorTypes.add(registryDescriptor.descriptorType()); + addContracts(providers, registryDescriptor.contracts(), new BoundInstance(registryDescriptor, this)); + + // add explicit instances + config.serviceInstances().forEach((descriptor, instance) -> { + if (processedDescriptorTypes.add(descriptor.descriptorType())) { + BoundInstance bi = new BoundInstance(descriptor, instance); + addContracts(providers, descriptor.contracts(), bi); + } + }); + + // add configured descriptors + for (Descriptor descriptor : config.serviceDescriptors()) { + if (processedDescriptorTypes.add(descriptor.descriptorType())) { + BoundDescriptor bd = new BoundDescriptor(this, descriptor, LazyValue.create(() -> instance(descriptor))); + addContracts(providers, descriptor.contracts(), bd); + } + } + + boolean logUnsupported = LOGGER.isLoggable(Level.TRACE); + + // and finally add discovered instances + for (DescriptorMetadata descriptorMeta : serviceDiscovery.allMetadata()) { + if (!descriptorMeta.registryType().equals(DescriptorMetadata.REGISTRY_TYPE_CORE)) { + // we can only support core services, others should be handled by other registry implementations + if (logUnsupported) { + LOGGER.log(Level.TRACE, + "Ignoring service of type \"" + descriptorMeta.registryType() + "\": " + descriptorMeta); + } + continue; + } + if (processedDescriptorTypes.add(descriptorMeta.descriptorType())) { + DiscoveredDescriptor dd = new DiscoveredDescriptor(this, + descriptorMeta, + LazyValue.create(() -> instance(descriptorMeta.descriptor()))); + addContracts(providers, descriptorMeta.contracts(), dd); + } + } + this.providersByContract = Map.copyOf(providers); + } + + @SuppressWarnings("unchecked") + @Override + public T get(TypeName contract) { + var provider = firstProvider(contract) + .orElseThrow(() -> new ServiceRegistryException("Contract " + contract.fqName() + + " is not supported, there are no service " + + "descriptors in this registry that can satisfy it.")); + return (T) provider.instance(); + } + + @SuppressWarnings("unchecked") + @Override + public Optional first(TypeName contract) { + return firstProvider(contract) + .map(ServiceProvider::instance) + .map(it -> (T) it); + } + + @SuppressWarnings("unchecked") + @Override + public List all(TypeName contract) { + return allProviders(contract).stream() + .map(ServiceProvider::instance) + .map(it -> (T) it) + .collect(Collectors.toList()); + } + + @Override + public Supplier supply(TypeName contract) { + return LazyValue.create(() -> get(contract)); + } + + @Override + public Supplier> supplyFirst(TypeName contract) { + return LazyValue.create(() -> first(contract)); + } + + @Override + public Supplier> supplyAll(TypeName contract) { + return LazyValue.create(() -> all(contract)); + } + + private static void addContracts(Map> providers, + Set contracts, + ServiceProvider provider) { + for (TypeName contract : contracts) { + providers.computeIfAbsent(contract, it -> new TreeSet<>(PROVIDER_COMPARATOR)) + .add(provider); + } + } + + private List allProviders(TypeName contract) { + Set serviceProviders = providersByContract.get(contract); + if (serviceProviders == null) { + return List.of(); + } + + return new ArrayList<>(serviceProviders); + } + + private Optional firstProvider(TypeName contract) { + Set serviceProviders = providersByContract.get(contract); + if (serviceProviders == null) { + return Optional.empty(); + } + ServiceProvider first = serviceProviders.iterator().next(); + return Optional.of(first); + } + + private Object instance(Descriptor descriptor) { + List dependencies = descriptor.dependencies(); + Map collectedDependencies = new HashMap<>(); + + for (Dependency dependency : dependencies) { + TypeName contract = dependency.contract(); + TypeName dependencyType = dependency.typeName(); + + if (dependencyType.isSupplier()) { + TypeName first = dependencyType.typeArguments().getFirst(); + collectedDependencies.put(dependency, (Supplier) () -> dependencyNoSupplier(first, contract)); + } else { + collectedDependencies.put(dependency, dependencyNoSupplier(dependencyType, contract)); + } + } + + return descriptor.instantiate(DependencyContext.create(collectedDependencies)); + } + + private Object dependencyNoSupplier(TypeName dependencyType, TypeName contract) { + if (dependencyType.isList()) { + return all(contract); + } else if (dependencyType.isOptional()) { + return first(contract); + } else { + // contract dependency + return get(contract); + } + } + + private interface ServiceProvider { + Descriptor descriptor(); + + Object instance(); + + double weight(); + + TypeName descriptorType(); + } + + private record BoundInstance(Descriptor descriptor, Object instance) implements ServiceProvider { + @Override + public double weight() { + return descriptor.weight(); + } + + @Override + public TypeName descriptorType() { + return descriptor.descriptorType(); + } + } + + private record BoundDescriptor(CoreServiceRegistry registry, + Descriptor descriptor, + LazyValue lazyInstance, + ReentrantLock lock) implements ServiceProvider { + + private BoundDescriptor(CoreServiceRegistry registry, + Descriptor descriptor, + LazyValue lazyInstance) { + this(registry, descriptor, lazyInstance, new ReentrantLock()); + } + + @Override + public Object instance() { + if (lazyInstance.isLoaded()) { + return lazyInstance.get(); + } + + if (lock.isHeldByCurrentThread()) { + throw new ServiceRegistryException("Cyclic dependency, attempting to obtain an instance of " + + descriptor.serviceType().fqName() + " while instantiating it"); + } + try { + lock.lock(); + return lazyInstance.get(); + } finally { + lock.unlock(); + } + } + + @Override + public double weight() { + return descriptor.weight(); + } + + @Override + public TypeName descriptorType() { + return descriptor.descriptorType(); + } + } + + private record DiscoveredDescriptor(CoreServiceRegistry registry, + DescriptorMetadata metadata, + LazyValue lazyInstance, + ReentrantLock lock) implements ServiceProvider { + + private DiscoveredDescriptor(CoreServiceRegistry registry, + DescriptorMetadata metadata, + LazyValue lazyInstance) { + this(registry, metadata, lazyInstance, new ReentrantLock()); + } + + @Override + public Descriptor descriptor() { + return metadata.descriptor(); + } + + @Override + public Object instance() { + if (lazyInstance.isLoaded()) { + return lazyInstance.get(); + } + if (lock.isHeldByCurrentThread()) { + throw new ServiceRegistryException("Cyclic dependency, attempting to obtain an instance of " + + metadata.descriptor().serviceType().fqName() + + " while instantiating it"); + } + try { + lock.lock(); + return lazyInstance.get(); + } finally { + lock.unlock(); + } + } + + @Override + public double weight() { + return metadata.weight(); + } + + @Override + public TypeName descriptorType() { + return metadata.descriptorType(); + } + } +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistryManager.java b/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistryManager.java new file mode 100644 index 00000000000..1c8f1a8aa28 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistryManager.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +class CoreServiceRegistryManager implements ServiceRegistryManager { + private final ReentrantReadWriteLock lifecycleLock = new ReentrantReadWriteLock(); + private final ServiceRegistryConfig config; + private final ServiceDiscovery discovery; + + private CoreServiceRegistry registry; + + CoreServiceRegistryManager(ServiceRegistryConfig config, ServiceDiscovery discovery) { + this.config = config; + this.discovery = discovery; + } + + @Override + public ServiceRegistry registry() { + Lock readLock = lifecycleLock.readLock(); + try { + readLock.lock(); + if (registry != null) { + return registry; + } + } finally { + readLock.unlock(); + } + + Lock writeLock = lifecycleLock.writeLock(); + try { + writeLock.lock(); + if (registry != null) { + return registry; + } + + registry = new CoreServiceRegistry(config, discovery); + + return registry; + } finally { + writeLock.unlock(); + } + } + + @Override + public void shutdown() { + Lock lock = lifecycleLock.writeLock(); + try { + lock.lock(); + registry = null; + } finally { + lock.unlock(); + } + } + +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/DependencyBlueprint.java b/service/registry/src/main/java/io/helidon/service/registry/DependencyBlueprint.java new file mode 100644 index 00000000000..7f9497676f7 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/DependencyBlueprint.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023, 2024 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.service.registry; + +import io.helidon.builder.api.Option; +import io.helidon.builder.api.Prototype; +import io.helidon.common.GenericType; +import io.helidon.common.types.TypeName; + +/** + * Dependency metadata. + * The basic dependency supports other services to be passed to a constructor parameter. + * The dependency may be a contract, {@link java.util.List} of contracts, or an {@link java.util.Optional} + * of contract, or {@link java.util.function.Supplier} of any of these. + */ +@Prototype.Blueprint +interface DependencyBlueprint { + /** + * Type name of the service that uses this dependency. + * + * @return the service declaring this dependency + */ + TypeName service(); + + /** + * Name of the constructor parameter. + * + * @return unique name of the parameter + */ + String name(); + + /** + * Each dependency ia a specific contract. Each service provides one or more contracts for dependencies. + * For example for {@code List}, the contract is {@code MyService}. + * + * @return contract of the service we depend on + */ + @Option.Redundant + TypeName contract(); + + /** + * Generic type equivalent to {@link Dependency#contract()}. We need both, to prevent reflection at runtime. + * + * @return generic type of the dependency + */ + @Option.Redundant + @Option.Default("OBJECT") + GenericType contractType(); + + /** + * Descriptor declaring this dependency. + * Descriptor is always public. + * + * @return descriptor + */ + @Option.Redundant + TypeName descriptor(); + + /** + * Field name that declares this dependency in the {@link Dependency#descriptor()}. Can be used for code generation. + * This field is always a public constant. + * + * @return field that has the dependency on the descriptor type + */ + @Option.Redundant + String descriptorConstant(); + + /** + * Type of the dependency (exact parameter type with all generics). + * + * @return type of the dependency as {@link io.helidon.common.types.TypeName} + */ + @Option.Redundant(stringValue = false) + TypeName typeName(); +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/DependencyContext.java b/service/registry/src/main/java/io/helidon/service/registry/DependencyContext.java new file mode 100644 index 00000000000..a2ca80dba69 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/DependencyContext.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023, 2024 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.service.registry; + +import java.util.Map; + +/** + * All data needed for creating an instance of a service. + *

+ * The context contains only the services needed for the specific location, as provided by + * {@link ServiceInfo#dependencies()}. + */ +public interface DependencyContext { + /** + * Create a new instance from a prepared map of dependencies. + * + * @param dependencies map of dependency to an instance + * @return context to use for creating instances of services + */ + static DependencyContext create(Map dependencies) { + return new DependencyContextImpl(dependencies); + } + + /** + * Obtain a parameter for a specific dependency. + * The dependency must be known in advance and provided through + * {@link ServiceInfo}. + * + * @param dependency the dependency metadata + * @param type of the parameter, for convenience, the result is cast to this type + * @return value for the parameter, this may be null if allowed + */ + T dependency(Dependency dependency); +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/DependencyContextImpl.java b/service/registry/src/main/java/io/helidon/service/registry/DependencyContextImpl.java new file mode 100644 index 00000000000..b6b21a47f85 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/DependencyContextImpl.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.util.Map; + +class DependencyContextImpl implements DependencyContext { + private final Map dependencies; + + DependencyContextImpl(Map dependencies) { + this.dependencies = dependencies; + } + + @SuppressWarnings("unchecked") + @Override + public T dependency(Dependency dependency) { + return (T) dependencies.get(dependency); + } +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/DescriptorMetadata.java b/service/registry/src/main/java/io/helidon/service/registry/DescriptorMetadata.java new file mode 100644 index 00000000000..c660dae6bb2 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/DescriptorMetadata.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.util.Set; + +import io.helidon.common.types.TypeName; + +/** + * Metadata of a single service descriptor. + * This information is stored within the Helidon specific {code META-INF} services file. + */ +public interface DescriptorMetadata { + /** + * {@link #registryType()} for core services. + */ + String REGISTRY_TYPE_CORE = "core"; + + /** + * Type of registry, such as {@code core}. + * + * @return registry type this descriptor is created for + */ + String registryType(); + + /** + * Descriptor type name. + * + * @return descriptor type + */ + TypeName descriptorType(); + + /** + * Contracts of the service. + * + * @return contracts the service implements/provides. + */ + Set contracts(); + + /** + * Weight of the service. + * + * @return service weight + * @see io.helidon.common.Weight + */ + double weight(); + + /** + * Descriptor instance. + * + * @return the descriptor + */ + GeneratedService.Descriptor descriptor(); +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/GeneratedService.java b/service/registry/src/main/java/io/helidon/service/registry/GeneratedService.java new file mode 100644 index 00000000000..506c42c56a6 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/GeneratedService.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023, 2024 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.service.registry; + +/** + * All types in this class are used from generated code for services. + */ +public final class GeneratedService { + private GeneratedService() { + } + + /** + * A descriptor of a service. In addition to providing service metadata, this also allows instantiation + * of the service instance, with dependent services as parameters. + * + * @param type of the described service + */ + public interface Descriptor extends ServiceInfo { + /** + * Create a new service instance. + * + * @param ctx dependency context with all dependencies of this service + * @return a new instance, must be of the type T or a subclass + */ + // we cannot return T, as it does not allow us to correctly handle inheritance + default Object instantiate(DependencyContext ctx) { + throw new IllegalStateException("Cannot instantiate type " + serviceType().fqName() + ", as it is either abstract," + + " or an interface."); + } + } +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/GlobalServiceRegistry.java b/service/registry/src/main/java/io/helidon/service/registry/GlobalServiceRegistry.java new file mode 100644 index 00000000000..33e4982f209 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/GlobalServiceRegistry.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; + +import io.helidon.common.config.GlobalConfig; + +/** + * A global singleton manager for a service registry. + *

+ * Note that when using this registry, testing is a bit more complicated, as the registry is shared + * statically. + */ +public final class GlobalServiceRegistry { + private static final AtomicReference INSTANCE = new AtomicReference<>(); + private static final ReadWriteLock RW_LOCK = new ReentrantReadWriteLock(); + + private GlobalServiceRegistry() { + } + + /** + * Whether a service registry instance is configured. + * + * @return {@code true} if a registry instance was already created + */ + public static boolean configured() { + return INSTANCE.get() != null; + } + + /** + * Current global service registry, will create a new instance if one is not configured. + * + * @return global service registry + */ + public static ServiceRegistry registry() { + try { + RW_LOCK.readLock().lock(); + ServiceRegistry currentInstance = INSTANCE.get(); + if (currentInstance != null) { + return currentInstance; + } + } finally { + RW_LOCK.readLock().unlock(); + } + try { + RW_LOCK.writeLock().lock(); + ServiceRegistryConfig config; + if (GlobalConfig.configured()) { + config = ServiceRegistryConfig.create(GlobalConfig.config().get("registry")); + } else { + config = ServiceRegistryConfig.create(); + } + ServiceRegistry newInstance = ServiceRegistryManager.create(config).registry(); + INSTANCE.set(newInstance); + return newInstance; + } finally { + RW_LOCK.writeLock().unlock(); + } + } + + /** + * Current global registry if configured, will replace the current global registry with the + * one provided by supplier if none registered. + * + * @param registrySupplier supplier of new global registry if not yet configured + * @return global service registry + */ + public static ServiceRegistry registry(Supplier registrySupplier) { + try { + RW_LOCK.readLock().lock(); + ServiceRegistry currentInstance = INSTANCE.get(); + if (currentInstance != null) { + return currentInstance; + } + } finally { + RW_LOCK.readLock().unlock(); + } + try { + RW_LOCK.writeLock().lock(); + ServiceRegistry newInstance = registrySupplier.get(); + INSTANCE.set(newInstance); + return newInstance; + } finally { + RW_LOCK.writeLock().unlock(); + } + } + + /** + * Set the current global registry. + * This method always returns the same instance as provided, though the next call to + * {@link #registry()} may return a different instance if the instance is replaced by another thread. + * + * @param newGlobalRegistry global registry to set + * @return the same instance + */ + public static ServiceRegistry registry(ServiceRegistry newGlobalRegistry) { + INSTANCE.set(newGlobalRegistry); + return newGlobalRegistry; + } +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/Service.java b/service/registry/src/main/java/io/helidon/service/registry/Service.java new file mode 100644 index 00000000000..2f46fb5ae41 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/Service.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import io.helidon.common.Weighted; +import io.helidon.common.types.TypeName; + +/** + * A set of annotations (and APIs) required to declare a service. + */ +public final class Service { + private Service() { + } + + /** + * A service provider. This is similar to {@link java.util.ServiceLoader} service providers. + *

+ * The provider is an implementation of a {@link io.helidon.service.registry.Service.Contract} that is discoverable by + * the service registry. + * A service provider annotated with this annotation must provide either of: + *

    + *
  • an accessible no-argument constructor + * (package private is sufficient), and must itself be at least package private
  • + *
  • an accessible constructor with arguments that are all services as well
  • + *
+ * + * The support for providing constructor arguments is limited to the following types: + *
    + *
  • {@code Contract} - obtain an instance of a service providing that contract
  • + *
  • {@code Optional} - the other service may not be available
  • + *
  • {@code List} - obtain all instances of services providing the contract
  • + *
  • {@code Supplier} - and suppliers of all above, to break instantiation chaining, and to support cyclic + * service references, just make sure you call the {@code get} method outside of the constructor
  • + *
+ */ + @Documented + @Retention(RetentionPolicy.CLASS) + @Target(ElementType.TYPE) + public @interface Provider { + /** + * Type name of this annotation. + */ + TypeName TYPE = TypeName.create(Provider.class); + } + + /** + * The {@code Contract} annotation is used to relay significance to the type that it annotates. While remaining optional in + * its use, it is typically placed on an interface definition to signify that the given type can be used for lookup in the + * service registry. + * While normally placed on interface types, it can also be placed on abstract and concrete class as well. The main point is + * that a {@code Contract} is the focal point for service lookup. + *

+ * If the developer does not have access to the source to place this annotation on the interface definition directly then + * consider using {@link ExternalContracts} instead - this annotation can be placed on the + * implementation class implementing the given {@code Contract} interface(s). + *

+ * Default behavior of the service registry is to only provide support lookup based on contracts. + */ + @Documented + @Retention(RetentionPolicy.CLASS) + @Target(ElementType.TYPE) + public @interface Contract { + } + + /** + * Placed on the implementation of a service as an alternative to using a {@link Contract}. + *

+ * Use this annotation when it is impossible to place an annotation on the interface itself - for instance of the interface + * comes from a 3rd party library provider. + */ + @Documented + @Retention(RetentionPolicy.CLASS) + @Target(ElementType.TYPE) + public @interface ExternalContracts { + + /** + * The advertised contract type(s) for the service class implementation. + * + * @return the external contract(s) + */ + Class[] value(); + } + + /** + * Annotation to add a custom service descriptor. + *

+ * The service descriptor will be added as any other service descriptor that is generated, only it is expected + * as a source. + * The descriptor MUST follow the approach of Helidon service descriptor, and must be public, + * provide a public constant called {@code INSTANCE}, and all its dependencies + * ({@link io.helidon.service.registry.Dependency} + * must be available as public constants (and correctly described in each Ip instance). + */ + @Documented + @Retention(RetentionPolicy.CLASS) + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + public @interface Descriptor { + /** + * Type name of this interface. + */ + TypeName TYPE = TypeName.create(Descriptor.class); + + /** + * Type of service registry that should read this descriptor. Defaults to + * {@value DescriptorMetadata#REGISTRY_TYPE_CORE}, so the descriptor must only implement + * {@link io.helidon.service.registry.GeneratedService.Descriptor}. + * + * @return type of registry this descriptor supports + */ + String registryType() default DescriptorMetadata.REGISTRY_TYPE_CORE; + + /** + * The weight of the service. This is required for predefined descriptors, as we do not want + * to instantiate them unless really needed. + * + * @return weight of this descriptor + */ + double weight() default Weighted.DEFAULT_WEIGHT; + + /** + * Contracts of this service descriptor. + * Normally these are discovered from the service provider type, but as this is a pre-built descriptor, + * we need to get them through annotation. + * + * @return contracts of this service descriptor + */ + Class[] contracts(); + } +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/ServiceDiscovery.java b/service/registry/src/main/java/io/helidon/service/registry/ServiceDiscovery.java new file mode 100644 index 00000000000..57cacc8337b --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/ServiceDiscovery.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.util.List; + +/** + * Access to discovered service metadata. + */ +public interface ServiceDiscovery { + /** + * A file that contains all services within a module. Each service has an identifier of the registry that + * created it (such as {@code core}), the class of the service descriptor, + * weight of the service, and a list of contracts it implements. + *

+ * This file is used by {@link io.helidon.service.registry.ServiceDiscovery} to find services. + */ + String SERVICES_RESOURCE = "META-INF/helidon/service.registry"; + /** + * A file that contains all service provider interfaces that expose a service for + * Java {@link java.util.ServiceLoader} that is used to tell Helidon Service Registry to add implementations to + * the registry to be discoverable in addition to services defined in {@link #SERVICES_RESOURCE}. + */ + String SERVICES_LOADER_RESOURCE = "META-INF/helidon/service.loader"; + + /** + * Create a new instance that discovers service descriptors from classpath. + * + * @return service discovery based on classpath + */ + static ServiceDiscovery create() { + return create(ServiceRegistryConfig.create()); + } + + /** + * Create a new instance that discovers service descriptors based on the configuration. + * + * @param config registry configuration to control discovery + * @return service discovery based on classpath + */ + static ServiceDiscovery create(ServiceRegistryConfig config) { + return CoreServiceDiscovery.create(config); + } + + /** + * Service discovery that does not discover anything. + * + * @return a no-op service discovery + */ + static ServiceDiscovery noop() { + return CoreServiceDiscovery.noop(); + } + + /** + * All discovered metadata of this service discovery. + * + * @return all discovered metadata + */ + List allMetadata(); +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/ServiceInfo.java b/service/registry/src/main/java/io/helidon/service/registry/ServiceInfo.java new file mode 100644 index 00000000000..50ffcf50e83 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/ServiceInfo.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.util.List; +import java.util.Set; + +import io.helidon.common.Weighted; +import io.helidon.common.types.TypeName; + +/** + * Service metadata. + */ +public interface ServiceInfo extends Weighted { + /** + * Type of the service this descriptor describes. + * + * @return service type + */ + TypeName serviceType(); + + /** + * Type of the service descriptor (usually generated). + * + * @return descriptor type + */ + TypeName descriptorType(); + + /** + * Set of contracts the described service implements. + * + * @return set of contracts + */ + default Set contracts() { + return Set.of(); + } + + /** + * List of dependencies required by this service. + * Each dependency is a point of injection of a dependency into + * a constructor. + * + * @return required dependencies + */ + default List dependencies() { + return List.of(); + } + + /** + * Returns {@code true} for abstract classes and interfaces, + * returns {@code false} by default. + * + * @return whether this descriptor describes an abstract class or interface + */ + default boolean isAbstract() { + return false; + } +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/ServiceLoader__ServiceDescriptor.java b/service/registry/src/main/java/io/helidon/service/registry/ServiceLoader__ServiceDescriptor.java new file mode 100644 index 00000000000..a2a39a053d3 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/ServiceLoader__ServiceDescriptor.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; + +import io.helidon.common.LazyValue; +import io.helidon.common.types.TypeName; + +/** + * Service descriptor to enable dependency on services loaded via {@link java.util.ServiceLoader}. + */ +@SuppressWarnings("checkstyle:TypeName") // matches pattern of generated descriptors +public abstract class ServiceLoader__ServiceDescriptor implements GeneratedService.Descriptor { + private static final TypeName DESCRIPTOR_TYPE = TypeName.create(ServiceLoader__ServiceDescriptor.class); + + // we must use instance comparison, so we must make sure we give the same instance for the same combination + private static final Map INSTANCE_CACHE = new HashMap<>(); + private static final ReentrantLock LOCK = new ReentrantLock(); + + private ServiceLoader__ServiceDescriptor() { + } + + /** + * Create a new instance for a specific service provider interface and its implementation. + * + * @param providerInterface provider interface type + * @param providerImpl provider implementation type + * @param weight weight of the provider + * @return new descriptor + */ + public static ServiceLoader__ServiceDescriptor create(TypeName providerInterface, + TypeName providerImpl, + double weight) { + LOCK.lock(); + try { + ProviderKey key = new ProviderKey(providerInterface, providerImpl); + ServiceLoader__ServiceDescriptor descriptor = INSTANCE_CACHE.get(key); + if (descriptor == null) { + descriptor = new ServiceProviderDescriptor(providerInterface, providerImpl, weight); + INSTANCE_CACHE.put(key, descriptor); + } + + return descriptor; + } finally { + LOCK.unlock(); + } + } + + @Override + public TypeName descriptorType() { + return DESCRIPTOR_TYPE; + } + + /** + * The interface of the service loader provider. + * + * @return provider interface type + */ + public abstract TypeName providerInterface(); + + private static class ServiceProviderDescriptor extends ServiceLoader__ServiceDescriptor { + private final TypeName providerInterface; + private final Set contracts; + private final TypeName providerImpl; + private final double weight; + private final LazyValue instance; + + private ServiceProviderDescriptor(TypeName providerInterface, + TypeName providerImpl, + double weight) { + + this.providerInterface = providerInterface; + this.contracts = Set.of(providerInterface); + this.providerImpl = providerImpl; + this.weight = weight; + this.instance = LazyValue.create(this::newInstance); + } + + @Override + public TypeName serviceType() { + return providerImpl; + } + + @Override + public Set contracts() { + return contracts; + } + + @Override + public Object instantiate(DependencyContext ctx) { + return instance.get(); + } + + @Override + public double weight() { + return weight; + } + + @Override + public TypeName providerInterface() { + return providerInterface; + } + + private Object newInstance() { + return CoreServiceDiscovery.instantiateServiceLoaded(serviceType()); + } + } + + private record ProviderKey(TypeName providerInterface, TypeName providerImpl) { + } +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/ServiceMetadata.java b/service/registry/src/main/java/io/helidon/service/registry/ServiceMetadata.java new file mode 100644 index 00000000000..b77781742bf --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/ServiceMetadata.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.util.Set; + +import io.helidon.common.types.TypeName; +import io.helidon.service.registry.GeneratedService.Descriptor; + +interface ServiceMetadata { + double weight(); + + Set contracts(); + + Descriptor descriptor(); +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry.java b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry.java new file mode 100644 index 00000000000..db60426d0fa --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import io.helidon.common.types.TypeName; + +/** + * Entry point to services in Helidon. + *

+ * The service registry has knowledge about all the services within your application. + */ +@Service.Contract +public interface ServiceRegistry { + /** + * Type name of this interface. + */ + TypeName TYPE = TypeName.create(ServiceRegistry.class); + + /** + * Get the first service instance matching the contract with the expectation that there is a match available. + * + * @param contract contract to look-up + * @param type of the contract + * @return the best service instance matching the contract + * @throws io.helidon.service.registry.ServiceRegistryException if there is no service that could satisfy the lookup, or the + * resolution to instance failed + */ + default T get(Class contract) { + return get(TypeName.create(contract)); + } + + /** + * Get the first service instance matching the contract with the expectation that there is a match available. + * + * @param contract contract to look-up + * @param type of the contract (we will "blindly" cast the result to the expected type, make sure you use the right + * one) + * @return the best service instance matching the contract + * @throws io.helidon.service.registry.ServiceRegistryException if there is no service that could satisfy the lookup, or the + * resolution to instance failed + */ + T get(TypeName contract); + + /** + * Get the first service instance matching the contract with the expectation that there may not be a match available. + * + * @param contract contract to look-up + * @param type of the contract + * @return the best service instance matching the contract, or an empty {@link java.util.Optional} if none match + * @throws io.helidon.service.registry.ServiceRegistryException if there is no service that could satisfy the lookup, or the + * resolution to instance failed + */ + default Optional first(Class contract) { + return first(TypeName.create(contract)); + } + + /** + * Get the first service instance matching the contract with the expectation that there may not be a match available. + * + * @param contract contract to look-up + * @param type of the contract + * @return the best service instance matching the contract, or an empty {@link java.util.Optional} if none match + * @throws io.helidon.service.registry.ServiceRegistryException if there is no service that could satisfy the lookup, or the + * resolution to instance failed + */ + Optional first(TypeName contract); + + /** + * Get all service instances matching the contract with the expectation that there may not be a match available. + * + * @param contract contract to look-up + * @param type of the contract + * @return list of services matching the criteria, may be empty if none matched, or no instances were provided + */ + default List all(Class contract) { + return all(TypeName.create(contract)); + } + + /** + * Get all service instances matching the contract with the expectation that there may not be a match available. + * + * @param contract contract to look-up + * @param type of the contract + * @return list of services matching the criteria, may be empty if none matched, or no instances were provided + */ + List all(TypeName contract); + + /** + * Get the first service supplier matching the contract with the expectation that there is a match available. + * The provided {@link java.util.function.Supplier#get()} may throw an + * {@link io.helidon.service.registry.ServiceRegistryException} in case the matching service cannot provide a value (either + * because of scope mismatch, or because an instance was not provided by the service provider. + * + * @param contract contract to find + * @param type of the contract + * @return the best service supplier matching the lookup + * @throws io.helidon.service.registry.ServiceRegistryException if there is no service that could satisfy the lookup + */ + default Supplier supply(Class contract) { + return supply(TypeName.create(contract)); + } + + /** + * Get the first service supplier matching the contract with the expectation that there is a match available. + * The provided {@link java.util.function.Supplier#get()} may throw an + * {@link io.helidon.service.registry.ServiceRegistryException} in case the matching service cannot provide a value (either + * because of scope mismatch, or because an instance was not provided by the service provider. + * + * @param contract contract to find + * @param type of the contract + * @return the best service supplier matching the lookup + * @throws io.helidon.service.registry.ServiceRegistryException if there is no service that could satisfy the lookup + */ + Supplier supply(TypeName contract); + + /** + * Get the first service supplier matching the contract with the expectation that there may not be a match available. + * + * @param contract contract we look for + * @param type of the contract + * @return supplier of an optional instance + */ + default Supplier> supplyFirst(Class contract) { + return supplyFirst(TypeName.create(contract)); + } + + /** + * Get the first service supplier matching the contract with the expectation that there may not be a match available. + * + * @param contract contract we look for + * @param type of the contract + * @return supplier of an optional instance + */ + Supplier> supplyFirst(TypeName contract); + + /** + * Lookup a supplier of a list of instances of the requested contract, with the expectation that there may not be a + * match available. + * + * @param contract contract we look for + * @param type of the contract + * @return a supplier of list of instances + */ + default Supplier> supplyAll(Class contract) { + return supplyAll(TypeName.create(contract)); + } + + /** + * Lookup a supplier of a list of instances of the requested contract, with the expectation that there may not be a + * match available. + * + * @param contract contract we look for + * @param type of the contract + * @return a supplier of list of instances + */ + Supplier> supplyAll(TypeName contract); +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryConfigBlueprint.java b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryConfigBlueprint.java new file mode 100644 index 00000000000..607f3bc8a4a --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryConfigBlueprint.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import io.helidon.builder.api.Option; +import io.helidon.builder.api.Prototype; +import io.helidon.common.config.Config; +import io.helidon.service.registry.GeneratedService.Descriptor; + +/** + * Helidon service registry configuration. + */ +@Prototype.Blueprint +@Prototype.Configured("registry") +@Prototype.CustomMethods(ServiceRegistryConfigSupport.CustomMethods.class) +interface ServiceRegistryConfigBlueprint { + /** + * Whether to discover services from the class path. + * When set to {@code false}, only services added through {@link #serviceDescriptors()} and/or + * {@link #serviceInstances()} would be available. + * + * @return whether to discover services from classpath, defaults to {@code true} + */ + @Option.Configured + @Option.DefaultBoolean(true) + boolean discoverServices(); + + /** + * Whether to discover services from Java service loader. + * See {@link io.helidon.service.registry.ServiceDiscovery#SERVICES_LOADER_RESOURCE}. + * + * @return whether to discover Java {@link java.util.ServiceLoader} services from classpath (a curated list only), + * defaults to {@code true} + */ + @Option.Configured + @Option.DefaultBoolean(true) + boolean discoverServicesFromServiceLoader(); + + /** + * Manually registered service descriptors to add to the registry. + * This is useful when {@link #discoverServices()} is set to {@code false}, to register only hand-picked services + * into the registry. + *

+ * Even when service discovery is used, this can be used to add service descriptors that are not part of + * a service discovery mechanism (such as testing services). + * + * @return services to register + */ + @Option.Singular + List> serviceDescriptors(); + + /** + * Manually register initial bindings for some of the services in the registry. + * + * @return service instances to register + */ + @Option.Singular + @Option.SameGeneric + Map, Object> serviceInstances(); + + /** + * Config instance used to configure this registry configuration. + * DO NOT USE for application configuration! + * + * @return config node used to configure this service registry config instance (if any) + */ + Optional config(); +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryConfigSupport.java b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryConfigSupport.java new file mode 100644 index 00000000000..15e9af6a88f --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryConfigSupport.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.util.Objects; +import java.util.Set; + +import io.helidon.builder.api.Prototype; +import io.helidon.common.Weighted; +import io.helidon.common.types.TypeName; + +class ServiceRegistryConfigSupport { + private ServiceRegistryConfigSupport() { + } + + static class CustomMethods { + private CustomMethods() { + } + + /** + * Put an instance of a contract outside of service described services. + * This will create a "virtual" service descriptor that will not be valid for metadata operations. + * + * @param builder ignored + * @param contract contract to add a specific instance for + * @param instance instance of the contract + */ + @Prototype.BuilderMethod + static void putContractInstance(ServiceRegistryConfig.BuilderBase builder, + TypeName contract, + Object instance) { + builder.putServiceInstance(new VirtualDescriptor(contract), instance); + } + + /** + * Put an instance of a contract outside of service described services. + * This will create a "virtual" service descriptor that will not be valid for metadata operations. + * + * @param builder ignored + * @param contract contract to add a specific instance for + * @param instance instance of the contract + */ + @Prototype.BuilderMethod + static void putContractInstance(ServiceRegistryConfig.BuilderBase builder, + Class contract, + Object instance) { + putContractInstance(builder, TypeName.create(contract), instance); + } + } + + private static class VirtualDescriptor implements GeneratedService.Descriptor { + private static final TypeName TYPE = TypeName.create(VirtualDescriptor.class); + private final Set contracts; + private final TypeName serviceType; + private final TypeName descriptorType; + + private VirtualDescriptor(TypeName contract) { + this.contracts = Set.of(contract); + this.serviceType = contract; + this.descriptorType = TypeName.builder(TYPE) + .className(TYPE.className() + "_" + contract.className() + "__VirtualDescriptor") + .build(); + } + + @Override + public TypeName serviceType() { + return serviceType; + } + + @Override + public TypeName descriptorType() { + return descriptorType; + } + + @Override + public Set contracts() { + return contracts; + } + + @Override + public double weight() { + return Weighted.DEFAULT_WEIGHT + 1000; + } + + @Override + public int hashCode() { + return Objects.hash(serviceType); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof VirtualDescriptor that)) { + return false; + } + return Objects.equals(serviceType, that.serviceType); + } + } +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryException.java b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryException.java new file mode 100644 index 00000000000..4c4e24621db --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryException.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 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.service.registry; + +/** + * An exception marking a problem with service registry operations. + */ +public class ServiceRegistryException extends RuntimeException { + /** + * Create an exception with a descriptive message. + * + * @param message the message + */ + public ServiceRegistryException(String message) { + super(message); + } + + /** + * Create an exception with a descriptive message and a cause. + * + * @param message the message + * @param cause throwable causing this exception + */ + public ServiceRegistryException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryManager.java b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryManager.java new file mode 100644 index 00000000000..6dcd436db72 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryManager.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 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.service.registry; + +/** + * Manager is responsible for managing the state of a {@link io.helidon.service.registry.ServiceRegistry}. + * Each manager instances owns a single service registry. + *

+ * To use a singleton service across application, either pass it through parameters, or use + * {@link io.helidon.service.registry.GlobalServiceRegistry}. + */ +public interface ServiceRegistryManager { + /** + * Create a new service registry manager with default configuration. + * + * @return a new service registry manager + */ + static ServiceRegistryManager create() { + return create(ServiceRegistryConfig.create()); + } + + /** + * Create a new service registry manager with custom configuration. + * + * @param config configuration of this registry manager + * @return a new configured service registry manager + */ + static ServiceRegistryManager create(ServiceRegistryConfig config) { + return ServiceRegistryManagerDiscovery.create(config); + } + + /** + * Get (or initialize and get) the service registry managed by this manager. + * + * @return service registry ready to be used + */ + ServiceRegistry registry(); + + /** + * Shutdown the managed service registry. + */ + void shutdown(); +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryManagerDiscovery.java b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryManagerDiscovery.java new file mode 100644 index 00000000000..94ea9c0d035 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistryManagerDiscovery.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 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.service.registry; + +import java.util.Optional; +import java.util.ServiceLoader; + +import io.helidon.common.HelidonServiceLoader; +import io.helidon.common.LazyValue; +import io.helidon.service.registry.spi.ServiceRegistryManagerProvider; + +final class ServiceRegistryManagerDiscovery { + private static final Optional REGISTRY_MANAGER_PROVIDER = + HelidonServiceLoader.builder(ServiceLoader.load(ServiceRegistryManagerProvider.class)) + .build() + .stream() + .findFirst(); + + private ServiceRegistryManagerDiscovery() { + } + + static ServiceRegistryManager create(ServiceRegistryConfig config) { + ServiceDiscovery discovery = config.discoverServices() || config.discoverServicesFromServiceLoader() + ? CoreServiceDiscovery.create(config) + : CoreServiceDiscovery.noop(); + + LazyValue coreManager = LazyValue.create(() -> new CoreServiceRegistryManager(config, discovery)); + + return REGISTRY_MANAGER_PROVIDER.map(it -> it.create(config, discovery, coreManager)) + .orElseGet(coreManager); + } +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry__ServiceDescriptor.java b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry__ServiceDescriptor.java new file mode 100644 index 00000000000..e69b6b09808 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry__ServiceDescriptor.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023, 2024 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.service.registry; + +import java.util.Set; + +import io.helidon.common.types.TypeName; + +/** + * Service descriptor to enable dependency on {@link io.helidon.service.registry.ServiceRegistry}. + */ +@SuppressWarnings("checkstyle:TypeName") // matches pattern of generated descriptors +public class ServiceRegistry__ServiceDescriptor implements GeneratedService.Descriptor { + /** + * Singleton instance to be referenced when building applications. + */ + public static final ServiceRegistry__ServiceDescriptor INSTANCE = new ServiceRegistry__ServiceDescriptor(); + + private static final TypeName DESCRIPTOR_TYPE = TypeName.create(ServiceRegistry__ServiceDescriptor.class); + private static final Set CONTRACTS = Set.of(ServiceRegistry.TYPE); + + private ServiceRegistry__ServiceDescriptor() { + } + + @Override + public TypeName serviceType() { + return ServiceRegistry.TYPE; + } + + @Override + public TypeName descriptorType() { + return DESCRIPTOR_TYPE; + } + + @Override + public Set contracts() { + return CONTRACTS; + } +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/package-info.java b/service/registry/src/main/java/io/helidon/service/registry/package-info.java new file mode 100644 index 00000000000..50b743b7f05 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023, 2024 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. + */ + +/** + * API required to define services, and to compile the code generated sources for Helidon Service Registry, + * with a core service registry implementation (replacement for {@link java.util.ServiceLoader}). + *

+ * The following main entry point for declaring services is {@link io.helidon.service.registry.Service}. + */ +package io.helidon.service.registry; diff --git a/service/registry/src/main/java/io/helidon/service/registry/spi/ServiceRegistryManagerProvider.java b/service/registry/src/main/java/io/helidon/service/registry/spi/ServiceRegistryManagerProvider.java new file mode 100644 index 00000000000..6ffed28d3ca --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/spi/ServiceRegistryManagerProvider.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 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.service.registry.spi; + +import java.util.function.Supplier; + +import io.helidon.service.registry.ServiceDiscovery; +import io.helidon.service.registry.ServiceRegistryConfig; +import io.helidon.service.registry.ServiceRegistryManager; + +/** + * A {@link java.util.ServiceLoader} provider that enables a different type of service registry. + * In Helidon this could be a service registry with full injection support. + */ +public interface ServiceRegistryManagerProvider { + /** + * Create a new registry manager. + * + * @param config configuration as provided to {@link io.helidon.service.registry.ServiceRegistryManager} + * @param serviceDiscovery service discovery to load service instances + * @param coreRegistryManager core service registry manager, if it would be used as a backing one for the one provided by this + * service (lazy loaded) + * @return a new service registry manager + */ + ServiceRegistryManager create(ServiceRegistryConfig config, + ServiceDiscovery serviceDiscovery, + Supplier coreRegistryManager); +} diff --git a/service/registry/src/main/java/io/helidon/service/registry/spi/package-info.java b/service/registry/src/main/java/io/helidon/service/registry/spi/package-info.java new file mode 100644 index 00000000000..e583f20e3b8 --- /dev/null +++ b/service/registry/src/main/java/io/helidon/service/registry/spi/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023, 2024 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. + */ + +/** + * Service registry SPI provides extension points for the service registry. + */ +package io.helidon.service.registry.spi; diff --git a/service/registry/src/main/java/module-info.java b/service/registry/src/main/java/module-info.java new file mode 100644 index 00000000000..b1b2230390f --- /dev/null +++ b/service/registry/src/main/java/module-info.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 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. + */ + +/** + * Core service registry, supporting {@link io.helidon.service.registry.Service.Provider}. + */ +module io.helidon.service.registry { + exports io.helidon.service.registry; + exports io.helidon.service.registry.spi; + + requires transitive io.helidon.common.config; + requires transitive io.helidon.builder.api; + requires transitive io.helidon.common.types; + + uses io.helidon.service.registry.spi.ServiceRegistryManagerProvider; +} \ No newline at end of file diff --git a/service/registry/src/main/resources/META-INF/native-image/io.helidon.service/helidon-service-registry/native-image.properties b/service/registry/src/main/resources/META-INF/native-image/io.helidon.service/helidon-service-registry/native-image.properties new file mode 100644 index 00000000000..af807d5c318 --- /dev/null +++ b/service/registry/src/main/resources/META-INF/native-image/io.helidon.service/helidon-service-registry/native-image.properties @@ -0,0 +1,19 @@ +# +# Copyright (c) 2024 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. +# +Args=--initialize-at-build-time=io.helidon.common.types \ + --initialize-at-build-time=io.helidon.service.registry.ServiceLoader__ServiceDescriptor \ + --initialize-at-build-time=io.helidon.service.registry.CoreServiceDiscovery \ + --initialize-at-build-time=io.helidon.common.Errors diff --git a/service/registry/src/main/resources/META-INF/native-image/io.helidon.service/helidon-service-registry/resource-config.json b/service/registry/src/main/resources/META-INF/native-image/io.helidon.service/helidon-service-registry/resource-config.json new file mode 100644 index 00000000000..aa0540496bb --- /dev/null +++ b/service/registry/src/main/resources/META-INF/native-image/io.helidon.service/helidon-service-registry/resource-config.json @@ -0,0 +1,10 @@ +{ + "resources": [ + { + "pattern": "META-INF/helidon/service.registry" + }, + { + "pattern": "META-INF/helidon/service.loader" + } + ] +} \ No newline at end of file diff --git a/service/tests/codegen/pom.xml b/service/tests/codegen/pom.xml new file mode 100644 index 00000000000..735ed76d54a --- /dev/null +++ b/service/tests/codegen/pom.xml @@ -0,0 +1,64 @@ + + + + + + io.helidon.service.tests + helidon-service-tests-project + 4.0.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + helidon-service-tests-codegen + Helidon Service Tests Codegen + Tests for service codegen + + + + io.helidon.service + helidon-service-codegen + + + io.helidon.service + helidon-service-registry + + + io.helidon.config + helidon-config-metadata + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + io.helidon.common.testing + helidon-common-testing-junit5 + test + + + diff --git a/service/tests/codegen/src/main/java/module-info.java b/service/tests/codegen/src/main/java/module-info.java new file mode 100644 index 00000000000..6dfc02fed59 --- /dev/null +++ b/service/tests/codegen/src/main/java/module-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 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. + */ + +module io.helidon.service.tests.codegen { + requires io.helidon.service.registry; + requires io.helidon.service.codegen; + requires io.helidon.config.metadata; +} \ No newline at end of file diff --git a/service/tests/codegen/src/test/java/io/helidon/service/tests/codegen/ServiceCodegenTypesTest.java b/service/tests/codegen/src/test/java/io/helidon/service/tests/codegen/ServiceCodegenTypesTest.java new file mode 100644 index 00000000000..29cf19a2688 --- /dev/null +++ b/service/tests/codegen/src/test/java/io/helidon/service/tests/codegen/ServiceCodegenTypesTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024 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.service.tests.codegen; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import io.helidon.common.types.TypeName; +import io.helidon.service.codegen.ServiceCodegenTypes; +import io.helidon.service.registry.Dependency; +import io.helidon.service.registry.DependencyContext; +import io.helidon.service.registry.GeneratedService; +import io.helidon.service.registry.Service; + +import org.hamcrest.CoreMatchers; +import org.hamcrest.collection.IsEmptyCollection; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +class ServiceCodegenTypesTest { + @Test + void testTypes() { + // it is really important to test ALL constants on the class, so let's use relfection + Field[] declaredFields = ServiceCodegenTypes.class.getDeclaredFields(); + + Set toCheck = new HashSet<>(); + Set checked = new HashSet<>(); + Map fields = new HashMap<>(); + + for (Field declaredField : declaredFields) { + String name = declaredField.getName(); + + assertThat(name + " must be a TypeName", declaredField.getType(), CoreMatchers.sameInstance(TypeName.class)); + assertThat(name + " must be static", Modifier.isStatic(declaredField.getModifiers()), is(true)); + assertThat(name + " must be public", Modifier.isPublic(declaredField.getModifiers()), is(true)); + assertThat(name + " must be final", Modifier.isFinal(declaredField.getModifiers()), is(true)); + + toCheck.add(name); + fields.put(name, declaredField); + } + + checkField(toCheck, checked, fields, "SERVICE_ANNOTATION_PROVIDER", Service.Provider.class); + checkField(toCheck, checked, fields, "SERVICE_ANNOTATION_CONTRACT", Service.Contract.class); + checkField(toCheck, checked, fields, "SERVICE_ANNOTATION_EXTERNAL_CONTRACTS", Service.ExternalContracts.class); + checkField(toCheck, checked, fields, "SERVICE_ANNOTATION_DESCRIPTOR", Service.Descriptor.class); + checkField(toCheck, checked, fields, "SERVICE_DESCRIPTOR", GeneratedService.Descriptor.class); + checkField(toCheck, checked, fields, "SERVICE_DEPENDENCY", Dependency.class); + checkField(toCheck, checked, fields, "SERVICE_DEPENDENCY_CONTEXT", DependencyContext.class); + + assertThat(toCheck, IsEmptyCollection.empty()); + } + + private void checkField(Set namesToCheck, + Set checkedNames, + Map namesToFields, + String name, + Class expectedType) { + Field field = namesToFields.get(name); + assertThat("Field " + name + " does not exist in the class", field, notNullValue()); + try { + namesToCheck.remove(name); + if (checkedNames.add(name)) { + TypeName value = (TypeName) field.get(null); + assertThat("Field " + name, value.fqName(), is(expectedType.getCanonicalName())); + } else { + fail("Field " + name + " is checked more than once"); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/service/tests/codegen/src/test/java/module-info.java b/service/tests/codegen/src/test/java/module-info.java new file mode 100644 index 00000000000..49940eb0f05 --- /dev/null +++ b/service/tests/codegen/src/test/java/module-info.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 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. + */ + +module io.helidon.service.tests.codegen.test { + exports io.helidon.service.tests.codegen; + + requires io.helidon.service.registry; + requires io.helidon.service.codegen; + requires io.helidon.config.metadata; + + requires hamcrest.all; + requires org.junit.jupiter.api; + + opens io.helidon.service.tests.codegen to org.junit.platform.commons; +} \ No newline at end of file diff --git a/service/tests/pom.xml b/service/tests/pom.xml new file mode 100644 index 00000000000..8c3134847f3 --- /dev/null +++ b/service/tests/pom.xml @@ -0,0 +1,51 @@ + + + + + io.helidon.service + helidon-service-project + 4.0.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + io.helidon.service.tests + helidon-service-tests-project + + Helidon Service Tests Project + pom + + + true + true + true + true + true + true + true + + + + registry + codegen + + + diff --git a/service/tests/registry/pom.xml b/service/tests/registry/pom.xml new file mode 100644 index 00000000000..3ab2a699224 --- /dev/null +++ b/service/tests/registry/pom.xml @@ -0,0 +1,110 @@ + + + + + io.helidon.service.tests + helidon-service-tests-project + 4.0.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + helidon-service-tests-registry + + Helidon Service Tests Registry + + + false + + + + + io.helidon.service + helidon-service-registry + + + + io.helidon.config + helidon-config + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + io.helidon.common.testing + helidon-common-testing-junit5 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.helidon.codegen + helidon-codegen-apt + ${helidon.version} + + + io.helidon.service + helidon-service-codegen + ${helidon.version} + + + io.helidon.codegen + helidon-codegen-helidon-copyright + ${helidon.version} + + + + + + io.helidon.codegen + helidon-codegen-apt + ${helidon.version} + + + io.helidon.service + helidon-service-codegen + ${helidon.version} + + + io.helidon.codegen + helidon-codegen-helidon-copyright + ${helidon.version} + + + + + + diff --git a/service/tests/registry/src/main/java/io/helidon/service/test/registry/MyContract.java b/service/tests/registry/src/main/java/io/helidon/service/test/registry/MyContract.java new file mode 100644 index 00000000000..42c5a4cb7ca --- /dev/null +++ b/service/tests/registry/src/main/java/io/helidon/service/test/registry/MyContract.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 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.service.test.registry; + +import io.helidon.service.registry.Service; + +@Service.Contract +public interface MyContract { + String message(); +} diff --git a/service/tests/registry/src/main/java/io/helidon/service/test/registry/MyService.java b/service/tests/registry/src/main/java/io/helidon/service/test/registry/MyService.java new file mode 100644 index 00000000000..4e8abd86080 --- /dev/null +++ b/service/tests/registry/src/main/java/io/helidon/service/test/registry/MyService.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 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.service.test.registry; + +import io.helidon.service.registry.Service; + +@Service.Provider +class MyService implements MyContract { + static int instances = 0; + + MyService() { + instances++; + } + + @Override + public String message() { + return "MyService"; + } +} diff --git a/service/tests/registry/src/main/java/io/helidon/service/test/registry/MyService2.java b/service/tests/registry/src/main/java/io/helidon/service/test/registry/MyService2.java new file mode 100644 index 00000000000..cc64d0419e1 --- /dev/null +++ b/service/tests/registry/src/main/java/io/helidon/service/test/registry/MyService2.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 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.service.test.registry; + +import io.helidon.common.Weight; +import io.helidon.service.registry.Service; + +@Service.Provider +@Weight(102) +class MyService2 implements MyContract { + static int instances; + + private final MyService service; + + MyService2(MyService service) { + instances++; + this.service = service; + } + + @Override + public String message() { + return service.message() + ":MyService2"; + } +} diff --git a/service/tests/registry/src/main/java/module-info.java b/service/tests/registry/src/main/java/module-info.java new file mode 100644 index 00000000000..88f0d5cf924 --- /dev/null +++ b/service/tests/registry/src/main/java/module-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 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. + */ + +module io.helidon.service.tests.core { + requires io.helidon.service.registry; + + exports io.helidon.service.test.registry; +} \ No newline at end of file diff --git a/service/tests/registry/src/test/java/io/helidon/service/test/registry/CyclicDependencyTest.java b/service/tests/registry/src/test/java/io/helidon/service/test/registry/CyclicDependencyTest.java new file mode 100644 index 00000000000..9d0e07e1c27 --- /dev/null +++ b/service/tests/registry/src/test/java/io/helidon/service/test/registry/CyclicDependencyTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024 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.service.test.registry; + +import java.util.List; +import java.util.Set; + +import io.helidon.common.GenericType; +import io.helidon.common.types.TypeName; +import io.helidon.service.registry.Dependency; +import io.helidon.service.registry.DependencyContext; +import io.helidon.service.registry.GeneratedService; +import io.helidon.service.registry.ServiceRegistry; +import io.helidon.service.registry.ServiceRegistryConfig; +import io.helidon.service.registry.ServiceRegistryException; +import io.helidon.service.registry.ServiceRegistryManager; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class CyclicDependencyTest { + private static final TypeName SERVICE_1 = TypeName.create(Service1.class); + private static final TypeName SERVICE_2 = TypeName.create(Service2.class); + + @Test + public void testCyclicDependency() { + ServiceRegistryManager manager = ServiceRegistryManager.create(ServiceRegistryConfig.builder() + .discoverServices(false) + .addServiceDescriptor(new Descriptor1()) + .addServiceDescriptor(new Descriptor2()) + .build()); + + try { + ServiceRegistry registry = manager.registry(); + ServiceRegistryException sre = assertThrows(ServiceRegistryException.class, () -> registry.get(Service1.class)); + assertThat(sre.getMessage(), startsWith("Cyclic dependency")); + sre = assertThrows(ServiceRegistryException.class, () -> registry.get(Service2.class)); + assertThat(sre.getMessage(), startsWith("Cyclic dependency")); + } finally { + manager.shutdown(); + } + } + + private static class Service1 { + Service1(Service2 second) { + } + } + + private static class Service2 { + Service2(Service1 first) { + } + } + + private static class Descriptor1 implements GeneratedService.Descriptor { + private static final TypeName TYPE = TypeName.create(Descriptor1.class); + + private static final Dependency DEP = Dependency.builder() + .contract(SERVICE_2) + .descriptor(TYPE) + .descriptorConstant("DEP") + .name("second") + .service(SERVICE_1) + .typeName(SERVICE_2) + .contractType(new GenericType() { }) + .build(); + + @Override + public Object instantiate(DependencyContext ctx) { + return new Service1(ctx.dependency(DEP)); + } + + @Override + public TypeName serviceType() { + return SERVICE_1; + } + + @Override + public TypeName descriptorType() { + return TYPE; + } + + @Override + public List dependencies() { + return List.of(DEP); + } + + @Override + public Set contracts() { + return Set.of(SERVICE_1); + } + } + + private static class Descriptor2 implements GeneratedService.Descriptor { + private static final TypeName TYPE = TypeName.create(Descriptor2.class); + + private static final Dependency DEP = Dependency.builder() + .contract(SERVICE_1) + .descriptor(TYPE) + .descriptorConstant("DEP") + .name("first") + .service(SERVICE_2) + .typeName(SERVICE_1) + .contractType(new GenericType() { }) + .build(); + + @Override + public Object instantiate(DependencyContext ctx) { + return new Service2(ctx.dependency(DEP)); + } + + @Override + public TypeName serviceType() { + return SERVICE_1; + } + + @Override + public TypeName descriptorType() { + return TYPE; + } + + @Override + public List dependencies() { + return List.of(DEP); + } + + @Override + public Set contracts() { + return Set.of(SERVICE_2); + } + } +} diff --git a/service/tests/registry/src/test/java/io/helidon/service/test/registry/RegistryConfigTest.java b/service/tests/registry/src/test/java/io/helidon/service/test/registry/RegistryConfigTest.java new file mode 100644 index 00000000000..c9a4eb9949c --- /dev/null +++ b/service/tests/registry/src/test/java/io/helidon/service/test/registry/RegistryConfigTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 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.service.test.registry; + +import java.util.Map; + +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import io.helidon.service.registry.ServiceRegistryConfig; + +import org.junit.jupiter.api.Test; + +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsEmptyCollection.empty; + +public class RegistryConfigTest { + @Test + void testDefaults() { + ServiceRegistryConfig cfg = ServiceRegistryConfig.create(); + assertThat(cfg.config(), is(optionalEmpty())); + assertThat(cfg.discoverServices(), is(true)); + assertThat(cfg.serviceDescriptors(), is(empty())); + assertThat(cfg.serviceInstances().size(), is(0)); + } + + @Test + void testFromConfig() { + Config config = Config.builder( + ConfigSources.create( + Map.of("registry.discover-services", "false" + ), "config-1")) + .disableEnvironmentVariablesSource() + .disableSystemPropertiesSource() + .build(); + Config registryConfig = config.get("registry"); + ServiceRegistryConfig cfg = ServiceRegistryConfig.create(registryConfig); + + assertThat(cfg.config(), optionalValue(sameInstance(registryConfig))); + assertThat(cfg.discoverServices(), is(false)); + assertThat(cfg.serviceDescriptors(), is(empty())); + assertThat(cfg.serviceInstances().size(), is(0)); + } +} diff --git a/service/tests/registry/src/test/java/io/helidon/service/test/registry/RegistryTest.java b/service/tests/registry/src/test/java/io/helidon/service/test/registry/RegistryTest.java new file mode 100644 index 00000000000..70e6aed2be3 --- /dev/null +++ b/service/tests/registry/src/test/java/io/helidon/service/test/registry/RegistryTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 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.service.test.registry; + +import java.util.List; + +import io.helidon.service.registry.ServiceRegistry; +import io.helidon.service.registry.ServiceRegistryManager; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; + +public class RegistryTest { + private static ServiceRegistryManager registryManager; + private static ServiceRegistry registry; + + @BeforeAll + public static void init() { + registryManager = ServiceRegistryManager.create(); + registry = registryManager.registry(); + } + + @AfterAll + public static void shutdown() { + if (registryManager != null) { + registryManager.shutdown(); + } + registryManager = null; + registry = null; + } + + @Test + public void testRegistryGet() { + MyContract myContract = registry.get(MyContract.class); + // higher weight + assertThat(myContract, instanceOf(MyService2.class)); + + assertThat(MyService2.instances, is(1)); + assertThat(MyService.instances, is(1)); + } + + @Test + public void testRegistryFirst() { + MyContract myContract = registry.first(MyContract.class).get(); + // higher weight + assertThat(myContract, instanceOf(MyService2.class)); + assertThat(MyService2.instances, is(1)); + assertThat(MyService.instances, is(1)); + } + + @Test + public void testRegistryAll() { + List myContracts = registry.all(MyContract.class); + assertThat(myContracts, hasSize(2)); + // higher weight + assertThat(myContracts.get(0), instanceOf(MyService2.class)); + assertThat(myContracts.get(1), instanceOf(MyService.class)); + + assertThat(MyService2.instances, is(1)); + assertThat(MyService.instances, is(1)); + } +} From 7bc29218d7c80ebba9b9322356ff12b18a535fd5 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Tue, 30 Apr 2024 13:49:34 +0200 Subject: [PATCH 04/20] Replace usage of Helidon Inject with Service Registry Signed-off-by: Tomas Langer --- common/tls/pom.xml | 11 -- config/config/pom.xml | 25 ---- config/tests/service-registry/pom.xml | 26 ---- examples/inject/README.md | 16 --- examples/inject/basics/README.md | 30 ----- examples/inject/basics/pom.xml | 89 -------------- .../helidon/examples/inject/basics/Big.java | 31 ----- .../examples/inject/basics/BigHammer.java | 30 ----- .../examples/inject/basics/Hammer.java | 44 ------- .../examples/inject/basics/Little.java | 30 ----- .../examples/inject/basics/LittleHammer.java | 30 ----- .../helidon/examples/inject/basics/Main.java | 61 ---------- .../helidon/examples/inject/basics/Tool.java | 34 ------ .../examples/inject/basics/ToolBox.java | 97 --------------- .../examples/inject/basics/package-info.java | 20 ---- .../src/main/resources/logging.properties | 26 ---- examples/inject/configdriven/README.md | 28 ----- examples/inject/configdriven/pom.xml | 113 ------------------ .../examples/inject/configdriven/Drill.java | 48 -------- .../configdriven/DrillConfigBlueprint.java | 31 ----- .../examples/inject/configdriven/Main.java | 55 --------- .../inject/configdriven/package-info.java | 20 ---- .../src/main/resources/application.yaml | 25 ---- .../src/main/resources/logging.properties | 26 ---- examples/inject/interceptors/README.md | 21 ---- examples/inject/interceptors/pom.xml | 94 --------------- .../examples/inject/interceptors/Main.java | 47 -------- .../inject/interceptors/ScrewDriver.java | 40 ------- .../examples/inject/interceptors/Turn.java | 23 ---- .../inject/interceptors/TurnInterceptor.java | 46 ------- .../inject/interceptors/TurningTool.java | 27 ----- .../inject/interceptors/package-info.java | 20 ---- .../src/main/resources/logging.properties | 26 ---- examples/inject/pom.xml | 40 ------- examples/inject/providers/README.md | 38 ------ examples/inject/providers/pom.xml | 96 --------------- .../inject/providers/AngleGrinderSaw.java | 48 -------- .../examples/inject/providers/Blade.java | 32 ----- .../inject/providers/BladeProvider.java | 78 ------------ .../inject/providers/CircularSaw.java | 49 -------- .../examples/inject/providers/HandSaw.java | 47 -------- .../examples/inject/providers/Main.java | 52 -------- .../examples/inject/providers/Nail.java | 32 ----- .../examples/inject/providers/NailGun.java | 55 --------- .../inject/providers/NailProvider.java | 41 ------- .../examples/inject/providers/Saw.java | 31 ----- .../examples/inject/providers/SizedBlade.java | 47 -------- .../inject/providers/StandardNail.java | 34 ------ .../examples/inject/providers/TableSaw.java | 50 -------- .../inject/providers/package-info.java | 20 ---- .../src/main/resources/logging.properties | 26 ---- .../inject/providers/AllenWrench.java | 30 ----- .../inject/providers/ProvidersTest.java | 33 ----- .../examples/inject/providers/Wrench.java | 23 ---- examples/pom.xml | 1 - fault-tolerance/fault-tolerance/pom.xml | 44 ------- tests/integration/native-image/mp-1/pom.xml | 5 - tests/integration/native-image/mp-3/pom.xml | 5 - webclient/api/pom.xml | 21 ---- webclient/http1/pom.xml | 20 ---- webserver/webserver/pom.xml | 5 - 61 files changed, 2293 deletions(-) delete mode 100644 examples/inject/README.md delete mode 100644 examples/inject/basics/README.md delete mode 100644 examples/inject/basics/pom.xml delete mode 100644 examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Big.java delete mode 100644 examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/BigHammer.java delete mode 100644 examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Hammer.java delete mode 100644 examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Little.java delete mode 100644 examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/LittleHammer.java delete mode 100644 examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Main.java delete mode 100644 examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Tool.java delete mode 100644 examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/ToolBox.java delete mode 100644 examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/package-info.java delete mode 100644 examples/inject/basics/src/main/resources/logging.properties delete mode 100644 examples/inject/configdriven/README.md delete mode 100644 examples/inject/configdriven/pom.xml delete mode 100644 examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/Drill.java delete mode 100644 examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/DrillConfigBlueprint.java delete mode 100644 examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/Main.java delete mode 100644 examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/package-info.java delete mode 100644 examples/inject/configdriven/src/main/resources/application.yaml delete mode 100644 examples/inject/configdriven/src/main/resources/logging.properties delete mode 100644 examples/inject/interceptors/README.md delete mode 100644 examples/inject/interceptors/pom.xml delete mode 100644 examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/Main.java delete mode 100644 examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/ScrewDriver.java delete mode 100644 examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/Turn.java delete mode 100644 examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/TurnInterceptor.java delete mode 100644 examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/TurningTool.java delete mode 100644 examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/package-info.java delete mode 100644 examples/inject/interceptors/src/main/resources/logging.properties delete mode 100644 examples/inject/pom.xml delete mode 100644 examples/inject/providers/README.md delete mode 100644 examples/inject/providers/pom.xml delete mode 100644 examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/AngleGrinderSaw.java delete mode 100644 examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Blade.java delete mode 100644 examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/BladeProvider.java delete mode 100644 examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/CircularSaw.java delete mode 100644 examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/HandSaw.java delete mode 100644 examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Main.java delete mode 100644 examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Nail.java delete mode 100644 examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/NailGun.java delete mode 100644 examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/NailProvider.java delete mode 100644 examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Saw.java delete mode 100644 examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/SizedBlade.java delete mode 100644 examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/StandardNail.java delete mode 100644 examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/TableSaw.java delete mode 100644 examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/package-info.java delete mode 100644 examples/inject/providers/src/main/resources/logging.properties delete mode 100644 examples/inject/providers/src/test/java/io/helidon/examples/inject/providers/AllenWrench.java delete mode 100644 examples/inject/providers/src/test/java/io/helidon/examples/inject/providers/ProvidersTest.java delete mode 100644 examples/inject/providers/src/test/java/io/helidon/examples/inject/providers/Wrench.java diff --git a/common/tls/pom.xml b/common/tls/pom.xml index 71b72195c11..cb42893885e 100644 --- a/common/tls/pom.xml +++ b/common/tls/pom.xml @@ -32,20 +32,9 @@ etc/spotbugs/exclude.xml - - false - - helidon-inject-api - io.helidon.inject - true - io.helidon.common helidon-common diff --git a/config/config/pom.xml b/config/config/pom.xml index 44090a3f54f..c5301ae0ebf 100644 --- a/config/config/pom.xml +++ b/config/config/pom.xml @@ -50,15 +50,6 @@ io.helidon.common helidon-common-media-type - - io.helidon.inject - helidon-inject-api - - - io.helidon.inject - helidon-inject-runtime - true - io.helidon.common.features helidon-common-features-api @@ -107,9 +98,6 @@ org.apache.maven.plugins maven-compiler-plugin - - -Ainject.acceptPreview=true - io.helidon.common.features @@ -121,11 +109,6 @@ helidon-builder-processor ${helidon.version} - - io.helidon.inject - helidon-inject-processor - ${helidon.version} - io.helidon.common.processor helidon-common-processor-helidon-copyright @@ -135,19 +118,11 @@ - io.helidon.common.features helidon-common-features-processor ${helidon.version} - - io.helidon.inject - helidon-inject-processor - ${helidon.version} - - - io.helidon.builder helidon-builder-processor ${helidon.version} diff --git a/config/tests/service-registry/pom.xml b/config/tests/service-registry/pom.xml index bcda355898b..c6cd2738391 100644 --- a/config/tests/service-registry/pom.xml +++ b/config/tests/service-registry/pom.xml @@ -34,24 +34,11 @@ io.helidon.config helidon-config - - jakarta.inject - jakarta.inject-api - - - io.helidon.inject - helidon-inject-runtime - io.helidon.config helidon-config-yaml test - - io.helidon.inject - helidon-inject-testing - test - org.junit.jupiter junit-jupiter-api @@ -70,15 +57,7 @@ org.apache.maven.plugins maven-compiler-plugin - - -Ainject.acceptPreview=true - - - io.helidon.inject - helidon-inject-processor - ${helidon.version} - io.helidon.common.processor helidon-common-processor-helidon-copyright @@ -87,11 +66,6 @@ - - io.helidon.inject - helidon-inject-processor - ${helidon.version} - io.helidon.common.processor helidon-common-processor-helidon-copyright diff --git a/examples/inject/README.md b/examples/inject/README.md deleted file mode 100644 index a5a8fe915ac..00000000000 --- a/examples/inject/README.md +++ /dev/null @@ -1,16 +0,0 @@ - -# Helidon Examples Injection - -Each subdirectory contains example code that highlights specific aspects of -Helidon Injection. - -Suggested path to follow: -1. [basics](./basics) -2. [providers](./providers) -3. [configdriven](./configdriven) -4. [interceptors](./interceptors) - -It's possible to compile all examples in sub-folders using -```shell -mvn package -``` diff --git a/examples/inject/basics/README.md b/examples/inject/basics/README.md deleted file mode 100644 index 7b9e68b375f..00000000000 --- a/examples/inject/basics/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Helidon Injection Basic Example - -This example shows the basics of using Helidon Injection. The -[Main.java](src/main/java/io/helidon/examples/inject/basics/Main.java) class shows: - -* programmatic lookup of services in the _Services_ registry in [Main](src/main/java/io/helidon/examples/inject/basics/Main.java). -* declarative injection in [ToolBox.java](src/main/java/io/helidon/examples/inject/basics/ToolBox.java). -* lifecycle via PostConstruct and RunLevel in [Main](src/main/java/io/helidon/examples/inject/basics/Main.java). -* annotation processing and source code generation (see [pom.xml](pom.xml) and [generated-sources](target/generated-sources/annotations/io/helidon/examples/inject/basics)). - -## Build and run - -```shell -mvn package -java -jar target/helidon-examples-inject-basics.jar -``` - -Expected Output: -``` -Startup service providers (ranked according to weight, pre-activated): [ToolBox:INIT] -Highest weighted service provider: ToolBox:INIT -Preferred (highest weighted) 'Big' Tool: Big Hammer -Optional 'Little' Hammer: Optional[Little Hammer] -Tools in the virtual ToolBox: - tool: Hammer:INIT - tool: BigHammer:ACTIVE - tool: LittleHammer:ACTIVE -Highest weighted service provider (after activation): ToolBox -All service providers (after all activations): [ToolBox:ACTIVE] -``` diff --git a/examples/inject/basics/pom.xml b/examples/inject/basics/pom.xml deleted file mode 100644 index a9edb39e601..00000000000 --- a/examples/inject/basics/pom.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - 4.0.0 - - io.helidon.applications - helidon-se - 4.0.0-SNAPSHOT - ../../../applications/se/pom.xml - - io.helidon.examples.inject - helidon-examples-inject-basics - Helidon Examples Injection Basics - - - Examples of programmatic and declarative usages of Injection. - - - - io.helidon.examples.inject.basics.Main - - - - - io.helidon.inject - helidon-inject-api - - - io.helidon.inject - helidon-inject-runtime - - - jakarta.annotation - jakarta.annotation-api - provided - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - -Ainject.autoAddNonContractInterfaces=false - -Ainject.debug=false - -Ainject.acceptPreview=true - - true - - - io.helidon.inject - helidon-inject-processor - ${helidon.version} - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-libs - - - - - - diff --git a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Big.java b/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Big.java deleted file mode 100644 index 0ab0287d556..00000000000 --- a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Big.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.basics; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import jakarta.inject.Qualifier; - -/** - * Custom annotation. - */ -@Qualifier -@Retention(RetentionPolicy.CLASS) -public @interface Big { - -} diff --git a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/BigHammer.java b/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/BigHammer.java deleted file mode 100644 index eab6cfaf221..00000000000 --- a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/BigHammer.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.basics; - -import jakarta.inject.Singleton; - -@Big -@Singleton -class BigHammer extends Hammer { - - @Override - public String name() { - return "Big " + super.name(); - } - -} diff --git a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Hammer.java b/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Hammer.java deleted file mode 100644 index af4ccb73dfb..00000000000 --- a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Hammer.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.basics; - -import io.helidon.common.Weight; -import io.helidon.common.Weighted; - -import jakarta.inject.Singleton; - -/** - * By adding the {@link Singleton} annotation results in Hammer becoming a service. Services can be looked up - * programmatically or declaratively injected via {@link jakarta.inject.Inject}. - *

- * Here {@link Weight} is used that is higher than the default, making it more preferred in weighted rankings. - */ -@Singleton -@Weight(Weighted.DEFAULT_WEIGHT + 1) -class Hammer implements Tool { - - @Override - public String name() { - return "Hammer"; - } - - @Override - public String toString() { - return name(); - } - -} diff --git a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Little.java b/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Little.java deleted file mode 100644 index 1eb289d4644..00000000000 --- a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Little.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.basics; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import jakarta.inject.Qualifier; - -/** - * Custom annotation. - */ -@Qualifier -@Retention(RetentionPolicy.CLASS) -public @interface Little { -} diff --git a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/LittleHammer.java b/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/LittleHammer.java deleted file mode 100644 index 824712912b6..00000000000 --- a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/LittleHammer.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.basics; - -import jakarta.inject.Singleton; - -@Little -@Singleton -class LittleHammer extends Hammer { - - @Override - public String name() { - return "Little " + super.name(); - } - -} diff --git a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Main.java b/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Main.java deleted file mode 100644 index 50007ddc5b6..00000000000 --- a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Main.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.basics; - -import java.util.List; - -import io.helidon.inject.api.InjectionServices; -import io.helidon.inject.api.RunLevel; -import io.helidon.inject.api.ServiceInfoCriteria; -import io.helidon.inject.api.ServiceProvider; -import io.helidon.inject.api.Services; - -/** - * Basics example. - */ -public class Main { - - /** - * Executes the example. - * - * @param args arguments - */ - public static void main(String... args) { - Services services = InjectionServices.realizedServices(); - - // 0. Demonstrates programmatic lookup from the Services registry. - // 1. when a service is being managed by a DI provider (like Helidon Injection) it should be "looked up" or injected instead of new'ed - // 2. Notice we get a ServiceProvider - service providers allow for lazy initialization - ServiceInfoCriteria criteria = ServiceInfoCriteria.builder() - .runLevel(RunLevel.STARTUP) - .build(); - - List> startupServiceProviders = services.lookupAll(criteria); - System.out.println("Startup service providers (ranked according to weight, pre-activated): " + startupServiceProviders); - - ServiceProvider highestWeightedServiceProvider = services.lookupFirst(criteria); - System.out.println("Highest weighted service provider: " + highestWeightedServiceProvider); - - // trigger lazy activations for the highest weighted service provider - System.out.println("Highest weighted service provider (after activation): " + highestWeightedServiceProvider.get()); - - // trigger all activations for the (remaining unactivated) startup service providers - startupServiceProviders.forEach(ServiceProvider::get); - System.out.println("All service providers (after all activations): " + startupServiceProviders); - } - -} diff --git a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Tool.java b/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Tool.java deleted file mode 100644 index 5c72bee20d4..00000000000 --- a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/Tool.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.basics; - -import io.helidon.inject.api.Contract; - -/** - * An example Tool interface contract. - */ -@Contract -public interface Tool { - - /** - * The name of the tool. - * - * @return name of the tool - */ - String name(); - -} diff --git a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/ToolBox.java b/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/ToolBox.java deleted file mode 100644 index 781b5f6efa6..00000000000 --- a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/ToolBox.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.basics; - -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -import io.helidon.common.Weight; -import io.helidon.common.Weighted; -import io.helidon.inject.api.RunLevel; - -import jakarta.annotation.PostConstruct; -import jakarta.inject.Inject; -import jakarta.inject.Provider; -import jakarta.inject.Singleton; - -/** - * By adding the {@link Singleton} annotation results in ToolBox becoming a service. Services can be looked up - * programmatically or declaratively injected via {@link jakarta.inject.Inject}. - *

- * Here {@link Weight} is used that is higher than the default, making it more preferred in weighted rankings. - */ -@Singleton -@RunLevel(RunLevel.STARTUP) -@Weight(Weighted.DEFAULT_WEIGHT + 1) -public class ToolBox { - - private final List> allToolProviders; - private Tool preferredBigTool; - - // Field injection is supported for non-static, non-private methods (but not recommended) - // Here we are using it to also showcase for Optional usages. - @Inject Optional optionalLittleHammer; - - /** - * Here the constructor injects all {@link Tool} provider instances available. {@link Provider} is used to allow lazy - * activation of services until {@link Provider#get()} is called. - * - * @param allToolProviders all tool providers - */ - @Inject - ToolBox(List> allToolProviders) { - this.allToolProviders = Objects.requireNonNull(allToolProviders); - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } - - /** - * Example of setter based injection. - * - * @param preferredBigTool the preferred big tool - */ - @Inject - @SuppressWarnings("unused") - void setPreferredBigTool(@Big Tool preferredBigTool) { - this.preferredBigTool = Objects.requireNonNull(preferredBigTool); - } - - /** - * This method will be called by Pico after this instance is lazily initialized (because this is the {@link PostConstruct} - * method). - */ - @PostConstruct - @SuppressWarnings("unused") - void init() { - System.out.println("Preferred (highest weighted) 'Big' Tool: " + preferredBigTool); - System.out.println("Optional 'Little' Hammer: " + optionalLittleHammer); - - printToolBoxContents(); - } - - public void printToolBoxContents() { - System.out.println("Tools in the virtual ToolBox:"); - for (Provider tool : allToolProviders) { - System.out.println(" tool: " + tool); - } - } - -} diff --git a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/package-info.java b/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/package-info.java deleted file mode 100644 index 5ad11f7d68f..00000000000 --- a/examples/inject/basics/src/main/java/io/helidon/examples/inject/basics/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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. - */ - -/** - * Examples of programmatic and declarative usages of Injection. - */ -package io.helidon.examples.inject.basics; diff --git a/examples/inject/basics/src/main/resources/logging.properties b/examples/inject/basics/src/main/resources/logging.properties deleted file mode 100644 index bd06e0ed087..00000000000 --- a/examples/inject/basics/src/main/resources/logging.properties +++ /dev/null @@ -1,26 +0,0 @@ -# -# 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. -# - - -handlers = java.util.logging.ConsoleHandler - -java.util.logging.ConsoleHandler.level = FINEST -java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter -java.util.logging.SimpleFormatter.format = [%1$tc] %4$s: %2$s - %5$s %6$s%n - -.level = INFO -io.helidon.config.level = WARNING -io.helidon.config.examples.level = FINEST diff --git a/examples/inject/configdriven/README.md b/examples/inject/configdriven/README.md deleted file mode 100644 index 00b6e219118..00000000000 --- a/examples/inject/configdriven/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Helidon Injection Config-Driven Example - -This example shows the basics of using Helidon Injection's Config-Driven Services. The -[Main.java](src/main/java/io/helidon/examples/inject/configdriven/Main.java) class shows: - -* setting up the bootstrap [configuration](./src/main/resources/application.yaml). -* [ConfigBean](src/main/java/io/helidon/examples/inject/configdriven/DrillConfig.java). -* [ConfiguredBy](src/main/java/io/helidon/examples/inject/configdriven/Drill.java) Services. -* annotation processing and source code generation (see [pom.xml](pom.xml) and [generated-sources](target/generated-sources/annotations/io/helidon/examples/inject/configdriven)). - -## Build and run - -```shell -mvn package -java -jar target/helidon-examples-inject-configdriven.jar -``` - -Expected Output: -``` -Preferred (highest weighted) 'Big' Tool: Big Hammer -Optional 'Little' Hammer: Optional[Little Hammer] -Tools in the virtual ToolBox: - tool: Hammer:INIT - tool: BigHammer:ACTIVE - tool: LittleHammer:ACTIVE - tool: Drill{Hand}:PENDING - tool: Drill{Impact}:PENDING -``` diff --git a/examples/inject/configdriven/pom.xml b/examples/inject/configdriven/pom.xml deleted file mode 100644 index e9fb4eeedd4..00000000000 --- a/examples/inject/configdriven/pom.xml +++ /dev/null @@ -1,113 +0,0 @@ - - - - - 4.0.0 - - io.helidon.applications - helidon-se - 4.0.0-SNAPSHOT - ../../../applications/se/pom.xml - - io.helidon.examples.inject - helidon-examples-inject-configdriven - Helidon Examples Injection Config-Driven - - - Examples of Config-driven services in Injection. - - - - io.helidon.examples.inject.configdriven.Main - - - - - io.helidon.examples.inject - helidon-examples-inject-basics - ${helidon.version} - - - io.helidon.inject.configdriven - helidon-inject-configdriven-runtime - - - io.helidon.config - helidon-config-yaml - - - io.helidon.config - helidon-config-metadata - true - - - jakarta.annotation - jakarta.annotation-api - provided - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - true - - - - io.helidon.inject.configdriven - helidon-inject-configdriven-processor - ${helidon.version} - - - - io.helidon.builder - helidon-builder-processor - ${helidon.version} - - - - - - io.helidon.builder - helidon-builder-processor - ${helidon.version} - - - io.helidon.inject.configdriven - helidon-inject-configdriven-processor - ${helidon.version} - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-libs - - - - - - diff --git a/examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/Drill.java b/examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/Drill.java deleted file mode 100644 index 9c843790410..00000000000 --- a/examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/Drill.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.configdriven; - -import java.util.Objects; - -import io.helidon.examples.inject.basics.Tool; -import io.helidon.inject.configdriven.api.ConfigDriven; - -import jakarta.annotation.PostConstruct; -import jakarta.inject.Inject; - -@ConfigDriven(DrillConfigBlueprint.class) -class Drill implements Tool { - - private final DrillConfig cfg; - - @Inject - Drill(DrillConfig cfg) { - this.cfg = Objects.requireNonNull(cfg); - } - - @Override - public String name() { - return cfg.name(); - } - - @PostConstruct - @SuppressWarnings("unused") - void init() { - System.out.println(name() + "; initialized"); - } - -} diff --git a/examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/DrillConfigBlueprint.java b/examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/DrillConfigBlueprint.java deleted file mode 100644 index bf782b095da..00000000000 --- a/examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/DrillConfigBlueprint.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.configdriven; - -import io.helidon.builder.api.Prototype; -import io.helidon.config.metadata.Configured; -import io.helidon.config.metadata.ConfiguredOption; -import io.helidon.inject.configdriven.api.ConfigBean; - -@ConfigBean(repeatable = true) -@Prototype.Blueprint -@Configured(root = true, prefix = "drill") -interface DrillConfigBlueprint { - @ConfiguredOption - String name(); - -} diff --git a/examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/Main.java b/examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/Main.java deleted file mode 100644 index c6aabe88c2d..00000000000 --- a/examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/Main.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.configdriven; - -import io.helidon.config.Config; -import io.helidon.config.ConfigSources; -import io.helidon.examples.inject.basics.ToolBox; -import io.helidon.inject.api.Bootstrap; -import io.helidon.inject.api.InjectionServices; -import io.helidon.inject.api.Services; - -/** - * Config-driven example. - */ -public class Main { - - /** - * Executes the example. - * - * @param args arguments - */ - public static void main(String... args) { - // we need to first initialize Injection - informing the framework where to find the application's Config - Config config = Config.builder() - .addSource(ConfigSources.classpath("application.yaml")) - .disableSystemPropertiesSource() - .disableEnvironmentVariablesSource() - .build(); - Bootstrap bootstrap = Bootstrap.builder() - .config(config) - .build(); - InjectionServices.globalBootstrap(bootstrap); - - // this drives config-driven service activations (see the contents of the toolbox being output) - Services services = InjectionServices.realizedServices(); - - // this will trigger the PostConstruct method to display the contents of the toolbox - services.lookupFirst(ToolBox.class).get(); - } - -} diff --git a/examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/package-info.java b/examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/package-info.java deleted file mode 100644 index 54f3306b2c0..00000000000 --- a/examples/inject/configdriven/src/main/java/io/helidon/examples/inject/configdriven/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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. - */ - -/** - * Examples of Config-Driven Services. - */ -package io.helidon.examples.inject.configdriven; diff --git a/examples/inject/configdriven/src/main/resources/application.yaml b/examples/inject/configdriven/src/main/resources/application.yaml deleted file mode 100644 index 45b8e6e8af8..00000000000 --- a/examples/inject/configdriven/src/main/resources/application.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# -# 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. -# - -# these are only needed for unit testing - if there is a repeated setup and tear down, etc. -inject: - permits-dynamic: true - -drill: - hand: - name: "Hand" - impact: - name: "Impact" diff --git a/examples/inject/configdriven/src/main/resources/logging.properties b/examples/inject/configdriven/src/main/resources/logging.properties deleted file mode 100644 index bd06e0ed087..00000000000 --- a/examples/inject/configdriven/src/main/resources/logging.properties +++ /dev/null @@ -1,26 +0,0 @@ -# -# 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. -# - - -handlers = java.util.logging.ConsoleHandler - -java.util.logging.ConsoleHandler.level = FINEST -java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter -java.util.logging.SimpleFormatter.format = [%1$tc] %4$s: %2$s - %5$s %6$s%n - -.level = INFO -io.helidon.config.level = WARNING -io.helidon.config.examples.level = FINEST diff --git a/examples/inject/interceptors/README.md b/examples/inject/interceptors/README.md deleted file mode 100644 index 9e01c210d17..00000000000 --- a/examples/inject/interceptors/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Helidon Injection Providers Example - -This example shows how interceptors can be leveraged to develop using Helidon Injection. The -[Main.java](./src/main/java/io/helidon/examples/inject/providers/Main.java) class shows: - -* Interception basics of Injection. - -## Build and run - -```shell -mvn package -java -jar target/helidon-examples-inject-interceptors.jar -``` - -Expected Output: -``` -Screw Driver (1st turn): -Screw Driver turning right -Screw Driver (2nd turn): -Screw Driver turning right -``` diff --git a/examples/inject/interceptors/pom.xml b/examples/inject/interceptors/pom.xml deleted file mode 100644 index 5ab470a9d6e..00000000000 --- a/examples/inject/interceptors/pom.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - - - 4.0.0 - - io.helidon.applications - helidon-se - 4.0.0-SNAPSHOT - ../../../applications/se/pom.xml - - io.helidon.examples.inject - helidon-examples-inject-interceptors - Helidon Examples Injection Interceptors - - - Example usages of Injection in Interceptors. - - - - io.helidon.examples.inject.interceptors.Main - - - - - io.helidon.examples.inject - helidon-examples-inject-basics - ${helidon.version} - - - jakarta.annotation - jakarta.annotation-api - provided - - - org.hamcrest - hamcrest-all - test - - - org.junit.jupiter - junit-jupiter-api - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - true - - -Ainject.acceptPreview=true - - - - io.helidon.inject - helidon-inject-processor - ${helidon.version} - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-libs - - - - - - diff --git a/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/Main.java b/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/Main.java deleted file mode 100644 index 5e0ae74355c..00000000000 --- a/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/Main.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.interceptors; - -import io.helidon.inject.api.InjectionServices; -import io.helidon.inject.api.ServiceProvider; -import io.helidon.inject.api.Services; - -/** - * Interceptors example. - */ -public class Main { - - /** - * Executes the example. - * - * @param args arguments - */ - public static void main(String... args) { - Services services = InjectionServices.realizedServices(); - - // use the intercepted screwdriver - note that hashCode(), equals(), and toString() are not intercepted - ServiceProvider screwDriver = services.lookupFirst(ScrewDriver.class); - System.out.println(screwDriver.get() + " (1st turn): "); - screwDriver.get().turn("left"); - - // use the intercepted screwdriver turning tool - note that hashCode(), equals(), and toString() are not intercepted - ServiceProvider turningTool = services.lookupFirst(TurningTool.class); - System.out.println(turningTool.get() + " (2nd turn): "); - turningTool.get().turn("left"); - } - -} diff --git a/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/ScrewDriver.java b/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/ScrewDriver.java deleted file mode 100644 index 4b0e9420900..00000000000 --- a/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/ScrewDriver.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.interceptors; - -import jakarta.inject.Singleton; - -@Singleton -class ScrewDriver implements TurningTool { - - @Override - public String name() { - return "Screw Driver"; - } - - @Turn - @Override - public void turn(String direction) { - System.out.println(name() + " turning " + direction); - } - - @Override - public String toString() { - return name(); - } - -} diff --git a/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/Turn.java b/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/Turn.java deleted file mode 100644 index d49bf1fbaee..00000000000 --- a/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/Turn.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.interceptors; - -import io.helidon.inject.api.InterceptedTrigger; - -@InterceptedTrigger -public @interface Turn { -} diff --git a/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/TurnInterceptor.java b/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/TurnInterceptor.java deleted file mode 100644 index 74ee823a692..00000000000 --- a/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/TurnInterceptor.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.interceptors; - -import io.helidon.inject.api.ClassNamed; -import io.helidon.inject.api.Interceptor; -import io.helidon.inject.api.InvocationContext; - -import jakarta.inject.Singleton; - -@ClassNamed(Turn.class) -@Singleton -@SuppressWarnings("unused") -class TurnInterceptor implements Interceptor { - - @Override - @SuppressWarnings("unchecked") - public V proceed(InvocationContext ctx, - Chain chain, - Object... args) { - // in "real life" you'd use the ctx to determine the best decision - this is just for simple demonstration only! - if (args.length == 1) { - // this is the call to turn() - args[0] = "right"; - } else if (args.length == 0 && ctx.elementInfo().elementName().equals("name")) { - return (V) ("intercepted: " + chain.proceed(args)); - } - - return chain.proceed(args); - } - -} diff --git a/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/TurningTool.java b/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/TurningTool.java deleted file mode 100644 index 128aa8ba0fc..00000000000 --- a/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/TurningTool.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.interceptors; - -import io.helidon.examples.inject.basics.Tool; -import io.helidon.inject.api.Contract; - -@Contract -public interface TurningTool extends Tool { - - void turn(String direction); - -} diff --git a/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/package-info.java b/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/package-info.java deleted file mode 100644 index 5acd84b9f97..00000000000 --- a/examples/inject/interceptors/src/main/java/io/helidon/examples/inject/interceptors/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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. - */ - -/** - * Examples of Intercepted services in Injection. - */ -package io.helidon.examples.inject.interceptors; diff --git a/examples/inject/interceptors/src/main/resources/logging.properties b/examples/inject/interceptors/src/main/resources/logging.properties deleted file mode 100644 index bd06e0ed087..00000000000 --- a/examples/inject/interceptors/src/main/resources/logging.properties +++ /dev/null @@ -1,26 +0,0 @@ -# -# 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. -# - - -handlers = java.util.logging.ConsoleHandler - -java.util.logging.ConsoleHandler.level = FINEST -java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter -java.util.logging.SimpleFormatter.format = [%1$tc] %4$s: %2$s - %5$s %6$s%n - -.level = INFO -io.helidon.config.level = WARNING -io.helidon.config.examples.level = FINEST diff --git a/examples/inject/pom.xml b/examples/inject/pom.xml deleted file mode 100644 index 895fb067670..00000000000 --- a/examples/inject/pom.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - 4.0.0 - - io.helidon.examples - helidon-examples-project - 4.0.0-SNAPSHOT - - io.helidon.examples.inject - helidon-examples-inject-project - pom - Helidon Examples Injection - - - basics - providers - configdriven - interceptors - - - diff --git a/examples/inject/providers/README.md b/examples/inject/providers/README.md deleted file mode 100644 index 57e5fa94f94..00000000000 --- a/examples/inject/providers/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Helidon Injection Providers Example - -This example shows how providers can be leveraged to develop using Helidon Injection. The -[Main.java](./src/main/java/io/helidon/examples/inject/providers/Main.java) class shows: - -* multi-module usage (i.e., this example extends [basics](../basics)). -* [standard Providers](src/main/java/io/helidon/examples/inject/providers/NailProvider.java). -* [InjectionPoint Providers](src/main/java/io/helidon/examples/inject/providers/BladeProvider.java). -* additional lifecycle examples via PostConstruct and RunLevel. - -## Build and run - -```shell -mvn package -java -jar target/helidon-examples-inject-providers.jar -``` - -Expected Output: -``` -Startup service providers (ranked according to weight, pre-activated): [ToolBox:INIT, CircularSaw:INIT, NailGun:INIT, TableSaw:INIT] -Preferred (highest weighted) 'Big' Tool: Big Hammer -Optional 'Little' Hammer: Optional[Little Hammer] -Tools in the virtual ToolBox: - tool: Hammer:INIT - tool: BigHammer:ACTIVE - tool: LittleHammer:ACTIVE - tool: AngleGrinderSaw:INIT - tool: CircularSaw:INIT - tool: HandSaw:INIT - tool: NailGun:INIT - tool: TableSaw:INIT -io.helidon.examples.inject.providers.CircularSaw:: will be injected with Optional.empty -Circular Saw: (blade=null); initialized -Nail Gun: (nail provider=NailProvider:INIT); initialized -io.helidon.examples.inject.providers.TableSaw:: will be injected with Optional[LARGE Blade] -Table Saw: (blade=LARGE Blade); initialized -All service providers (after all activations): [ToolBox:ACTIVE, CircularSaw:ACTIVE, NailGun:ACTIVE, TableSaw:ACTIVE] -``` diff --git a/examples/inject/providers/pom.xml b/examples/inject/providers/pom.xml deleted file mode 100644 index 29ec907114e..00000000000 --- a/examples/inject/providers/pom.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - - - 4.0.0 - - io.helidon.applications - helidon-se - 4.0.0-SNAPSHOT - ../../../applications/se/pom.xml - - io.helidon.examples.inject - helidon-examples-inject-providers - Helidon Examples Injection Providers - - - Example usages of Injection Providers. - - - - io.helidon.examples.inject.providers.Main - - - - - io.helidon.examples.inject - helidon-examples-inject-basics - ${helidon.version} - - - jakarta.annotation - jakarta.annotation-api - provided - - - org.hamcrest - hamcrest-all - test - - - org.junit.jupiter - junit-jupiter-api - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - true - - -Ainject.autoAddNonContractInterfaces=true - -Ainject.debug=false - -Ainject.acceptPreview=true - - - - io.helidon.inject - helidon-inject-processor - ${helidon.version} - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-libs - - - - - - diff --git a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/AngleGrinderSaw.java b/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/AngleGrinderSaw.java deleted file mode 100644 index ea9565c2caf..00000000000 --- a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/AngleGrinderSaw.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import java.util.Optional; - -import io.helidon.examples.inject.basics.Little; - -import jakarta.annotation.PostConstruct; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -@Singleton -class AngleGrinderSaw implements Saw { - - private final Blade blade; - - @Inject - AngleGrinderSaw(@Little Optional blade) { - this.blade = blade.orElse(null); - } - - @Override - public String name() { - return "Angle Grinder Saw: (blade=" + blade + ")"; - } - - @PostConstruct - @SuppressWarnings("unused") - void init() { - System.out.println(name() + "; initialized"); - } - -} diff --git a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Blade.java b/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Blade.java deleted file mode 100644 index 19ec44abb15..00000000000 --- a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Blade.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import io.helidon.inject.api.Contract; -import io.helidon.inject.api.Services; - -/** - * Normally, one would need to place {@link Contract} on interfaces. Here, however, we used - * {@code -Ainject.autoAddNonContractInterfaces=true} in the {@code pom.xml} thereby making all interfaces into contracts that - * can be found via {@link Services#lookup}. - */ -//@Contract -public interface Blade { - - String name(); - -} diff --git a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/BladeProvider.java b/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/BladeProvider.java deleted file mode 100644 index 759cdcd1e36..00000000000 --- a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/BladeProvider.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Optional; - -import io.helidon.common.LazyValue; -import io.helidon.examples.inject.basics.Big; -import io.helidon.examples.inject.basics.Little; -import io.helidon.inject.api.ContextualServiceQuery; -import io.helidon.inject.api.InjectionPointInfo; -import io.helidon.inject.api.InjectionPointProvider; -import io.helidon.inject.api.Qualifier; -import io.helidon.inject.api.ServiceInfoCriteria; - -import jakarta.inject.Singleton; - -import static io.helidon.common.LazyValue.create; - -@Singleton -public class BladeProvider implements InjectionPointProvider { - - static final LazyValue> LARGE_BLADE = create(() -> Optional.of(new SizedBlade(SizedBlade.Size.LARGE))); - static final LazyValue> SMALL_BLADE = create(() -> Optional.of(new SizedBlade(SizedBlade.Size.SMALL))); - - /** - * Here we are creating the right sized blade based upon the injection point's criteria. Note that the scope/cardinality - * is still (0..1), meaning there will be at most 1 LARGE and at most 1 SMALL blades provided. - * All {@code Provider}s control the scope of the service instances they provide. - * - * @param query the service query - * @return the blade appropriate for the injection point, or empty if nothing matches - * - * @see NailProvider - */ - @Override - public Optional first(ContextualServiceQuery query) { - ServiceInfoCriteria criteria = query.serviceInfoCriteria(); - if (contains(criteria.qualifiers(), Big.class)) { - return logAndReturn(LARGE_BLADE.get(), query); - } else if (contains(criteria.qualifiers(), Little.class)) { - return logAndReturn(SMALL_BLADE.get(), query); - } - return logAndReturn(Optional.empty(), query); - } - - static Optional logAndReturn(Optional result, - ContextualServiceQuery query) { - InjectionPointInfo ip = query.injectionPointInfo().orElse(null); - // note: a "regular" service lookup via Injection will not have an injection point associated with it - if (ip != null) { - System.out.println(ip.serviceTypeName() + "::" + ip.elementName() + " will be injected with " + result); - } - return result; - } - - static boolean contains(Collection qualifiers, - Class anno) { - return qualifiers.stream().anyMatch(it -> it.typeName().name().equals(anno.getName())); - } - -} diff --git a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/CircularSaw.java b/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/CircularSaw.java deleted file mode 100644 index cb951417b6a..00000000000 --- a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/CircularSaw.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import java.util.Optional; - -import io.helidon.inject.api.RunLevel; - -import jakarta.annotation.PostConstruct; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -@Singleton -@RunLevel(RunLevel.STARTUP) -class CircularSaw implements Saw { - - private final Blade blade; - - @Inject - CircularSaw(Optional blade) { - this.blade = blade.orElse(null); - } - - @Override - public String name() { - return "Circular Saw: (blade=" + blade + ")"; - } - - @PostConstruct - @SuppressWarnings("unused") - void init() { - System.out.println(name() + "; initialized"); - } - -} diff --git a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/HandSaw.java b/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/HandSaw.java deleted file mode 100644 index 1a9961ec302..00000000000 --- a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/HandSaw.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import java.util.Optional; - -import jakarta.annotation.PostConstruct; -import jakarta.inject.Inject; -import jakarta.inject.Named; -import jakarta.inject.Singleton; - -@Singleton -class HandSaw implements Saw { - - private final Blade blade; - - @Inject - HandSaw(@Named("replacement-blade-that-does-not-exist") Optional blade) { - this.blade = blade.orElse(null); - } - - @Override - public String name() { - return "Hand Saw: (blade=" + blade + ")"; - } - - @PostConstruct - @SuppressWarnings("unused") - void init() { - System.out.println(name() + "; initialized"); - } - -} diff --git a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Main.java b/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Main.java deleted file mode 100644 index 9c706922311..00000000000 --- a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Main.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import java.util.List; - -import io.helidon.inject.api.InjectionServices; -import io.helidon.inject.api.RunLevel; -import io.helidon.inject.api.ServiceInfoCriteria; -import io.helidon.inject.api.ServiceProvider; -import io.helidon.inject.api.Services; - -/** - * Providers example. - */ -public class Main { - - /** - * Executes the example. - * - * @param args arguments - */ - public static void main(String... args) { - Services services = InjectionServices.realizedServices(); - - ServiceInfoCriteria criteria = ServiceInfoCriteria.builder() - .runLevel(RunLevel.STARTUP) - .build(); - - List> startupServiceProviders = services.lookupAll(criteria); - System.out.println("Startup service providers (ranked according to weight, pre-activated): " + startupServiceProviders); - - // trigger all activations for startup service providers - startupServiceProviders.forEach(ServiceProvider::get); - System.out.println("All service providers (after all activations): " + startupServiceProviders); - } - -} diff --git a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Nail.java b/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Nail.java deleted file mode 100644 index b14c5f5837f..00000000000 --- a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Nail.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import io.helidon.inject.api.Contract; -import io.helidon.inject.api.Services; - -/** - * Normally, one would need to place {@link Contract} on interfaces. Here, however, we used - * {@code -Ainject.autoAddNonContractInterfaces=true} in the {@code pom.xml} thereby making all interfaces into contracts that - * can be found via {@link Services#lookup}. - */ -//@Contract -public interface Nail { - - int id(); - -} diff --git a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/NailGun.java b/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/NailGun.java deleted file mode 100644 index f19c713d616..00000000000 --- a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/NailGun.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import java.util.Objects; - -import io.helidon.examples.inject.basics.Tool; -import io.helidon.inject.api.RunLevel; - -import jakarta.annotation.PostConstruct; -import jakarta.inject.Inject; -import jakarta.inject.Provider; -import jakarta.inject.Singleton; - -@Singleton -@RunLevel(RunLevel.STARTUP) -class NailGun implements Tool { - - private final Provider nailProvider; - - @Inject - NailGun(Provider nailProvider) { - this.nailProvider = Objects.requireNonNull(nailProvider); - } - - @Override - public String name() { - return "Nail Gun: (nail provider=" + nailProvider + ")"; - } - - /** - * This method will be called by Injection after this instance is lazily initialized (because this is the {@link PostConstruct} - * method). - */ - @PostConstruct - @SuppressWarnings("unused") - void init() { - System.out.println(name() + "; initialized"); - } - -} diff --git a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/NailProvider.java b/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/NailProvider.java deleted file mode 100644 index 1d2bd7bfe6b..00000000000 --- a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/NailProvider.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import jakarta.inject.Provider; -import jakarta.inject.Singleton; - -/** - * Showcases dependent scope-creating a nail for caller's demand for a {@link Nail} to be provided. - * All {@code Provider}s control the scope of the service instances they provide. - * - * @see BladeProvider - */ -@Singleton -class NailProvider implements Provider { - - /** - * Creates a new nail every its called. - * - * @return a new nail instance - */ - @Override - public Nail get() { - return new StandardNail(); - } - -} diff --git a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Saw.java b/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Saw.java deleted file mode 100644 index cb73214ea37..00000000000 --- a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/Saw.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import io.helidon.examples.inject.basics.Tool; -import io.helidon.inject.api.Contract; -import io.helidon.inject.api.Services; - -/** - * Normally, one would need to place {@link Contract} on interfaces. Here, however, we used - * {@code -Ainject.autoAddNonContractInterfaces=true} in the {@code pom.xml} thereby making all interfaces into contracts that - * can be found via {@link Services#lookup}. - */ -//@Contract -public interface Saw extends Tool { - -} diff --git a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/SizedBlade.java b/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/SizedBlade.java deleted file mode 100644 index fdb830560b7..00000000000 --- a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/SizedBlade.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import java.util.Objects; - -/** - * See {@link Blade} - */ -class SizedBlade implements Blade { - - private final Size size; - - public enum Size { - SMALL, - LARGE - } - - public SizedBlade(Size size) { - this.size = Objects.requireNonNull(size); - } - - @Override - public String name() { - return size + " Blade"; - } - - @Override - public String toString() { - return name(); - } - -} diff --git a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/StandardNail.java b/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/StandardNail.java deleted file mode 100644 index 00eb469c237..00000000000 --- a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/StandardNail.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import java.util.concurrent.atomic.AtomicInteger; - -class StandardNail implements Nail { - - private static final AtomicInteger counter = new AtomicInteger(); - private final int id = counter.incrementAndGet(); - - StandardNail() { - } - - @Override - public int id() { - return id; - } - -} diff --git a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/TableSaw.java b/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/TableSaw.java deleted file mode 100644 index 70f9bd2b9a5..00000000000 --- a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/TableSaw.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import java.util.Optional; - -import io.helidon.examples.inject.basics.Big; -import io.helidon.inject.api.RunLevel; - -import jakarta.annotation.PostConstruct; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -@Singleton -@RunLevel(RunLevel.STARTUP) -class TableSaw implements Saw { - - private final Blade blade; - - @Inject - TableSaw(@Big Optional blade) { - this.blade = blade.orElse(null); - } - - @Override - public String name() { - return "Table Saw: (blade=" + blade + ")"; - } - - @PostConstruct - @SuppressWarnings("unused") - void init() { - System.out.println(name() + "; initialized"); - } - -} diff --git a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/package-info.java b/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/package-info.java deleted file mode 100644 index fd932e1fa0b..00000000000 --- a/examples/inject/providers/src/main/java/io/helidon/examples/inject/providers/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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. - */ - -/** - * Examples of providers in Injection. - */ -package io.helidon.examples.inject.providers; diff --git a/examples/inject/providers/src/main/resources/logging.properties b/examples/inject/providers/src/main/resources/logging.properties deleted file mode 100644 index bd06e0ed087..00000000000 --- a/examples/inject/providers/src/main/resources/logging.properties +++ /dev/null @@ -1,26 +0,0 @@ -# -# 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. -# - - -handlers = java.util.logging.ConsoleHandler - -java.util.logging.ConsoleHandler.level = FINEST -java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter -java.util.logging.SimpleFormatter.format = [%1$tc] %4$s: %2$s - %5$s %6$s%n - -.level = INFO -io.helidon.config.level = WARNING -io.helidon.config.examples.level = FINEST diff --git a/examples/inject/providers/src/test/java/io/helidon/examples/inject/providers/AllenWrench.java b/examples/inject/providers/src/test/java/io/helidon/examples/inject/providers/AllenWrench.java deleted file mode 100644 index e218d8c3245..00000000000 --- a/examples/inject/providers/src/test/java/io/helidon/examples/inject/providers/AllenWrench.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import jakarta.inject.Singleton; - -@Singleton -@SuppressWarnings("unused") -public class AllenWrench implements Wrench { - - @Override - public String name() { - return "Allen Wrench"; - } - -} diff --git a/examples/inject/providers/src/test/java/io/helidon/examples/inject/providers/ProvidersTest.java b/examples/inject/providers/src/test/java/io/helidon/examples/inject/providers/ProvidersTest.java deleted file mode 100644 index 105f0e86bf0..00000000000 --- a/examples/inject/providers/src/test/java/io/helidon/examples/inject/providers/ProvidersTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import io.helidon.examples.inject.basics.ToolBox; - -import org.junit.jupiter.api.Test; - -class ProvidersTest { - - /** - * Through testing, this will additionally have an {@link AllenWrench} in the {@link ToolBox}. - */ - @Test - void main() { - Main.main(); - } - -} diff --git a/examples/inject/providers/src/test/java/io/helidon/examples/inject/providers/Wrench.java b/examples/inject/providers/src/test/java/io/helidon/examples/inject/providers/Wrench.java deleted file mode 100644 index 247e00b3477..00000000000 --- a/examples/inject/providers/src/test/java/io/helidon/examples/inject/providers/Wrench.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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. - */ - -package io.helidon.examples.inject.providers; - -import io.helidon.examples.inject.basics.Tool; - -public interface Wrench extends Tool { - -} diff --git a/examples/pom.xml b/examples/pom.xml index b8fe6d01211..ac4c94ea6f6 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -47,7 +47,6 @@ graphql health integrations - inject jbatch logging media diff --git a/fault-tolerance/fault-tolerance/pom.xml b/fault-tolerance/fault-tolerance/pom.xml index e86c5474f24..4e51b161d8e 100644 --- a/fault-tolerance/fault-tolerance/pom.xml +++ b/fault-tolerance/fault-tolerance/pom.xml @@ -41,23 +41,6 @@ io.helidon.common helidon-common-configurable - - - io.helidon.inject - helidon-inject-api - - - - io.helidon.inject - helidon-inject-runtime - true - - io.helidon.inject.configdriven - helidon-inject-configdriven-api - true - - - - io.helidon.inject.configdriven - helidon-inject-configdriven-runtime - true - org.hamcrest hamcrest-all @@ -129,11 +95,6 @@ helidon-config-metadata-processor ${helidon.version} - - io.helidon.inject.configdriven - helidon-inject-configdriven-processor - ${helidon.version} - io.helidon.codegen helidon-codegen-apt @@ -157,11 +118,6 @@ helidon-config-metadata-processor ${helidon.version} - - io.helidon.inject.configdriven - helidon-inject-configdriven-processor - ${helidon.version} - io.helidon.common.features helidon-common-features-processor diff --git a/tests/integration/native-image/mp-1/pom.xml b/tests/integration/native-image/mp-1/pom.xml index 8ee1ce95039..e44426cdef2 100644 --- a/tests/integration/native-image/mp-1/pom.xml +++ b/tests/integration/native-image/mp-1/pom.xml @@ -53,11 +53,6 @@ io.helidon.security.providers helidon-security-providers-oidc - - io.helidon.inject - helidon-inject-runtime - true - io.helidon.tracing.providers helidon-tracing-providers-jaeger diff --git a/tests/integration/native-image/mp-3/pom.xml b/tests/integration/native-image/mp-3/pom.xml index a1eb94f4b91..d1bcde10734 100644 --- a/tests/integration/native-image/mp-3/pom.xml +++ b/tests/integration/native-image/mp-3/pom.xml @@ -42,11 +42,6 @@ io.helidon.microprofile.bundles helidon-microprofile - - io.helidon.inject - helidon-inject-runtime - true - io.smallrye jandex diff --git a/webclient/api/pom.xml b/webclient/api/pom.xml index 6cf10fed096..f4083b8d2a7 100644 --- a/webclient/api/pom.xml +++ b/webclient/api/pom.xml @@ -77,16 +77,6 @@ io.helidon.builder helidon-builder-api - - io.helidon.inject.configdriven - helidon-inject-configdriven-api - true - - - io.helidon.inject.configdriven - helidon-inject-configdriven-runtime - true - org.junit.jupiter junit-jupiter-api @@ -136,12 +126,6 @@ helidon-codegen-helidon-copyright ${helidon.version} - - - io.helidon.inject.configdriven - helidon-inject-configdriven-processor - ${helidon.version} - io.helidon.config helidon-config-metadata-processor @@ -150,11 +134,6 @@ - - io.helidon.inject.configdriven - helidon-inject-configdriven-processor - ${helidon.version} - io.helidon.common.features helidon-common-features-processor diff --git a/webclient/http1/pom.xml b/webclient/http1/pom.xml index 5c20441e312..bae4376e42a 100644 --- a/webclient/http1/pom.xml +++ b/webclient/http1/pom.xml @@ -77,16 +77,6 @@ io.helidon.builder helidon-builder-api - - io.helidon.inject.configdriven - helidon-inject-configdriven-api - true - - - io.helidon.inject.configdriven - helidon-inject-configdriven-runtime - true - io.helidon.logging helidon-logging-jul @@ -126,11 +116,6 @@ helidon-common-features-processor ${helidon.version} - - io.helidon.inject.configdriven - helidon-inject-configdriven-processor - ${helidon.version} - io.helidon.config helidon-config-metadata-processor @@ -154,11 +139,6 @@ - - io.helidon.inject.configdriven - helidon-inject-configdriven-processor - ${helidon.version} - io.helidon.common.features helidon-common-features-processor diff --git a/webserver/webserver/pom.xml b/webserver/webserver/pom.xml index 19181012a4a..f9915e2315a 100644 --- a/webserver/webserver/pom.xml +++ b/webserver/webserver/pom.xml @@ -135,11 +135,6 @@ io.helidon.common.testing test - - io.helidon.inject - helidon-inject-testing - test - From 5397548c9d44c66984bce097845532ab85fbc017 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 2 May 2024 13:43:35 +0200 Subject: [PATCH 05/20] Remove usage of Helidon Inject from config, webserver, webclient, tls, and fault tolerance --- .../codegen/apt/AptTypeInfoFactory.java | 2 +- common/tls/pom.xml | 4 ++ .../io/helidon/common/tls/TlsManager.java | 6 +- common/tls/src/main/java/module-info.java | 3 +- config/config/pom.xml | 41 +++++++++-- .../main/java/io/helidon/config/Config.java | 5 +- ...onfigProducer.java => ConfigProvider.java} | 40 +++++++---- .../java/io/helidon/config/MetaConfig.java | 17 ++++- .../io/helidon/config/spi/ConfigFilter.java | 2 + .../config/spi/ConfigMapperProvider.java | 4 +- .../io/helidon/config/spi/ConfigParser.java | 4 +- .../io/helidon/config/spi/ConfigSource.java | 6 +- config/config/src/main/java/module-info.java | 11 +-- .../resources/META-INF/helidon/service.loader | 4 ++ .../tests/config-metadata-builder-api/pom.xml | 14 +++- config/tests/service-registry/pom.xml | 32 +++++++-- .../service/registry/ConfigProducerTest.java | 37 +++++----- .../service/registry/TestConfigSource.java | 10 +-- .../src/test/resources/application.yaml | 7 +- etc/copyright-exclude.txt | 1 + .../java/io/helidon/faulttolerance/Async.java | 4 +- .../io/helidon/faulttolerance/AsyncImpl.java | 30 +------- .../io/helidon/faulttolerance/Bulkhead.java | 4 +- .../BulkheadConfigBlueprint.java | 2 - .../helidon/faulttolerance/BulkheadImpl.java | 5 +- .../faulttolerance/CircuitBreaker.java | 4 +- .../CircuitBreakerConfigBlueprint.java | 2 - .../faulttolerance/CircuitBreakerImpl.java | 8 +-- .../java/io/helidon/faulttolerance/Retry.java | 4 +- .../io/helidon/faulttolerance/RetryImpl.java | 5 +- .../io/helidon/faulttolerance/Timeout.java | 4 +- .../helidon/faulttolerance/TimeoutImpl.java | 5 +- .../src/main/java/module-info.java | 12 ---- .../inject/tests/inject/tbox/ToolBoxTest.java | 13 ++-- .../HelloInjectionWorldSanityTest.java | 8 +-- .../mp-1/src/main/java/module-info.java | 5 +- tests/integration/vault/pom.xml | 5 ++ .../io/helidon/webclient/api/LoomClient.java | 7 +- .../api/WebClientConfigBlueprint.java | 2 - webclient/api/src/main/java/module-info.java | 3 - .../webserver/WebServerConfigDrivenTest.java | 69 ------------------- 41 files changed, 194 insertions(+), 257 deletions(-) rename config/config/src/main/java/io/helidon/config/{ConfigProducer.java => ConfigProvider.java} (68%) create mode 100644 config/config/src/main/resources/META-INF/helidon/service.loader delete mode 100644 webserver/webserver/src/test/java/io/helidon/webserver/WebServerConfigDrivenTest.java diff --git a/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeInfoFactory.java b/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeInfoFactory.java index e3e83f8e950..d044bc99704 100644 --- a/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeInfoFactory.java +++ b/codegen/apt/src/main/java/io/helidon/codegen/apt/AptTypeInfoFactory.java @@ -589,7 +589,7 @@ private static void addInherited(AptContext ctx, Optional found = create(ctx, annotationType, it -> false); if (found.isEmpty()) { - ctx.logger().log(System.Logger.Level.INFO, "Annotation " + annotationType + ctx.logger().log(System.Logger.Level.DEBUG, "Annotation " + annotationType + " not available, cannot obtain inherited annotations"); return; } diff --git a/common/tls/pom.xml b/common/tls/pom.xml index cb42893885e..7934038f4dd 100644 --- a/common/tls/pom.xml +++ b/common/tls/pom.xml @@ -50,6 +50,10 @@ io.helidon.builder helidon-builder-api + + + io.helidon.service + helidon-service-registry true diff --git a/common/tls/src/main/java/io/helidon/common/tls/TlsManager.java b/common/tls/src/main/java/io/helidon/common/tls/TlsManager.java index 83be02abee0..2f4c26ee810 100644 --- a/common/tls/src/main/java/io/helidon/common/tls/TlsManager.java +++ b/common/tls/src/main/java/io/helidon/common/tls/TlsManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -23,7 +23,7 @@ import javax.net.ssl.X509TrustManager; import io.helidon.common.config.NamedService; -import io.helidon.inject.api.Contract; +import io.helidon.service.registry.Service; /** * Implementors of this contract are responsible for managing the {@link javax.net.ssl.SSLContext} instance lifecycle, as well @@ -32,7 +32,7 @@ *

* How context changes are observed is based upon the implementation of the manager. */ -@Contract +@Service.Contract public interface TlsManager extends NamedService { /** diff --git a/common/tls/src/main/java/module-info.java b/common/tls/src/main/java/module-info.java index b48537cc15b..8bdffb196b8 100644 --- a/common/tls/src/main/java/module-info.java +++ b/common/tls/src/main/java/module-info.java @@ -18,7 +18,8 @@ * TLS configuration for client and server. */ module io.helidon.common.tls { - requires static io.helidon.inject.api; + // only annotation, no generated code + requires static io.helidon.service.registry; requires io.helidon.builder.api; requires io.helidon.common; diff --git a/config/config/pom.xml b/config/config/pom.xml index c5301ae0ebf..e788c3c70b2 100644 --- a/config/config/pom.xml +++ b/config/config/pom.xml @@ -50,6 +50,15 @@ io.helidon.common helidon-common-media-type + + io.helidon.builder + helidon-builder-api + + + io.helidon.service + helidon-service-registry + true + io.helidon.common.features helidon-common-features-api @@ -104,14 +113,24 @@ helidon-common-features-processor ${helidon.version} + + io.helidon.codegen + helidon-codegen-apt + ${helidon.version} + io.helidon.builder - helidon-builder-processor + helidon-builder-codegen ${helidon.version} - io.helidon.common.processor - helidon-common-processor-helidon-copyright + io.helidon.service + helidon-service-codegen + ${helidon.version} + + + io.helidon.codegen + helidon-codegen-helidon-copyright ${helidon.version} @@ -122,14 +141,24 @@ helidon-common-features-processor ${helidon.version} + + io.helidon.codegen + helidon-codegen-apt + ${helidon.version} + io.helidon.builder - helidon-builder-processor + helidon-builder-codegen + ${helidon.version} + + + io.helidon.service + helidon-service-codegen ${helidon.version} - io.helidon.common.processor - helidon-common-processor-helidon-copyright + io.helidon.codegen + helidon-codegen-helidon-copyright ${helidon.version} diff --git a/config/config/src/main/java/io/helidon/config/Config.java b/config/config/src/main/java/io/helidon/config/Config.java index 62f015c0a97..f34c4a88d38 100644 --- a/config/config/src/main/java/io/helidon/config/Config.java +++ b/config/config/src/main/java/io/helidon/config/Config.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023 Oracle and/or its affiliates. + * Copyright (c) 2017, 2024 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. @@ -38,6 +38,7 @@ import io.helidon.config.spi.ConfigSource; import io.helidon.config.spi.MergingStrategy; import io.helidon.config.spi.OverrideSource; +import io.helidon.service.registry.Service; /** *

Configuration

@@ -232,7 +233,6 @@ * throws {@link ConfigMappingException}, unless you use the config beans support, * that can handle classes that fulfill some requirements (see documentation), such as a public constructor, * static "create(Config)" method etc. - *

*

Handling Multiple Configuration * Sources

* A {@code Config} instance, including the default {@code Config} returned by @@ -240,6 +240,7 @@ * config system merges these together so that values from config sources with higher {@link io.helidon.common.Weight weight} * have priority over values from config sources with lower weight. */ +@Service.Contract public interface Config extends io.helidon.common.config.Config { /** * Generic type of configuration. diff --git a/config/config/src/main/java/io/helidon/config/ConfigProducer.java b/config/config/src/main/java/io/helidon/config/ConfigProvider.java similarity index 68% rename from config/config/src/main/java/io/helidon/config/ConfigProducer.java rename to config/config/src/main/java/io/helidon/config/ConfigProvider.java index 5db55517f1f..d6ed2e86620 100644 --- a/config/config/src/main/java/io/helidon/config/ConfigProducer.java +++ b/config/config/src/main/java/io/helidon/config/ConfigProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2024 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. @@ -19,34 +19,44 @@ import java.util.List; import java.util.Map; import java.util.function.Function; +import java.util.function.Supplier; 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.ConfigFilter; +import io.helidon.config.spi.ConfigMapperProvider; +import io.helidon.config.spi.ConfigParser; import io.helidon.config.spi.ConfigSource; -import io.helidon.inject.api.ExternalContracts; +import io.helidon.service.registry.Service; -import jakarta.inject.Inject; -import jakarta.inject.Provider; -import jakarta.inject.Singleton; - -@Singleton -@ExternalContracts(Config.class) -class ConfigProducer implements Config { +@Service.Provider +@Service.ExternalContracts(Config.class) +class ConfigProvider implements Config { private final Config config; - @Inject - ConfigProducer(List> serviceProviders) { + ConfigProvider(Supplier metaConfig, + Supplier> configSources, + Supplier> configParsers, + Supplier> configFilters, + Supplier> configMappers) { 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) + .config(metaConfig.get().metaConfiguration()) + .update(it -> configSources.get() .forEach(it::addSource)) + .disableParserServices() + .update(it -> configParsers.get() + .forEach(it::addParser)) + .disableFilterServices() + .update(it -> configFilters.get() + .forEach(it::addFilter)) + .disableMapperServices() + .update(it -> configMappers.get() + .forEach(it::addMapper)) .build(); } } diff --git a/config/config/src/main/java/io/helidon/config/MetaConfig.java b/config/config/src/main/java/io/helidon/config/MetaConfig.java index d96bd48fc5f..afbe1b9c502 100644 --- a/config/config/src/main/java/io/helidon/config/MetaConfig.java +++ b/config/config/src/main/java/io/helidon/config/MetaConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022 Oracle and/or its affiliates. + * Copyright (c) 2019, 2024 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. @@ -32,6 +32,7 @@ import io.helidon.config.spi.OverrideSource; import io.helidon.config.spi.PollingStrategy; import io.helidon.config.spi.RetryPolicy; +import io.helidon.service.registry.Service; /** * Meta configuration. @@ -69,6 +70,7 @@ *
  • Classpath resource config source for resource {@code default.yaml} that is mandatory
  • * */ +@Service.Provider public final class MetaConfig { private static final System.Logger LOGGER = System.getLogger(MetaConfig.class.getName()); private static final Set SUPPORTED_MEDIA_TYPES; @@ -88,7 +90,10 @@ public final class MetaConfig { SUPPORTED_SUFFIXES = List.copyOf(supportedSuffixes); } - private MetaConfig() { + private final Config metaConfig; + + MetaConfig() { + this.metaConfig = metaConfig().orElseGet(Config::empty); } /** @@ -201,7 +206,15 @@ public static List configSource(Config sourceMetaConfig) { return List.of(source); } + } + /** + * Meta configuration if provided, or empty config if not. + * + * @return meta configuration + */ + public Config metaConfiguration() { + return this.metaConfig; } // override config source diff --git a/config/config/src/main/java/io/helidon/config/spi/ConfigFilter.java b/config/config/src/main/java/io/helidon/config/spi/ConfigFilter.java index f6994eea604..80b237ca605 100644 --- a/config/config/src/main/java/io/helidon/config/spi/ConfigFilter.java +++ b/config/config/src/main/java/io/helidon/config/spi/ConfigFilter.java @@ -18,6 +18,7 @@ import io.helidon.config.Config; import io.helidon.config.ConfigItem; +import io.helidon.service.registry.Service; /** * Filter that can transform elementary configuration ({@code String}) values @@ -53,6 +54,7 @@ * @see Config.Builder#addFilter(java.util.function.Function) */ @FunctionalInterface +@Service.Contract public interface ConfigFilter { /** diff --git a/config/config/src/main/java/io/helidon/config/spi/ConfigMapperProvider.java b/config/config/src/main/java/io/helidon/config/spi/ConfigMapperProvider.java index 7d0be9cccf7..c35c8b0e0cd 100644 --- a/config/config/src/main/java/io/helidon/config/spi/ConfigMapperProvider.java +++ b/config/config/src/main/java/io/helidon/config/spi/ConfigMapperProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023 Oracle and/or its affiliates. + * Copyright (c) 2017, 2024 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. @@ -23,6 +23,7 @@ import io.helidon.common.GenericType; import io.helidon.config.Config; +import io.helidon.service.registry.Service; /** * Provides mapping functions that convert a {@code Config} @@ -43,6 +44,7 @@ * @see Config.Builder#disableMapperServices() */ @FunctionalInterface +@Service.Contract public interface ConfigMapperProvider { /** diff --git a/config/config/src/main/java/io/helidon/config/spi/ConfigParser.java b/config/config/src/main/java/io/helidon/config/spi/ConfigParser.java index bda0970a675..2413f0e61ab 100644 --- a/config/config/src/main/java/io/helidon/config/spi/ConfigParser.java +++ b/config/config/src/main/java/io/helidon/config/spi/ConfigParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023 Oracle and/or its affiliates. + * Copyright (c) 2017, 2024 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. @@ -27,6 +27,7 @@ import io.helidon.common.media.type.MediaType; import io.helidon.config.spi.ConfigNode.ObjectNode; +import io.helidon.service.registry.Service; /** * Transforms config {@link io.helidon.config.spi.ConfigParser.Content} into a {@link ConfigNode.ObjectNode} that @@ -49,6 +50,7 @@ * @see io.helidon.config.spi.ParsableSource * @see io.helidon.config.ConfigParsers ConfigParsers - access built-in implementations. */ +@Service.Contract public interface ConfigParser { /** * Returns set of supported media types by the parser. diff --git a/config/config/src/main/java/io/helidon/config/spi/ConfigSource.java b/config/config/src/main/java/io/helidon/config/spi/ConfigSource.java index 59829aa6974..0a89530f30d 100644 --- a/config/config/src/main/java/io/helidon/config/spi/ConfigSource.java +++ b/config/config/src/main/java/io/helidon/config/spi/ConfigSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023 Oracle and/or its affiliates. + * Copyright (c) 2017, 2024 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. @@ -20,7 +20,7 @@ import io.helidon.config.Config; import io.helidon.config.ConfigSources; -import io.helidon.inject.api.Contract; +import io.helidon.service.registry.Service; /** * {@link Source} of configuration. @@ -63,7 +63,7 @@ * @see io.helidon.config.AbstractConfigSource * @see ConfigSources ConfigSources - access built-in implementations. */ -@Contract +@Service.Contract public interface ConfigSource extends Supplier, Source { @Override default ConfigSource get() { diff --git a/config/config/src/main/java/module-info.java b/config/config/src/main/java/module-info.java index 35e64e3bf5b..03790f9abcf 100644 --- a/config/config/src/main/java/module-info.java +++ b/config/config/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023 Oracle and/or its affiliates. + * Copyright (c) 2017, 2024 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. @@ -27,16 +27,13 @@ in = HelidonFlavor.SE ) module io.helidon.config { - - requires io.helidon.inject.api; - requires static io.helidon.common.features.api; - requires static io.helidon.inject.runtime; - requires static jakarta.inject; + requires static io.helidon.service.registry; requires transitive io.helidon.common.config; requires transitive io.helidon.common.media.type; requires transitive io.helidon.common; + requires transitive io.helidon.builder.api; exports io.helidon.config; exports io.helidon.config.spi; @@ -54,8 +51,6 @@ with io.helidon.config.PropertiesConfigParser; provides io.helidon.common.config.spi.ConfigProvider with io.helidon.config.HelidonConfigProvider; - provides io.helidon.inject.api.ModuleComponent - with io.helidon.config.Injection$$Module; // needed when running with modules - to make private methods accessible opens io.helidon.config to weld.core.impl, io.helidon.microprofile.cdi; diff --git a/config/config/src/main/resources/META-INF/helidon/service.loader b/config/config/src/main/resources/META-INF/helidon/service.loader new file mode 100644 index 00000000000..d07172ed9b3 --- /dev/null +++ b/config/config/src/main/resources/META-INF/helidon/service.loader @@ -0,0 +1,4 @@ +# List of service contracts we want to support either from service registry, or from service loader +io.helidon.config.spi.ConfigParser +io.helidon.config.spi.ConfigFilter +io.helidon.config.spi.ConfigMapperProvider diff --git a/config/tests/config-metadata-builder-api/pom.xml b/config/tests/config-metadata-builder-api/pom.xml index 7e6d088a01c..a1ffd5d69a8 100644 --- a/config/tests/config-metadata-builder-api/pom.xml +++ b/config/tests/config-metadata-builder-api/pom.xml @@ -77,9 +77,14 @@ helidon-config-metadata-processor ${helidon.version} + + io.helidon.codegen + helidon-codegen-apt + ${helidon.version} + io.helidon.builder - helidon-builder-processor + helidon-builder-codegen ${helidon.version} @@ -90,9 +95,14 @@ helidon-config-metadata-processor ${helidon.version} + + io.helidon.codegen + helidon-codegen-apt + ${helidon.version} + io.helidon.builder - helidon-builder-processor + helidon-builder-codegen ${helidon.version} diff --git a/config/tests/service-registry/pom.xml b/config/tests/service-registry/pom.xml index c6cd2738391..09310504d0b 100644 --- a/config/tests/service-registry/pom.xml +++ b/config/tests/service-registry/pom.xml @@ -34,6 +34,10 @@ io.helidon.config helidon-config + + io.helidon.service + helidon-service-registry + io.helidon.config helidon-config-yaml @@ -59,16 +63,36 @@ - io.helidon.common.processor - helidon-common-processor-helidon-copyright + io.helidon.codegen + helidon-codegen-apt + ${helidon.version} + + + io.helidon.service + helidon-service-codegen + ${helidon.version} + + + io.helidon.codegen + helidon-codegen-helidon-copyright ${helidon.version} - io.helidon.common.processor - helidon-common-processor-helidon-copyright + io.helidon.codegen + helidon-codegen-apt + ${helidon.version} + + + io.helidon.service + helidon-service-codegen + ${helidon.version} + + + io.helidon.codegen + helidon-codegen-helidon-copyright ${helidon.version} diff --git a/config/tests/service-registry/src/test/java/io/helidon/config/tests/service/registry/ConfigProducerTest.java b/config/tests/service-registry/src/test/java/io/helidon/config/tests/service/registry/ConfigProducerTest.java index 19c0badf958..dbd7ebaf6da 100644 --- a/config/tests/service-registry/src/test/java/io/helidon/config/tests/service/registry/ConfigProducerTest.java +++ b/config/tests/service-registry/src/test/java/io/helidon/config/tests/service/registry/ConfigProducerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -20,9 +20,7 @@ import io.helidon.common.config.Config; import io.helidon.common.config.GlobalConfig; -import io.helidon.inject.api.Bootstrap; -import io.helidon.inject.api.InjectionServices; -import io.helidon.inject.testing.InjectionTestingSupport; +import io.helidon.service.registry.ServiceRegistryManager; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.MethodOrderer; @@ -35,22 +33,25 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class ConfigProducerTest { + private ServiceRegistryManager registryManager; + @AfterEach - void reset() { - InjectionTestingSupport.resetAll(); + void shutdownServices() { + if (registryManager != null) { + registryManager.shutdown(); + } } @Test - @Order(0) // this must be first, as once we set global config, this method will always fail + @Order(0) + // this must be first, as once we set global config, this method will always fail void testConfig() { - InjectionServices.globalBootstrap(Bootstrap.builder() - .config(GlobalConfig.config()) - .build()); + registryManager = ServiceRegistryManager.create(); // value should be overridden using our custom config source - Config config = InjectionServices.realizedServices() - .lookup(Config.class) - .get(); + Config config = registryManager + .registry() + .get(Config.class); assertThat(config.get("app.value").asString().asOptional(), is(Optional.of("source"))); } @@ -61,13 +62,11 @@ void testExplicitConfig() { // value should use the config as we provided it GlobalConfig.config(io.helidon.config.Config::create, true); - InjectionServices.globalBootstrap(Bootstrap.builder() - .config(GlobalConfig.config()) - .build()); + registryManager = ServiceRegistryManager.create(); - Config config = InjectionServices.realizedServices() - .lookup(Config.class) - .get(); + Config config = registryManager + .registry() + .get(Config.class); assertThat(config.get("app.value").asString().asOptional(), is(Optional.of("file"))); } diff --git a/config/tests/service-registry/src/test/java/io/helidon/config/tests/service/registry/TestConfigSource.java b/config/tests/service-registry/src/test/java/io/helidon/config/tests/service/registry/TestConfigSource.java index 6769a3ceb99..a98bdab8457 100644 --- a/config/tests/service-registry/src/test/java/io/helidon/config/tests/service/registry/TestConfigSource.java +++ b/config/tests/service-registry/src/test/java/io/helidon/config/tests/service/registry/TestConfigSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -22,15 +22,11 @@ import io.helidon.config.ConfigException; import io.helidon.config.spi.ConfigContent; import io.helidon.config.spi.ConfigNode; -import io.helidon.config.spi.ConfigSource; import io.helidon.config.spi.NodeConfigSource; -import io.helidon.inject.api.ExternalContracts; +import io.helidon.service.registry.Service; -import jakarta.inject.Singleton; - -@Singleton +@Service.Provider @Weight(200) -@ExternalContracts(ConfigSource.class) class TestConfigSource implements NodeConfigSource { @Override diff --git a/config/tests/service-registry/src/test/resources/application.yaml b/config/tests/service-registry/src/test/resources/application.yaml index a0154957f7d..f048a795f74 100644 --- a/config/tests/service-registry/src/test/resources/application.yaml +++ b/config/tests/service-registry/src/test/resources/application.yaml @@ -1,5 +1,5 @@ # -# Copyright (c) 2023 Oracle and/or its affiliates. +# Copyright (c) 2023, 2024 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. @@ -15,8 +15,3 @@ # app.value: "file" - -# needed to reset state of service registry in test -inject: - permits-dynamic: true - service-lookup-caching: true diff --git a/etc/copyright-exclude.txt b/etc/copyright-exclude.txt index 44a2370c1ee..3c4b0e2df76 100644 --- a/etc/copyright-exclude.txt +++ b/etc/copyright-exclude.txt @@ -66,3 +66,4 @@ persistence_3_0.xsd src/test/resources/static/classpath/index.html ._java_ ._inject_ +service.loader diff --git a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Async.java b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Async.java index b3918dd9ee9..84188d97fcb 100644 --- a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Async.java +++ b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Async.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2024 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. @@ -21,13 +21,11 @@ import java.util.function.Supplier; import io.helidon.builder.api.RuntimeType; -import io.helidon.inject.api.Contract; /** * Runs synchronous suppliers asynchronously using virtual threads. Includes * convenient static method to avoid creating instances of this class. */ -@Contract @RuntimeType.PrototypedBy(AsyncConfig.class) public interface Async extends RuntimeType.Api { diff --git a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/AsyncImpl.java b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/AsyncImpl.java index 22edfa41581..fffab873415 100644 --- a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/AsyncImpl.java +++ b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/AsyncImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2024 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. @@ -16,20 +16,12 @@ package io.helidon.faulttolerance; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; -import io.helidon.inject.api.InjectionServices; -import io.helidon.inject.api.Qualifier; -import io.helidon.inject.api.ServiceInfoCriteria; -import io.helidon.inject.api.ServiceProvider; - -import jakarta.inject.Inject; - /** * Implementation of {@code Async}. Default executor accessed from {@link FaultTolerance#executor()}. */ @@ -40,16 +32,6 @@ class AsyncImpl implements Async { private final CompletableFuture onStart; private final AsyncConfig config; - // this must only be invoked when within Pico, so we can use pico services - @Inject - AsyncImpl(AsyncConfig config) { - this.executor = config.executor() - .or(() -> config.executorName().flatMap(AsyncImpl::executorService)) - .orElseGet(() -> FaultTolerance.executor().get()); - this.onStart = config.onStart().orElseGet(CompletableFuture::new); - this.config = config; - } - AsyncImpl(AsyncConfig config, boolean internal) { this.executor = config.executor().orElseGet(() -> FaultTolerance.executor().get()); this.onStart = config.onStart().orElseGet(CompletableFuture::new); @@ -96,14 +78,4 @@ public boolean cancel(boolean mayInterruptIfRunning) { return result; } - - private static Optional executorService(String name) { - var qualifier = Qualifier.createNamed(name); - return InjectionServices.realizedServices().lookupFirst(ExecutorService.class, - ServiceInfoCriteria.builder() - .addQualifier(qualifier) - .build(), - false) - .map(ServiceProvider::get); - } } diff --git a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Bulkhead.java b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Bulkhead.java index d0531dd8b13..98c8ac521e0 100644 --- a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Bulkhead.java +++ b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Bulkhead.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 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. @@ -20,7 +20,6 @@ import java.util.function.Supplier; import io.helidon.builder.api.RuntimeType; -import io.helidon.inject.api.Contract; /** * Bulkhead protects a resource that cannot serve unlimited parallel @@ -31,7 +30,6 @@ * additional attempts to invoke will end with a failed response with * {@link BulkheadException}. */ -@Contract @RuntimeType.PrototypedBy(BulkheadConfig.class) public interface Bulkhead extends FtHandler, RuntimeType.Api { /** diff --git a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/BulkheadConfigBlueprint.java b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/BulkheadConfigBlueprint.java index 2e31bdaf7f8..7e1d62847c9 100644 --- a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/BulkheadConfigBlueprint.java +++ b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/BulkheadConfigBlueprint.java @@ -21,12 +21,10 @@ import io.helidon.builder.api.Option; import io.helidon.builder.api.Prototype; -import io.helidon.inject.configdriven.api.ConfigBean; /** * {@link Bulkhead} configuration bean. */ -@ConfigBean(repeatable = true) @Prototype.Configured("fault-tolerance.bulkheads") @Prototype.Blueprint(decorator = BulkheadConfigBlueprint.BuilderDecorator.class) interface BulkheadConfigBlueprint extends Prototype.Factory { diff --git a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/BulkheadImpl.java b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/BulkheadImpl.java index 394734f0d02..50d82096a6a 100644 --- a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/BulkheadImpl.java +++ b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/BulkheadImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 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. @@ -32,8 +32,6 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; -import jakarta.inject.Inject; - class BulkheadImpl implements Bulkhead { private static final System.Logger LOGGER = System.getLogger(BulkheadImpl.class.getName()); @@ -48,7 +46,6 @@ class BulkheadImpl implements Bulkhead { private final Set> cancelledSuppliers = new CopyOnWriteArraySet<>(); private final BulkheadConfig config; - @Inject BulkheadImpl(BulkheadConfig config) { this.inProgress = new Semaphore(config.limit(), true); this.name = config.name().orElseGet(() -> "bulkhead-" + System.identityHashCode(config)); diff --git a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/CircuitBreaker.java b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/CircuitBreaker.java index c1b7e62c64b..07004da88a5 100644 --- a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/CircuitBreaker.java +++ b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/CircuitBreaker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 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. @@ -19,7 +19,6 @@ import java.util.function.Consumer; import io.helidon.builder.api.RuntimeType; -import io.helidon.inject.api.Contract; /** * CircuitBreaker protects a potentially failing endpoint from overloading and the application @@ -31,7 +30,6 @@ * and requests can process as usual again. */ @RuntimeType.PrototypedBy(CircuitBreakerConfig.class) -@Contract public interface CircuitBreaker extends FtHandler, RuntimeType.Api { /** * Create a new circuit builder based on its configuration. diff --git a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/CircuitBreakerConfigBlueprint.java b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/CircuitBreakerConfigBlueprint.java index 72d22facdf3..1d59f607d45 100644 --- a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/CircuitBreakerConfigBlueprint.java +++ b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/CircuitBreakerConfigBlueprint.java @@ -23,11 +23,9 @@ import io.helidon.builder.api.Option; import io.helidon.builder.api.Prototype; -import io.helidon.inject.configdriven.api.ConfigBean; @Prototype.Blueprint(decorator = CircuitBreakerConfigBlueprint.BuilderDecorator.class) @Prototype.Configured("fault-tolerance.circuit-breakers") -@ConfigBean(wantDefault = true, repeatable = true) interface CircuitBreakerConfigBlueprint extends Prototype.Factory { int DEFAULT_ERROR_RATIO = 60; int DEFAULT_SUCCESS_THRESHOLD = 1; diff --git a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/CircuitBreakerImpl.java b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/CircuitBreakerImpl.java index 6f1b1a5d714..f5bad10bc16 100644 --- a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/CircuitBreakerImpl.java +++ b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/CircuitBreakerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 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. @@ -23,11 +23,6 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; -import io.helidon.inject.configdriven.api.ConfigDriven; - -import jakarta.inject.Inject; - -@ConfigDriven(CircuitBreakerConfigBlueprint.class) class CircuitBreakerImpl implements CircuitBreaker { /* Configuration options @@ -52,7 +47,6 @@ class CircuitBreakerImpl implements CircuitBreaker { private final String name; private final CircuitBreakerConfig config; - @Inject CircuitBreakerImpl(CircuitBreakerConfig config) { this.delayMillis = config.delay().toMillis(); this.successThreshold = config.successThreshold(); diff --git a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Retry.java b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Retry.java index 3bd5111497c..70f78ad5e0f 100644 --- a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Retry.java +++ b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Retry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 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. @@ -23,12 +23,10 @@ import java.util.function.Supplier; import io.helidon.builder.api.RuntimeType; -import io.helidon.inject.api.Contract; /** * Retry supports retry policies to be applied on an execution of asynchronous tasks. */ -@Contract @RuntimeType.PrototypedBy(RetryConfig.class) public interface Retry extends FtHandler, RuntimeType.Api { /** diff --git a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/RetryImpl.java b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/RetryImpl.java index e1145084243..a6e8c41dfa4 100644 --- a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/RetryImpl.java +++ b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/RetryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 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. @@ -25,8 +25,6 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.function.Supplier; -import jakarta.inject.Inject; - class RetryImpl implements Retry { private final ErrorChecker errorChecker; private final long maxTimeNanos; @@ -35,7 +33,6 @@ class RetryImpl implements Retry { private final AtomicLong retryCounter = new AtomicLong(0L); private final String name; - @Inject RetryImpl(RetryConfig retryConfig) { this.name = retryConfig.name().orElseGet(() -> "retry-" + System.identityHashCode(retryConfig)); this.errorChecker = ErrorChecker.create(retryConfig.skipOn(), retryConfig.applyOn()); diff --git a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Timeout.java b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Timeout.java index f71cf303e1c..008819e826f 100644 --- a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Timeout.java +++ b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/Timeout.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 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. @@ -20,12 +20,10 @@ import java.util.function.Consumer; import io.helidon.builder.api.RuntimeType; -import io.helidon.inject.api.Contract; /** * Timeout attempts to terminate execution after defined duration of time. */ -@Contract @RuntimeType.PrototypedBy(TimeoutConfig.class) public interface Timeout extends FtHandler, RuntimeType.Api { /** diff --git a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/TimeoutImpl.java b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/TimeoutImpl.java index fe784a2cea2..c519bc3431e 100644 --- a/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/TimeoutImpl.java +++ b/fault-tolerance/fault-tolerance/src/main/java/io/helidon/faulttolerance/TimeoutImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 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. @@ -23,8 +23,6 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; -import jakarta.inject.Inject; - class TimeoutImpl implements Timeout { private static final System.Logger LOGGER = System.getLogger(TimeoutImpl.class.getName()); @@ -34,7 +32,6 @@ class TimeoutImpl implements Timeout { private final String name; private final TimeoutConfig config; - @Inject TimeoutImpl(TimeoutConfig config) { this.timeoutMillis = config.timeout().toMillis(); this.executor = config.executor().orElseGet(FaultTolerance.executor()); diff --git a/fault-tolerance/fault-tolerance/src/main/java/module-info.java b/fault-tolerance/fault-tolerance/src/main/java/module-info.java index 28726cffa06..cc6869ef511 100644 --- a/fault-tolerance/fault-tolerance/src/main/java/module-info.java +++ b/fault-tolerance/fault-tolerance/src/main/java/module-info.java @@ -28,23 +28,11 @@ module io.helidon.faulttolerance { requires io.helidon.common; - requires io.helidon.common.types; requires io.helidon.common.configurable; requires io.helidon.config; - requires io.helidon.inject.api; requires io.helidon.builder.api; - requires static jakarta.inject; requires static io.helidon.common.features.api; - requires static io.helidon.inject.configdriven.api; - // needed to compile generated types - requires static io.helidon.inject.configdriven.runtime; - requires static io.helidon.inject.runtime; - exports io.helidon.faulttolerance; - - // inject module - provides io.helidon.inject.api.ModuleComponent with io.helidon.faulttolerance.Injection$$Module; - } diff --git a/inject/tests/resources-inject/src/test/java/io/helidon/inject/tests/inject/tbox/ToolBoxTest.java b/inject/tests/resources-inject/src/test/java/io/helidon/inject/tests/inject/tbox/ToolBoxTest.java index a24b8a7b73b..81e4448d86b 100644 --- a/inject/tests/resources-inject/src/test/java/io/helidon/inject/tests/inject/tbox/ToolBoxTest.java +++ b/inject/tests/resources-inject/src/test/java/io/helidon/inject/tests/inject/tbox/ToolBoxTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -180,14 +180,13 @@ void modules() { List> allModules = services.lookupAll(ModuleComponent.class); List desc = allModules.stream().map(ServiceProvider::description).collect(Collectors.toList()); // note that order matters here - // there is now config module as active as well assertThat("ensure that Annotation Processors are enabled in the tools module meta-inf/services", - desc, contains("Injection$$Module:ACTIVE", "Injection$$Module:ACTIVE", "Injection$$TestModule:ACTIVE")); + desc, contains("Injection$$Module:ACTIVE", "Injection$$TestModule:ACTIVE")); List names = allModules.stream() .sorted() .map(m -> m.get().named().orElse(m.get().getClass().getSimpleName() + ":null")).collect(Collectors.toList()); assertThat(names, - contains("io.helidon.config", "io.helidon.inject.tests.inject", "io.helidon.inject.tests.inject/test")); + contains("io.helidon.inject.tests.inject", "io.helidon.inject.tests.inject/test")); } /** @@ -304,8 +303,7 @@ void startupAndShutdownCallsPostConstructAndPreDestroy() { assertThat(report, hasEntry(create("io.helidon.inject.tests.inject.stacking.OuterCommonContractImpl"), "ACTIVE->DESTROYED")); assertThat(report, hasEntry(create("io.helidon.inject.tests.inject.stacking.CommonContractImpl"), "ACTIVE->DESTROYED")); assertThat(report, hasEntry(create("io.helidon.inject.tests.inject.TestingSingleton"), "ACTIVE->DESTROYED")); - // ConfigProducer is the 9th - assertThat(report + " : expected 9 services to be present", report.size(), equalTo(9)); + assertThat(report + " : expected 8 services to be present", report.size(), equalTo(8)); assertThat(TestingSingleton.postConstructCount(), equalTo(1)); assertThat(TestingSingleton.preDestroyCount(), equalTo(1)); @@ -322,8 +320,7 @@ void startupAndShutdownCallsPostConstructAndPreDestroy() { .collect(Collectors.toMap(Map.Entry::getKey, e2 -> e2.getValue().startingActivationPhase().toString() + "->" + e2.getValue().finishingActivationPhase())); - // now contains config as well - assertThat(report.toString(), report.size(), is(9)); + assertThat(report.toString(), report.size(), is(8)); tearDown(); map = injectionServices.shutdown().orElseThrow(); diff --git a/inject/tests/runtime/src/test/java/io/helidon/inject/runtime/HelloInjectionWorldSanityTest.java b/inject/tests/runtime/src/test/java/io/helidon/inject/runtime/HelloInjectionWorldSanityTest.java index 0661fa3c8db..fbe7f4a90c3 100644 --- a/inject/tests/runtime/src/test/java/io/helidon/inject/runtime/HelloInjectionWorldSanityTest.java +++ b/inject/tests/runtime/src/test/java/io/helidon/inject/runtime/HelloInjectionWorldSanityTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -67,8 +67,7 @@ * Sanity type tests only. The "real" testing is in the tests submodules. */ class HelloInjectionWorldSanityTest { - // helidon-config is now one of the modules - private static final int EXPECTED_MODULES = 3; + private static final int EXPECTED_MODULES = 2; @BeforeEach void setUp() { @@ -96,9 +95,8 @@ void sanity() { assertThat(moduleProviders.size(), equalTo(EXPECTED_MODULES)); List descriptions = ServiceUtils.toDescriptions(moduleProviders); - // helidon-config is now first assertThat(descriptions, - containsInAnyOrder("Injection$$Module:ACTIVE", "EmptyModule:ACTIVE", "HelloInjection$$Module:ACTIVE")); + containsInAnyOrder("EmptyModule:ACTIVE", "HelloInjection$$Module:ACTIVE")); List> applications = services.lookupAll(Application.class); assertThat(applications.size(), diff --git a/tests/integration/native-image/mp-1/src/main/java/module-info.java b/tests/integration/native-image/mp-1/src/main/java/module-info.java index 4c64bf47b48..d99ace56449 100644 --- a/tests/integration/native-image/mp-1/src/main/java/module-info.java +++ b/tests/integration/native-image/mp-1/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 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. @@ -40,9 +40,6 @@ // never reach health check CDI extension requires io.helidon.health.checks; - // needed to compile injection generated classes - requires static io.helidon.inject.runtime; - exports io.helidon.tests.integration.nativeimage.mp1; exports io.helidon.tests.integration.nativeimage.mp1.other; diff --git a/tests/integration/vault/pom.xml b/tests/integration/vault/pom.xml index 677424ab280..a9065651271 100644 --- a/tests/integration/vault/pom.xml +++ b/tests/integration/vault/pom.xml @@ -113,5 +113,10 @@ helidon-common-testing-junit5 test + + io.helidon.logging + helidon-logging-jul + test + \ No newline at end of file diff --git a/webclient/api/src/main/java/io/helidon/webclient/api/LoomClient.java b/webclient/api/src/main/java/io/helidon/webclient/api/LoomClient.java index 8039bb12c46..616ae4918ba 100644 --- a/webclient/api/src/main/java/io/helidon/webclient/api/LoomClient.java +++ b/webclient/api/src/main/java/io/helidon/webclient/api/LoomClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2024 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. @@ -28,20 +28,16 @@ import io.helidon.common.HelidonServiceLoader; import io.helidon.common.LazyValue; import io.helidon.http.Method; -import io.helidon.inject.configdriven.api.ConfigDriven; import io.helidon.webclient.spi.ClientProtocolProvider; import io.helidon.webclient.spi.HttpClientSpi; import io.helidon.webclient.spi.HttpClientSpiProvider; import io.helidon.webclient.spi.Protocol; import io.helidon.webclient.spi.ProtocolConfig; -import jakarta.inject.Inject; - /** * Base class for HTTP implementations of {@link WebClient}. */ @SuppressWarnings("rawtypes") -@ConfigDriven(WebClientConfigBlueprint.class) class LoomClient implements WebClient { static final LazyValue EXECUTOR = LazyValue.create(() -> { return Executors.newThreadPerTaskExecutor(Thread.ofVirtual() @@ -75,7 +71,6 @@ class LoomClient implements WebClient { * @param config builder the subclass is built from */ @SuppressWarnings({"rawtypes", "unchecked"}) - @Inject protected LoomClient(WebClientConfig config) { this.config = config; this.protocolConfigs = ProtocolConfigs.create(config.protocolConfigs()); diff --git a/webclient/api/src/main/java/io/helidon/webclient/api/WebClientConfigBlueprint.java b/webclient/api/src/main/java/io/helidon/webclient/api/WebClientConfigBlueprint.java index f2019e26532..aec75406815 100644 --- a/webclient/api/src/main/java/io/helidon/webclient/api/WebClientConfigBlueprint.java +++ b/webclient/api/src/main/java/io/helidon/webclient/api/WebClientConfigBlueprint.java @@ -20,14 +20,12 @@ import io.helidon.builder.api.Option; import io.helidon.builder.api.Prototype; -import io.helidon.inject.configdriven.api.ConfigBean; import io.helidon.webclient.spi.ProtocolConfig; import io.helidon.webclient.spi.ProtocolConfigProvider; /** * WebClient configuration. */ -@ConfigBean(repeatable = true, wantDefault = true) @Prototype.Blueprint @Prototype.Configured("clients") interface WebClientConfigBlueprint extends HttpClientConfigBlueprint, Prototype.Factory { diff --git a/webclient/api/src/main/java/module-info.java b/webclient/api/src/main/java/module-info.java index 9962fc5e408..70636be3d5a 100644 --- a/webclient/api/src/main/java/module-info.java +++ b/webclient/api/src/main/java/module-info.java @@ -31,9 +31,6 @@ requires static io.helidon.common.features.api; // @Feature requires static io.helidon.config.metadata; // @ConfiguredOption etc - requires static io.helidon.inject.configdriven.api; - requires static io.helidon.inject.configdriven.runtime; - requires static jakarta.inject; // Injection support requires transitive io.helidon.common.config; requires transitive io.helidon.common.configurable; diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/WebServerConfigDrivenTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/WebServerConfigDrivenTest.java deleted file mode 100644 index 5ae961cc649..00000000000 --- a/webserver/webserver/src/test/java/io/helidon/webserver/WebServerConfigDrivenTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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. - */ - -package io.helidon.webserver; - -import io.helidon.config.Config; -import io.helidon.inject.api.Bootstrap; -import io.helidon.inject.api.InjectionServices; -import io.helidon.inject.api.Phase; -import io.helidon.inject.api.ServiceProvider; -import io.helidon.inject.api.Services; -import io.helidon.inject.testing.InjectionTestingSupport; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -@Disabled -class WebServerConfigDrivenTest { - static final boolean NORMAL_PRODUCTION_PATH = false; - - @AfterEach - public void reset() { - if (!NORMAL_PRODUCTION_PATH) { - // requires 'inject.permits-dynamic=true' to be able to reset - InjectionTestingSupport.resetAll(); - } - } - - @Test - void testConfigDriven() { - // This will pick up application.yaml from the classpath as default configuration file - Config config = Config.create(); - - if (NORMAL_PRODUCTION_PATH) { - // bootstrap Injection with our config tree when it initializes - InjectionServices.globalBootstrap(Bootstrap.builder().config(config).build()); - } - - // initialize Injection, and drive all activations based upon what has been configured - Services services; - if (NORMAL_PRODUCTION_PATH) { - services = InjectionServices.realizedServices(); - } else { - InjectionServices injectionServices = InjectionTestingSupport.testableServices(config); - services = injectionServices.services(); - } - - ServiceProvider webServerSp = services.lookupFirst(WebServer.class); - assertThat(webServerSp.currentActivationPhase(), is(Phase.ACTIVE)); - } - -} From 48d6e5788208ae9cfd4985d70107c00c17030407 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 8 May 2024 19:09:31 +0200 Subject: [PATCH 06/20] OCI module based on service registry Refactored OCI TLS on the new module In progress --- all/pom.xml | 4 + bom/pom.xml | 5 + integrations/oci/README.md | 4 + integrations/oci/oci/pom.xml | 112 +++++++++++++++ .../integrations/oci/AtnDetailsProvider.java | 67 +++++++++ .../integrations/oci/AtnStrategyConfig.java | 78 +++++++++++ .../oci/AtnStrategyConfigFile.java | 69 ++++++++++ .../oci/AtnStrategyInstancePrincipal.java | 72 ++++++++++ .../oci/AtnStrategyResourcePrincipal.java | 55 ++++++++ .../ConfigFileStrategyConfigBlueprint.java | 27 ++++ .../oci/ConfigStrategyConfigBlueprint.java | 80 +++++++++++ .../integrations/oci/OciConfigBlueprint.java | 90 ++++++++++++ .../integrations/oci/OciConfigProvider.java | 21 +++ .../integrations/oci/RegionProvider.java | 31 +++++ .../oci/RegionProviderAtnStrategy.java | 35 +++++ .../oci/RegionProviderConfig.java | 29 ++++ .../integrations/oci/SdkRegionProvider.java | 22 +++ .../integrations/oci/spi/OciAtnStrategy.java | 13 ++ .../integrations/oci/spi/OciRegion.java | 12 ++ .../oci/oci/src/main/java/module-info.java | 10 ++ integrations/oci/pom.xml | 1 + .../OciAuthenticationDetailsProvider.java | 2 +- .../oci/sdk/runtime/OciAvailability.java | 2 + .../oci/sdk/runtime/OciExtension.java | 3 + .../runtime/src/main/java/module-info.java | 3 + .../oci/sdk/tests/test-application/pom.xml | 29 ---- .../oci/sdk/tests/test-module1/pom.xml | 14 -- .../oci/sdk/tests/test-module2/pom.xml | 14 -- integrations/oci/tls-certificates/pom.xml | 128 ++++-------------- .../DefaultOciCertificatesDownloader.java | 23 ++-- .../DefaultOciCertificatesTlsManager.java | 43 ++---- ...aultOciCertificatesTlsManagerProvider.java | 1 - .../DefaultOciPrivateKeyDownloader.java | 12 +- .../oci/tls/certificates/LifecycleHook.java | 32 ----- ...CertificatesTlsManagerConfigBlueprint.java | 21 ++- .../spi/OciCertificatesDownloader.java | 4 +- .../spi/OciPrivateKeyDownloader.java | 4 +- .../src/main/java/module-info.java | 11 +- 38 files changed, 926 insertions(+), 257 deletions(-) create mode 100644 integrations/oci/README.md create mode 100644 integrations/oci/oci/pom.xml create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnDetailsProvider.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfig.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfigFile.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyResourcePrincipal.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigFileStrategyConfigBlueprint.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigStrategyConfigBlueprint.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigProvider.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProvider.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderAtnStrategy.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderConfig.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/SdkRegionProvider.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciAtnStrategy.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciRegion.java create mode 100644 integrations/oci/oci/src/main/java/module-info.java delete mode 100644 integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/LifecycleHook.java diff --git a/all/pom.xml b/all/pom.xml index 50a532aa89e..c8c4daa4b15 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -641,6 +641,10 @@ io.helidon.integrations.micrometer helidon-integrations-micrometer-cdi + + io.helidon.integrations.oci + helidon-integrations-oci + io.helidon.integrations.oci.sdk helidon-integrations-oci-sdk-cdi diff --git a/bom/pom.xml b/bom/pom.xml index 324eda72f5a..cb053fa4c05 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -858,6 +858,11 @@ helidon-integrations-micrometer-cdi ${helidon.version} + + io.helidon.integrations.oci + helidon-integrations-oci + ${helidon.version} + io.helidon.integrations.oci.sdk helidon-integrations-oci-sdk-cdi diff --git a/integrations/oci/README.md b/integrations/oci/README.md new file mode 100644 index 00000000000..f418bbb88cd --- /dev/null +++ b/integrations/oci/README.md @@ -0,0 +1,4 @@ +# OCI Integration + +Modules for integrating Oracle Cloud Infrastructure features. + diff --git a/integrations/oci/oci/pom.xml b/integrations/oci/oci/pom.xml new file mode 100644 index 00000000000..17f03d135d4 --- /dev/null +++ b/integrations/oci/oci/pom.xml @@ -0,0 +1,112 @@ + + + + 4.0.0 + + io.helidon.integrations.oci + helidon-integrations-oci-project + 4.0.0-SNAPSHOT + + helidon-integrations-oci + Helidon Integrations OCI + + + Common tools for OCI integration. + + + + + io.helidon.service + helidon-service-registry + + + io.helidon.common + helidon-common-configurable + + + com.oracle.oci.sdk + oci-java-sdk-common + + + org.hamcrest + hamcrest-all + test + + + org.junit.jupiter + junit-jupiter-api + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.helidon.codegen + helidon-codegen-apt + ${helidon.version} + + + io.helidon.builder + helidon-builder-codegen + ${helidon.version} + + + io.helidon.service + helidon-service-codegen + ${helidon.version} + + + io.helidon.codegen + helidon-codegen-helidon-copyright + ${helidon.version} + + + + + + io.helidon.codegen + helidon-codegen-apt + ${helidon.version} + + + io.helidon.builder + helidon-builder-codegen + ${helidon.version} + + + io.helidon.service + helidon-service-codegen + ${helidon.version} + + + io.helidon.codegen + helidon-codegen-helidon-copyright + ${helidon.version} + + + + + + diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnDetailsProvider.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnDetailsProvider.java new file mode 100644 index 00000000000..cb8598f6b11 --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnDetailsProvider.java @@ -0,0 +1,67 @@ +package io.helidon.integrations.oci; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import io.helidon.common.LazyValue; +import io.helidon.common.config.ConfigException; +import io.helidon.integrations.oci.spi.OciAtnStrategy; +import io.helidon.service.registry.Service; + +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; + +@Service.Provider +@Service.ExternalContracts(AbstractAuthenticationDetailsProvider.class) +class AtnDetailsProvider implements Supplier { + + private final LazyValue provider; + + AtnDetailsProvider(OciConfig ociConfig, List atnDetailsProviders) { + String chosenStrategy = ociConfig.atnStrategy(); + LazyValue providerLazyValue = null; + + if (OciConfigBlueprint.STRATEGY_AUTO.equals(chosenStrategy)) { + // auto, chose from existing + providerLazyValue = LazyValue.create(() -> { + List strategies = new ArrayList<>(); + for (OciAtnStrategy atnDetailsProvider : atnDetailsProviders) { + Optional provider = atnDetailsProvider.provider(); + if (provider.isPresent()) { + return provider.get(); + } + strategies.add(atnDetailsProvider.strategy()); + } + throw new RuntimeException("Cannot discover OCI Authentication Details Provider, none of the strategies" + + " returned a valid provider. Supported strategies: " + strategies); + }); + } else { + List strategies = new ArrayList<>(); + + for (OciAtnStrategy atnDetailsProvider : atnDetailsProviders) { + if (chosenStrategy.equals(atnDetailsProvider.strategy())) { + providerLazyValue = LazyValue.create(() -> atnDetailsProvider.provider().orElseThrow(() -> + new ConfigException( + "Strategy \"" + chosenStrategy + "\" did not provide an authentication provider, " + + "yet it is requested through configuration."))); + break; + } + strategies.add(atnDetailsProvider.strategy()); + } + + if (providerLazyValue == null) { + throw new ConfigException("There is a strategy chosen for OCI authentication: \"" + chosenStrategy + + "\", yet there is not provider that can provide that strategy. Supported " + + "strategies: " + strategies); + } + } + + this.provider = providerLazyValue; + } + + @Override + public AbstractAuthenticationDetailsProvider get() { + return provider.get(); + } +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfig.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfig.java new file mode 100644 index 00000000000..11c8830acab --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfig.java @@ -0,0 +1,78 @@ +package io.helidon.integrations.oci; + +import java.util.Optional; + +import io.helidon.common.LazyValue; +import io.helidon.common.Weight; +import io.helidon.common.Weighted; +import io.helidon.common.configurable.Resource; +import io.helidon.integrations.oci.spi.OciAtnStrategy; +import io.helidon.service.registry.Service; + +import com.oracle.bmc.Region; +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; +import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider; +import com.oracle.bmc.auth.SimplePrivateKeySupplier; + +/** + * Config based authentication strategy, uses the {@link com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider}. + */ +@Weight(Weighted.DEFAULT_WEIGHT - 10) +@Service.Provider +class AtnStrategyConfig implements OciAtnStrategy { + static final String STRATEGY = "config"; + + private final LazyValue> provider; + + AtnStrategyConfig(OciConfig config) { + provider = config.configStrategyConfig() + .map(configStrategyConfigBlueprint -> LazyValue.create(() -> { + return Optional.of(createProvider(configStrategyConfigBlueprint)); + })) + .orElseGet(() -> LazyValue.create(Optional.empty())); + } + + @Override + public String strategy() { + return STRATEGY; + } + + @Override + public Optional provider() { + return provider.get(); + } + + private static AbstractAuthenticationDetailsProvider createProvider(ConfigStrategyConfigBlueprint config) { + Region region = Region.fromRegionCodeOrId(config.region()); + + var builder = SimpleAuthenticationDetailsProvider.builder(); + + // private key may be provided through different means + if (config.privateKey().isPresent()) { + // as a resource (classpath, file system, base64, plain text) + Resource resource = config.privateKey().get(); + builder.privateKeySupplier(resource::stream); + } else { + // or as the default location in user.home/.oci/oic_api_key.pem + String keyFile = System.getProperty("user.home"); + if (keyFile == null) { + keyFile = "/"; + } else { + if (!keyFile.endsWith("/")) { + keyFile = keyFile + "/"; + } + } + keyFile = keyFile + ".oci/oci_api_key.pem"; + + builder.privateKeySupplier(new SimplePrivateKeySupplier(keyFile)); + } + + return builder.region(region) + .tenantId(config.tenantId()) + .userId(config.userId()) + .fingerprint(config.fingerprint()) + .passphraseCharacters(config.passphrase()) + .build(); + + } +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfigFile.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfigFile.java new file mode 100644 index 00000000000..b54d67cc0be --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfigFile.java @@ -0,0 +1,69 @@ +package io.helidon.integrations.oci; + +import java.io.IOException; +import java.lang.System.Logger.Level; +import java.util.Optional; + +import io.helidon.common.LazyValue; +import io.helidon.common.Weight; +import io.helidon.common.Weighted; +import io.helidon.integrations.oci.spi.OciAtnStrategy; +import io.helidon.service.registry.Service; + +import com.oracle.bmc.ConfigFileReader; +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; +import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider; + +/** + * Config file based authentication strategy, uses the {@link com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider}. + */ +@Weight(Weighted.DEFAULT_WEIGHT - 20) +@Service.Provider +class AtnStrategyConfigFile implements OciAtnStrategy { + static final String DEFAULT_PROFILE_NAME = "DEFAULT"; + static final String STRATEGY = "config_file"; + + private static final System.Logger LOGGER = System.getLogger(AtnStrategyConfigFile.class.getName()); + + private final LazyValue> provider; + + AtnStrategyConfigFile(OciConfig config) { + provider = createProvider(config); + } + + @Override + public String strategy() { + return STRATEGY; + } + + @Override + public Optional provider() { + return provider.get(); + } + + private static LazyValue> createProvider(OciConfig config) { + return LazyValue.create(() -> { + // there are two options to override - the path to config file, and the profile + var strategyConfig = config.configFileStrategyConfig(); + String profile = strategyConfig.map(ConfigFileStrategyConfigBlueprint::profile) + .orElse(DEFAULT_PROFILE_NAME); + String configFilePath = strategyConfig.flatMap(ConfigFileStrategyConfigBlueprint::path) + .orElse(null); + + try { + ConfigFileReader.ConfigFile configFile; + if (configFilePath == null) { + configFile = ConfigFileReader.parseDefault(profile); + } else { + configFile = ConfigFileReader.parse(configFilePath, profile); + } + return Optional.of(new ConfigFileAuthenticationDetailsProvider(configFile)); + } catch (IOException e) { + if (LOGGER.isLoggable(Level.TRACE)) { + LOGGER.log(Level.TRACE, "Cannot parse config file. Location: " + configFilePath + ", profile: " + profile, e); + } + return Optional.empty(); + } + }); + } +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java new file mode 100644 index 00000000000..9d9f2fa6c54 --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java @@ -0,0 +1,72 @@ +package io.helidon.integrations.oci; + +import java.io.IOException; +import java.lang.System.Logger.Level; +import java.net.InetAddress; +import java.time.Duration; +import java.util.Optional; + +import io.helidon.common.LazyValue; +import io.helidon.common.Weight; +import io.helidon.common.Weighted; +import io.helidon.integrations.oci.spi.OciAtnStrategy; +import io.helidon.service.registry.Service; + +import com.oracle.bmc.Region; +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; +import com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider; + +/** + * Config file based authentication strategy, uses the {@link com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider}. + */ +@Weight(Weighted.DEFAULT_WEIGHT - 40) +@Service.Provider +class AtnStrategyInstancePrincipal implements OciAtnStrategy { + static final String STRATEGY = "instance-principal"; + + private static final System.Logger LOGGER = System.getLogger(AtnStrategyInstancePrincipal.class.getName()); + + private final LazyValue> provider; + + AtnStrategyInstancePrincipal(OciConfig config) { + provider = createProvider(config); + } + + @Override + public String strategy() { + return STRATEGY; + } + + @Override + public Optional provider() { + return provider.get(); + } + + private static LazyValue> createProvider(OciConfig config) { + return LazyValue.create(() -> { + if (imdsVailable(config)) { + return Optional.of(InstancePrincipalsAuthenticationDetailsProvider.builder().build()); + } + return Optional.empty(); + }); + } + + private static boolean imdsVailable(OciConfig config) { + String imdsAddress = config.imdsAddress(); + Duration timeout = config.imdsTimeout(); + + try { + if (InetAddress.getByName(imdsAddress) + .isReachable((int) timeout.toMillis())) { + return Region.getRegionFromImds("http://" + imdsAddress + "/opc/v2") != null; + } + return false; + } catch (IOException e) { + LOGGER.log(Level.TRACE, + "imds service is not reachable, or timed out for address: " + imdsAddress + ", instance principal " + + "strategy is not available.", + e); + return false; + } + } +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyResourcePrincipal.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyResourcePrincipal.java new file mode 100644 index 00000000000..7891892f29e --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyResourcePrincipal.java @@ -0,0 +1,55 @@ +package io.helidon.integrations.oci; + +import java.lang.System.Logger.Level; +import java.util.Optional; + +import io.helidon.common.LazyValue; +import io.helidon.common.Weight; +import io.helidon.common.Weighted; +import io.helidon.integrations.oci.spi.OciAtnStrategy; +import io.helidon.service.registry.Service; + +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; +import com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider; + +/** + * Config file based authentication strategy, uses the {@link com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider}. + */ +@Weight(Weighted.DEFAULT_WEIGHT - 30) +@Service.Provider +class AtnStrategyResourcePrincipal implements OciAtnStrategy { + static final String RESOURCE_PRINCIPAL_VERSION_ENV_VAR = "OCI_RESOURCE_PRINCIPAL_VERSION"; + static final String STRATEGY = "resource-principal"; + + private static final System.Logger LOGGER = System.getLogger(AtnStrategyResourcePrincipal.class.getName()); + + private final LazyValue> provider; + + AtnStrategyResourcePrincipal(OciConfig config) { + provider = createProvider(config); + } + + @Override + public String strategy() { + return STRATEGY; + } + + @Override + public Optional provider() { + return provider.get(); + } + + private static LazyValue> createProvider(OciConfig config) { + return LazyValue.create(() -> { + // https://github.com/oracle/oci-java-sdk/blob/v2.19.0/bmc-common/src/main/java/com/oracle/bmc/auth/ResourcePrincipalAuthenticationDetailsProvider.java#L246-L251 + if (System.getenv(RESOURCE_PRINCIPAL_VERSION_ENV_VAR) == null) { + if (LOGGER.isLoggable(Level.TRACE)) { + LOGGER.log(Level.TRACE, "Environment variable \"" + RESOURCE_PRINCIPAL_VERSION_ENV_VAR + + "\" is not set, resource principal cannot be used."); + } + return Optional.empty(); + } + return Optional.of(ResourcePrincipalAuthenticationDetailsProvider.builder().build()); + }); + } +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigFileStrategyConfigBlueprint.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigFileStrategyConfigBlueprint.java new file mode 100644 index 00000000000..9c0af884697 --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigFileStrategyConfigBlueprint.java @@ -0,0 +1,27 @@ +package io.helidon.integrations.oci; + +import java.util.Optional; + +import io.helidon.builder.api.Option; +import io.helidon.builder.api.Prototype; + +@Prototype.Blueprint +@Prototype.Configured +interface ConfigFileStrategyConfigBlueprint { + /** + * The OCI configuration profile path. + * + * @return the OCI configuration profile path + */ + @Option.Configured + Optional path(); + + /** + * The OCI configuration/auth profile name. + * + * @return the optional OCI configuration/auth profile name + */ + @Option.Configured + @Option.Default(AtnStrategyConfigFile.DEFAULT_PROFILE_NAME) + String profile(); +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigStrategyConfigBlueprint.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigStrategyConfigBlueprint.java new file mode 100644 index 00000000000..ef4f44ed5b8 --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigStrategyConfigBlueprint.java @@ -0,0 +1,80 @@ +package io.helidon.integrations.oci; + +import java.util.Optional; + +import io.helidon.builder.api.Option; +import io.helidon.builder.api.Prototype; +import io.helidon.common.configurable.Resource; + +/** + * Configuration of the {@code config} authentication strategy. + */ +@Prototype.Blueprint +@Prototype.Configured +interface ConfigStrategyConfigBlueprint { + /** + * The OCI region. + * + * @return the OCI region + */ + @Option.Configured + String region(); + + /** + * The OCI authentication fingerprint. + *

    + * This configuration property must be provided in order to set the API signing key's fingerprint. + * See {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getFingerprint()} for more details. + * + * @return the OCI authentication fingerprint + */ + @Option.Configured + String fingerprint(); + + /** + * The OCI authentication private key resource. + * A resource can be defined as a resource on classpath, file on the file system, + * base64 encoded text value in config, or plain-text value in config. + *

    + * If not defined, we will use {@code .oci/oic_api_key.pem} file in user home directory. + * + * @return the OCI authentication key file + */ + @Option.Configured + Optional privateKey(); + + /** + * The OCI authentication passphrase. + *

    + * This property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getPassphraseCharacters()}. + * + * @return the OCI authentication passphrase + */ + @Option.Configured + @Option.Confidential + char[] passphrase(); + + /** + * The OCI tenant id. + *

    + * This property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getTenantId()}. + * + * @return the OCI tenant id + */ + @Option.Configured + String tenantId(); + + /** + * The OCI user id. + *

    + * This property must be provided in order to set the + * {@linkplain com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider#getUserId()}. + * + * @return the OCI user id + */ + @Option.Configured + String userId(); +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java new file mode 100644 index 00000000000..d3ef52a5264 --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java @@ -0,0 +1,90 @@ +package io.helidon.integrations.oci; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; + +import io.helidon.builder.api.Option; +import io.helidon.builder.api.Prototype; + +@Prototype.Configured("oci") +@Prototype.Blueprint +interface OciConfigBlueprint { + String STRATEGY_AUTO = "auto"; + String IMDS_ADDRESS = "169.254.169.254"; + + /** + * Authentication strategy to use. If the configured strategy is not available, an exception + * would be thrown for OCI related services. + *

    + * Known and supported authentication strategies for public OCI: + *

      + *
    • {@value #STRATEGY_AUTO} - use the list of {@link #allowedAtnStrategies()} (in the provided order), and choose + * the first one + * capable of providing data
    • + *
    • {@value AtnStrategyConfig#STRATEGY} - + * use configuration of the application to obtain values needed to set up connectivity, uses + * {@link com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider}
    • + *
    • {@value AtnStrategyConfigFile#STRATEGY} - use configuration file of OCI ({@code home/.oci/config}), uses + * {@link com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider}
    • + *
    • {@value AtnStrategyResourcePrincipal#STRATEGY} - use identity of the OCI resource the service is executed on (fn), uses + * {@link com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider}
    • + *
    • {@value AtnStrategyInstancePrincipal#STRATEGY} - use identity of the OCI instance the service is running on, uses + * {@link com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider}
    • + *
    + * + * @return the authentication strategy to apply + */ + @Option.Configured + @Option.Default("auto") + String atnStrategy(); + + /** + * List of attempted authentication strategies in case {@link #atnStrategy()} is set to {@value #STRATEGY_AUTO}. + *

    + * In case the list is empty, all available strategies will be tried, ordered by their {@link io.helidon.common.Weight} + * + * @return list of authentication strategies to be tried + * @see #atnStrategy() + */ + @Option.Configured + List allowedAtnStrategies(); + + /** + * Config strategy configuration (if provided and used). + * + * @return information needed for config {@link #atnStrategy()} + */ + @Option.Configured("config-strategy") + Optional configStrategyConfig(); + + /** + * Config file strategy configuration (if provided and used). + * + * @return information to customize config for {@link #atnStrategy()} + */ + @Option.Configured("config-file-strategy") + Optional configFileStrategyConfig(); + + /** + * The OCI IMDS address or hostname. + *

    + * This configuration property is used to identify the metadata service url. + * + * @return the OCI IMDS hostname + */ + @Option.Configured + @Option.Default(IMDS_ADDRESS) + String imdsAddress(); + + /** + * The OCI IMDS connection timeout. This is used to auto-detect availability. + *

    + * This configuration property is used when attempting to connect to the metadata service. + * + * @return the OCI IMDS connection timeout + */ + @Option.Configured + @Option.Default("PT0.1S") + Duration imdsTimeout(); +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigProvider.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigProvider.java new file mode 100644 index 00000000000..80cff6555da --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigProvider.java @@ -0,0 +1,21 @@ +package io.helidon.integrations.oci; + +import java.util.function.Supplier; + +import io.helidon.common.config.Config; +import io.helidon.service.registry.Service; + +@Service.Provider +class OciConfigProvider implements Supplier { + private final OciConfig ociConfig; + + OciConfigProvider(Config config) { + this.ociConfig = config.get("oci").map(OciConfig::create) + .orElseGet(OciConfig::create); + } + + @Override + public OciConfig get() { + return ociConfig; + } +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProvider.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProvider.java new file mode 100644 index 00000000000..d793ce3ef35 --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProvider.java @@ -0,0 +1,31 @@ +package io.helidon.integrations.oci; + +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import io.helidon.integrations.oci.spi.OciRegion; +import io.helidon.service.registry.Service; + +import com.oracle.bmc.Region; + +@Service.Provider +@Service.ExternalContracts(Region.class) +class RegionProvider implements Supplier { + private final List regionProviders; + + RegionProvider(List regionProviders) { + this.regionProviders = regionProviders; + } + + @Override + public Region get() { + for (OciRegion regionProvider : regionProviders) { + Optional region = regionProvider.region(); + if (region.isPresent()) { + return region.get(); + } + } + throw new RuntimeException("Cannot discover OCI region"); + } +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderAtnStrategy.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderAtnStrategy.java new file mode 100644 index 00000000000..5f60294beae --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderAtnStrategy.java @@ -0,0 +1,35 @@ +package io.helidon.integrations.oci; + +import java.util.Optional; +import java.util.function.Supplier; + +import io.helidon.common.LazyValue; +import io.helidon.common.Weight; +import io.helidon.common.Weighted; +import io.helidon.integrations.oci.spi.OciRegion; +import io.helidon.service.registry.Service; + +import com.oracle.bmc.Region; +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; + +@Service.Provider +@Weight(Weighted.DEFAULT_WEIGHT - 20) +class RegionProviderAtnStrategy implements OciRegion { + private final LazyValue> region; + + RegionProviderAtnStrategy(Supplier atnProvider) { + + this.region = LazyValue.create(() -> { + var provider = atnProvider.get(); + if (provider instanceof RegionProvider regionProvider) { + return Optional.of(regionProvider.get()); + } + return Optional.empty(); + }); + } + + @Override + public Optional region() { + return region.get(); + } +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderConfig.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderConfig.java new file mode 100644 index 00000000000..3d9139019dd --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderConfig.java @@ -0,0 +1,29 @@ +package io.helidon.integrations.oci; + +import java.util.Optional; + +import io.helidon.common.LazyValue; +import io.helidon.common.Weight; +import io.helidon.common.Weighted; +import io.helidon.common.config.Config; +import io.helidon.integrations.oci.spi.OciRegion; +import io.helidon.service.registry.Service; + +import com.oracle.bmc.Region; + +@Service.Provider +@Weight(Weighted.DEFAULT_WEIGHT - 10) +class RegionProviderConfig implements OciRegion { + private final LazyValue> region; + + RegionProviderConfig(Config config) { + this.region = LazyValue.create(() -> config.get("oci.region") + .asString() + .map(Region::fromRegionCodeOrId)); + } + + @Override + public Optional region() { + return region.get(); + } +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/SdkRegionProvider.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/SdkRegionProvider.java new file mode 100644 index 00000000000..9ab66543e16 --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/SdkRegionProvider.java @@ -0,0 +1,22 @@ +package io.helidon.integrations.oci; + +import java.util.Optional; + +import io.helidon.common.LazyValue; +import io.helidon.common.Weight; +import io.helidon.common.Weighted; +import io.helidon.integrations.oci.spi.OciRegion; +import io.helidon.service.registry.Service; + +import com.oracle.bmc.Region; + +@Service.Provider +@Weight(Weighted.DEFAULT_WEIGHT - 100) +class SdkRegionProvider implements OciRegion { + private final LazyValue> region = LazyValue.create(() -> Optional.ofNullable(Region.getRegionFromImds())); + + @Override + public Optional region() { + return region.get(); + } +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciAtnStrategy.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciAtnStrategy.java new file mode 100644 index 00000000000..b39ac6a8939 --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciAtnStrategy.java @@ -0,0 +1,13 @@ +package io.helidon.integrations.oci.spi; + +import java.util.Optional; + +import io.helidon.service.registry.Service; + +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; + +@Service.Contract +public interface OciAtnStrategy { + String strategy(); + Optional provider(); +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciRegion.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciRegion.java new file mode 100644 index 00000000000..51296f6db17 --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciRegion.java @@ -0,0 +1,12 @@ +package io.helidon.integrations.oci.spi; + +import java.util.Optional; + +import io.helidon.service.registry.Service; + +import com.oracle.bmc.Region; + +@Service.Contract +public interface OciRegion { + Optional region(); +} diff --git a/integrations/oci/oci/src/main/java/module-info.java b/integrations/oci/oci/src/main/java/module-info.java new file mode 100644 index 00000000000..64f1477ae96 --- /dev/null +++ b/integrations/oci/oci/src/main/java/module-info.java @@ -0,0 +1,10 @@ +module io.helidon.integrations.oci { + requires io.helidon.common.configurable; + requires io.helidon.service.registry; + requires oci.java.sdk.common; + requires io.helidon.common.config; + requires org.bouncycastle.util; + + exports io.helidon.integrations.oci; + exports io.helidon.integrations.oci.spi; +} diff --git a/integrations/oci/pom.xml b/integrations/oci/pom.xml index 98bf17a7e7d..4c76d5d5817 100644 --- a/integrations/oci/pom.xml +++ b/integrations/oci/pom.xml @@ -33,6 +33,7 @@ pom + oci metrics oci-secrets-config-source oci-secrets-mp-config-source diff --git a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAuthenticationDetailsProvider.java b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAuthenticationDetailsProvider.java index 8df6c824070..1c0ead3bbcd 100644 --- a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAuthenticationDetailsProvider.java +++ b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAuthenticationDetailsProvider.java @@ -205,7 +205,7 @@ enum AuthStrategy { */ CONFIG_FILE(VAL_CONFIG_FILE, ConfigFileAuthenticationDetailsProvider.class, - (configBean) -> configBean.fileConfigIsPresent() + (configBean) -> configBean.fileConfigIsPresent() && (configBean.configPath().isEmpty() || canReadPath(configBean.configPath().orElse(null))), (configBean) -> { // https://github.com/oracle/oci-java-sdk/blob/master/bmc-common/src/main/java/com/oracle/bmc/auth/ConfigFileAuthenticationDetailsProvider.java diff --git a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAvailability.java b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAvailability.java index ed4a3872793..9cd8323eff0 100644 --- a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAvailability.java +++ b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAvailability.java @@ -22,8 +22,10 @@ * Provides a convenient contract for checking whether the current runtime environment is running on/inside an OCI compute node. * * @see OciExtension + * @deprecated replaced with {@code helidon-integrations-oci} module */ @Contract +@Deprecated(forRemoval = true, since = "4.1.0") public interface OciAvailability { /** diff --git a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciExtension.java b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciExtension.java index 1c704bde419..9248e9fca73 100644 --- a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciExtension.java +++ b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciExtension.java @@ -118,7 +118,10 @@ * @see Oracle Cloud Infrastructure Java SDK + * + * @deprecated replaced with {@code helidon-integrations-oci} module */ +@Deprecated(forRemoval = true, since = "4.1.0") public final class OciExtension { /** * The name for the OCI bootstrap configuration file (value = {@value}). diff --git a/integrations/oci/sdk/runtime/src/main/java/module-info.java b/integrations/oci/sdk/runtime/src/main/java/module-info.java index 3cc42c3c7d0..4648b9fbee4 100644 --- a/integrations/oci/sdk/runtime/src/main/java/module-info.java +++ b/integrations/oci/sdk/runtime/src/main/java/module-info.java @@ -16,7 +16,10 @@ /** * Helidon Injection Integrations to support OCI Runtime module. + * + * @deprecated replaced with {@code helidon-integrations-oci} module */ +@Deprecated(forRemoval = true, since = "4.1.0") module io.helidon.integrations.oci.sdk.runtime { requires io.helidon.builder.api; diff --git a/integrations/oci/sdk/tests/test-application/pom.xml b/integrations/oci/sdk/tests/test-application/pom.xml index e4423eb08f1..067ad22dc27 100644 --- a/integrations/oci/sdk/tests/test-application/pom.xml +++ b/integrations/oci/sdk/tests/test-application/pom.xml @@ -113,35 +113,6 @@ - - io.helidon.inject - helidon-inject-maven-plugin - ${helidon.version} - - - compile - compile - - application-create - - - - testCompile - test-compile - - test-application-create - - - - - - - - - - - - diff --git a/integrations/oci/sdk/tests/test-module1/pom.xml b/integrations/oci/sdk/tests/test-module1/pom.xml index 2bac814fdb7..14d01da15c9 100644 --- a/integrations/oci/sdk/tests/test-module1/pom.xml +++ b/integrations/oci/sdk/tests/test-module1/pom.xml @@ -40,20 +40,6 @@ io.helidon.integrations.oci.sdk helidon-integrations-oci-sdk-runtime - - io.helidon.inject - helidon-inject-runtime - - - jakarta.annotation - jakarta.annotation-api - provided - - - jakarta.inject - jakarta.inject-api - provided - io.helidon.common.testing helidon-common-testing-junit5 diff --git a/integrations/oci/sdk/tests/test-module2/pom.xml b/integrations/oci/sdk/tests/test-module2/pom.xml index c942c4dbf6c..4366cdfe007 100644 --- a/integrations/oci/sdk/tests/test-module2/pom.xml +++ b/integrations/oci/sdk/tests/test-module2/pom.xml @@ -40,20 +40,6 @@ io.helidon.integrations.oci.sdk helidon-integrations-oci-sdk-runtime - - io.helidon.inject - helidon-inject-runtime - - - jakarta.annotation - jakarta.annotation-api - provided - - - jakarta.inject - jakarta.inject-api - provided - io.helidon.common.testing helidon-common-testing-junit5 diff --git a/integrations/oci/tls-certificates/pom.xml b/integrations/oci/tls-certificates/pom.xml index 4b64b407263..bfd2018dd1e 100644 --- a/integrations/oci/tls-certificates/pom.xml +++ b/integrations/oci/tls-certificates/pom.xml @@ -55,25 +55,14 @@ com.oracle.oci.sdk oci-java-sdk-vault - - jakarta.inject - jakarta.inject-api - true - - - jakarta.annotation - jakarta.annotation-api - true - - - io.helidon.config - helidon-config-metadata - true - io.helidon.builder helidon-builder-api + + io.helidon.service + helidon-service-registry + io.helidon.common helidon-common-tls @@ -82,25 +71,17 @@ io.helidon.common helidon-common-key-util - - io.helidon.integrations.oci.sdk - helidon-integrations-oci-sdk-runtime - io.helidon.fault-tolerance helidon-fault-tolerance - - io.helidon.config - helidon-config-yaml - io.helidon.scheduling helidon-scheduling - io.helidon.inject.configdriven - helidon-inject-configdriven-runtime + io.helidon.integrations.oci + helidon-integrations-oci @@ -109,7 +90,6 @@ oci-java-sdk-common-httpclient-jersey3 test - io.helidon.common.testing helidon-common-testing-junit5 @@ -125,11 +105,6 @@ junit-jupiter-api test - - io.helidon.microprofile.cdi - helidon-microprofile-cdi - test - io.helidon.microprofile.testing helidon-microprofile-testing-junit5 @@ -148,82 +123,39 @@ org.apache.maven.plugins maven-compiler-plugin - - - default-testCompile - - testCompile - - - true - - -Ainject.ignoreUnsupportedAnnotations=true - - - - io.helidon.inject.configdriven - helidon-inject-configdriven-processor - ${helidon.version} - - - io.helidon.builder - helidon-builder-processor - ${helidon.version} - - - io.helidon.common.processor - helidon-common-processor-helidon-copyright - ${helidon.version} - - - io.helidon.config - helidon-config-metadata-processor - ${helidon.version} - - - - - - default-compile - - compile - - - true - - - io.helidon.inject.configdriven - helidon-inject-configdriven-processor - ${helidon.version} - - - io.helidon.builder - helidon-builder-processor - ${helidon.version} - - - io.helidon.common.processor - helidon-common-processor-helidon-copyright - ${helidon.version} - - - - - + + + + io.helidon.builder + helidon-builder-codegen + ${helidon.version} + + + io.helidon.codegen + helidon-codegen-helidon-copyright + ${helidon.version} + + + io.helidon.service + helidon-service-codegen + ${helidon.version} + + + io.helidon.builder - helidon-builder-processor + helidon-builder-codegen ${helidon.version} - io.helidon.inject.configdriven - helidon-inject-configdriven-processor + io.helidon.codegen + helidon-codegen-helidon-copyright ${helidon.version} - io.helidon.common.processor - helidon-common-processor-helidon-copyright + io.helidon.service + helidon-service-codegen ${helidon.version} diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesDownloader.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesDownloader.java index 1c29ab89e3c..6b96965a0ba 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesDownloader.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesDownloader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -27,23 +27,28 @@ import java.util.Objects; import io.helidon.common.pki.PemReader; -import io.helidon.integrations.oci.sdk.runtime.OciExtension; import io.helidon.integrations.oci.tls.certificates.spi.OciCertificatesDownloader; +import io.helidon.service.registry.Service; +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; import com.oracle.bmc.certificates.CertificatesClient; import com.oracle.bmc.certificates.requests.GetCertificateAuthorityBundleRequest; import com.oracle.bmc.certificates.requests.GetCertificateBundleRequest; import com.oracle.bmc.certificates.responses.GetCertificateAuthorityBundleResponse; import com.oracle.bmc.certificates.responses.GetCertificateBundleResponse; -import jakarta.inject.Singleton; import static io.helidon.integrations.oci.tls.certificates.spi.OciCertificatesDownloader.create; /** * Implementation of the {@link OciCertificatesDownloader} that will use OCI's Certificates Service to download certs. */ -@Singleton +@Service.Provider class DefaultOciCertificatesDownloader implements OciCertificatesDownloader { + private final AbstractAuthenticationDetailsProvider authProvider; + + DefaultOciCertificatesDownloader(AbstractAuthenticationDetailsProvider authProvider) { + this.authProvider = authProvider; + } @Override public Certificates loadCertificates(String certOcid) { @@ -65,9 +70,9 @@ public X509Certificate loadCACertificate(String caCertOcid) { } } - static Certificates loadCerts(String certOcid) { + Certificates loadCerts(String certOcid) { try (CertificatesClient client = CertificatesClient.builder() - .build(OciExtension.ociAuthenticationProvider().get())) { + .build(authProvider)) { GetCertificateBundleResponse res = client.getCertificateBundle(GetCertificateBundleRequest.builder() .certificateId(certOcid) @@ -82,10 +87,10 @@ static Certificates loadCerts(String certOcid) { } } - static X509Certificate loadCACert(String caCertOcid) { + X509Certificate loadCACert(String caCertOcid) { GetCertificateAuthorityBundleResponse res; try (CertificatesClient client = CertificatesClient.builder() - .build(OciExtension.ociAuthenticationProvider().get())) { + .build(authProvider)) { res = client.getCertificateAuthorityBundle(GetCertificateAuthorityBundleRequest.builder() .certificateAuthorityId(caCertOcid) .build()); @@ -109,7 +114,7 @@ static X509Certificate toCertificate(InputStream certIs) { if (certs.size() != 1) { throw new IllegalStateException("Expected a single certificate in stream but found: " + certs.size()); } - return certs.get(0); + return certs.getFirst(); } // use the eTag, defaulting to the hash of the certs if not present diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManager.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManager.java index bf3fd52cf11..10a9440e363 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManager.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManager.java @@ -27,6 +27,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; @@ -36,14 +37,10 @@ import io.helidon.common.tls.ConfiguredTlsManager; import io.helidon.common.tls.TlsConfig; import io.helidon.config.Config; -import io.helidon.faulttolerance.Async; -import io.helidon.inject.api.InjectionServices; -import io.helidon.inject.api.ServiceProvider; -import io.helidon.inject.api.Services; import io.helidon.integrations.oci.tls.certificates.spi.OciCertificatesDownloader; import io.helidon.integrations.oci.tls.certificates.spi.OciPrivateKeyDownloader; - -import jakarta.inject.Provider; +import io.helidon.service.registry.GlobalServiceRegistry; +import io.helidon.service.registry.ServiceRegistry; /** * The default implementation (service loader and provider-driven) implementation of {@link OciCertificatesTlsManager}. @@ -57,11 +54,8 @@ class DefaultOciCertificatesTlsManager extends ConfiguredTlsManager implements O private final OciCertificatesTlsManagerConfig cfg; private final AtomicReference lastVersionDownloaded = new AtomicReference<>(""); - // these will only be non-null when enabled - private Provider pkDownloader; - private Provider certDownloader; - private ScheduledExecutorService asyncExecutor; - private Async async; + private Supplier pkDownloader; + private Supplier certDownloader; private TlsConfig tlsConfig; DefaultOciCertificatesTlsManager(OciCertificatesTlsManagerConfig cfg) { @@ -83,37 +77,26 @@ class DefaultOciCertificatesTlsManager extends ConfiguredTlsManager implements O @Override // TlsManager public void init(TlsConfig tls) { this.tlsConfig = tls; - Services services = InjectionServices.realizedServices(); - this.pkDownloader = services.lookupFirst(OciPrivateKeyDownloader.class); - this.certDownloader = services.lookupFirst(OciCertificatesDownloader.class); - this.asyncExecutor = Executors.newSingleThreadScheduledExecutor(); - this.async = Async.builder().executor(asyncExecutor).build(); + ServiceRegistry registry = GlobalServiceRegistry.registry(); + this.pkDownloader = registry.supply(OciPrivateKeyDownloader.class); + this.certDownloader = registry.supply(OciCertificatesDownloader.class); + ScheduledExecutorService asyncExecutor = Executors.newSingleThreadScheduledExecutor(); // the initial loading of the tls loadContext(true); - // register for any available graceful shutdown events - Optional> shutdownHook = services.lookupFirst(LifecycleHook.class, false); - shutdownHook.ifPresent(sp -> sp.get().registerShutdownConsumer(this::shutdown)); - // now schedule for reload checking String taskIntervalDescription = - io.helidon.scheduling.Scheduling.cronBuilder() + io.helidon.scheduling.Scheduling.cron() .executor(asyncExecutor) .expression(cfg.schedule()) .task(inv -> maybeReload()) .build() .description(); - LOGGER.log(System.Logger.Level.DEBUG, () -> - OciCertificatesTlsManagerConfig.class.getSimpleName() + " scheduled: " + taskIntervalDescription); - } - private void shutdown(Object event) { - try { - LOGGER.log(System.Logger.Level.DEBUG, "Shutting down"); - asyncExecutor.shutdownNow(); - } catch (Exception e) { - LOGGER.log(System.Logger.Level.WARNING, "Shut down failed", e); + if (LOGGER.isLoggable(System.Logger.Level.DEBUG)) { + LOGGER.log(System.Logger.Level.DEBUG, + "Scheduled: " + taskIntervalDescription); } } diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManagerProvider.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManagerProvider.java index 593824375da..a11d551fc05 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManagerProvider.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManagerProvider.java @@ -23,7 +23,6 @@ /** * The service provider for {@link OciCertificatesTlsManager}. */ -//@Singleton - this is config driven, not pico driven - need to rectify this public class DefaultOciCertificatesTlsManagerProvider implements TlsManagerProvider { /** diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciPrivateKeyDownloader.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciPrivateKeyDownloader.java index 3c3b08d4003..9f4f69b1c3d 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciPrivateKeyDownloader.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciPrivateKeyDownloader.java @@ -34,24 +34,26 @@ import javax.crypto.spec.PSource; import javax.crypto.spec.SecretKeySpec; -import io.helidon.integrations.oci.sdk.runtime.OciExtension; import io.helidon.integrations.oci.tls.certificates.spi.OciPrivateKeyDownloader; +import io.helidon.service.registry.Service; +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; import com.oracle.bmc.keymanagement.KmsCryptoClient; import com.oracle.bmc.keymanagement.model.ExportKeyDetails; import com.oracle.bmc.keymanagement.requests.ExportKeyRequest; import com.oracle.bmc.keymanagement.responses.ExportKeyResponse; -import jakarta.inject.Singleton; /** * Implementation of the {@link OciPrivateKeyDownloader} that will use OCI's KMS to export a key. */ -@Singleton +@Service.Provider class DefaultOciPrivateKeyDownloader implements OciPrivateKeyDownloader { private final PrivateKey wrappingPrivateKey; private final String wrappingPublicKeyPem; + private final AbstractAuthenticationDetailsProvider authProvider; - DefaultOciPrivateKeyDownloader() { + DefaultOciPrivateKeyDownloader(AbstractAuthenticationDetailsProvider authProvider) { + this.authProvider = authProvider; try { KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); generator.initialize(2048); @@ -72,7 +74,7 @@ public PrivateKey loadKey(String keyOcid, Objects.requireNonNull(vaultCryptoEndpoint); try (KmsCryptoClient client = KmsCryptoClient.builder() .endpoint(vaultCryptoEndpoint.toString()) - .build(OciExtension.ociAuthenticationProvider().get())) { + .build(authProvider)) { ExportKeyResponse exportKeyResponse = client.exportKey(ExportKeyRequest.builder() .exportKeyDetails(ExportKeyDetails.builder() diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/LifecycleHook.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/LifecycleHook.java deleted file mode 100644 index 9dc6f078242..00000000000 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/LifecycleHook.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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. - */ - -package io.helidon.integrations.oci.tls.certificates; - -import java.util.function.Consumer; - -import io.helidon.inject.api.Contract; - -// consider making this public and relocating this to somewhere under common or inject -// it is here to ensure proper shutdown (decoupled from mp) and presenting non-intermittent test failures -@Contract -interface LifecycleHook { - - void registerStartupConsumer(Consumer consumer); - - void registerShutdownConsumer(Consumer consumer); - -} diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfigBlueprint.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfigBlueprint.java index c9393f6f1c9..fe83c4a0e6a 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfigBlueprint.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfigBlueprint.java @@ -20,15 +20,14 @@ import java.util.Optional; import java.util.function.Supplier; +import io.helidon.builder.api.Option; import io.helidon.builder.api.Prototype; -import io.helidon.config.metadata.Configured; -import io.helidon.config.metadata.ConfiguredOption; /** * Blueprint configuration for {@link OciCertificatesTlsManager}. */ @Prototype.Blueprint -@Configured +@Prototype.Configured interface OciCertificatesTlsManagerConfigBlueprint extends Prototype.Factory { /** @@ -37,7 +36,7 @@ interface OciCertificatesTlsManagerConfigBlueprint extends Prototype.Factory vaultManagementEndpoint(); /** @@ -64,7 +63,7 @@ interface OciCertificatesTlsManagerConfigBlueprint extends Prototype.Factory compartmentOcid(); /** @@ -72,7 +71,7 @@ interface OciCertificatesTlsManagerConfigBlueprint extends Prototype.Factory keyPassword(); } diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciCertificatesDownloader.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciCertificatesDownloader.java index b47e62b8f58..028a415aeb8 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciCertificatesDownloader.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciCertificatesDownloader.java @@ -19,12 +19,12 @@ import java.security.cert.X509Certificate; import java.util.Objects; -import io.helidon.inject.api.Contract; +import io.helidon.service.registry.Service; /** * The contract used for downloading certificates from OCI. */ -@Contract +@Service.Contract public interface OciCertificatesDownloader { /** diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciPrivateKeyDownloader.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciPrivateKeyDownloader.java index 5e28d389a06..9dbfdeb9f3b 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciPrivateKeyDownloader.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciPrivateKeyDownloader.java @@ -19,12 +19,12 @@ import java.net.URI; import java.security.PrivateKey; -import io.helidon.inject.api.Contract; +import io.helidon.service.registry.Service; /** * The contract used for downloading private keys from OCI. */ -@Contract +@Service.Contract public interface OciPrivateKeyDownloader { /** diff --git a/integrations/oci/tls-certificates/src/main/java/module-info.java b/integrations/oci/tls-certificates/src/main/java/module-info.java index c57615445be..b2b82d1209d 100644 --- a/integrations/oci/tls-certificates/src/main/java/module-info.java +++ b/integrations/oci/tls-certificates/src/main/java/module-info.java @@ -18,10 +18,6 @@ * Helidon Integrations of OCI Certificates Service. */ module io.helidon.integrations.oci.tls.certificates { - requires static io.helidon.config.metadata; - requires static jakarta.annotation; - requires static jakarta.inject; - requires io.helidon.builder.api; requires io.helidon.common; requires io.helidon.common.config; @@ -29,10 +25,9 @@ requires io.helidon.common.tls; requires io.helidon.config; requires io.helidon.faulttolerance; - requires io.helidon.integrations.oci.sdk.runtime; - requires io.helidon.inject.api; - requires io.helidon.inject.runtime; requires io.helidon.scheduling; + requires io.helidon.service.registry; + requires io.helidon.integrations.oci; requires oci.java.sdk.common; requires oci.java.sdk.certificates; @@ -47,6 +42,4 @@ provides io.helidon.common.tls.spi.TlsManagerProvider with io.helidon.integrations.oci.tls.certificates.DefaultOciCertificatesTlsManagerProvider; - provides io.helidon.inject.api.ModuleComponent - with io.helidon.integrations.oci.tls.certificates.Injection$$Module; } From 19eef188f3be9ddceab5ce83d1306814c0654f1b Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 16 May 2024 00:08:46 +0200 Subject: [PATCH 07/20] Suppress checkstyle for service registry tests --- etc/checkstyle-suppressions.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/etc/checkstyle-suppressions.xml b/etc/checkstyle-suppressions.xml index 4f3db1bae81..e3af4e8b4bf 100644 --- a/etc/checkstyle-suppressions.xml +++ b/etc/checkstyle-suppressions.xml @@ -1,7 +1,7 @@ + Date: Thu, 16 May 2024 00:09:21 +0200 Subject: [PATCH 08/20] Support for config factory methods in custom methods for builder (instead just on Blueprint) --- .../builder/codegen/FactoryMethods.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/builder/codegen/src/main/java/io/helidon/builder/codegen/FactoryMethods.java b/builder/codegen/src/main/java/io/helidon/builder/codegen/FactoryMethods.java index 7f33c720c30..cfd9931b2c3 100644 --- a/builder/codegen/src/main/java/io/helidon/builder/codegen/FactoryMethods.java +++ b/builder/codegen/src/main/java/io/helidon/builder/codegen/FactoryMethods.java @@ -151,12 +151,30 @@ private static Optional createFromConfigMethod(CodegenContext ctx // first look at declared type and blueprint String methodName = "create" + capitalize(typeHandler.name()); + + // check the blueprint itself, and then check the custom methods type Optional returnType = findFactoryMethodByParamType(blueprint, COMMON_CONFIG, methodName); + TypeName typeWithFactoryMethod = blueprint.typeName(); + + if (returnType.isEmpty()) { + if (blueprint.hasAnnotation(Types.PROTOTYPE_CUSTOM_METHODS)) { + Optional typeInfo = blueprint.annotation(Types.PROTOTYPE_CUSTOM_METHODS) + .typeValue() + .flatMap(ctx::typeInfo); + if (typeInfo.isPresent()) { + TypeInfo customMethods = typeInfo.get(); + typeWithFactoryMethod = customMethods.typeName(); + returnType = findFactoryMethodByParamType(customMethods, + COMMON_CONFIG, + methodName); + } + } + } + if (returnType.isPresent()) { - TypeName typeWithFactoryMethod = blueprint.typeName(); return Optional.of(new FactoryMethod(typeWithFactoryMethod, returnType.get(), methodName, From fb1b4052a8da5adac273953b54d726339512c323 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 16 May 2024 00:10:08 +0200 Subject: [PATCH 09/20] Support for `Supplier` as a service implementation, with possible `null` return value. --- .../service/registry/CoreServiceRegistry.java | 54 +++++++++---------- .../io/helidon/service/registry/Service.java | 10 +++- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java b/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java index 91684248441..df159526ff4 100644 --- a/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java +++ b/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java @@ -32,6 +32,7 @@ import io.helidon.common.LazyValue; import io.helidon.common.types.TypeName; +import io.helidon.common.types.TypeNames; import io.helidon.service.registry.GeneratedService.Descriptor; /** @@ -55,12 +56,12 @@ class CoreServiceRegistry implements ServiceRegistry { // add me ServiceRegistry__ServiceDescriptor registryDescriptor = ServiceRegistry__ServiceDescriptor.INSTANCE; processedDescriptorTypes.add(registryDescriptor.descriptorType()); - addContracts(providers, registryDescriptor.contracts(), new BoundInstance(registryDescriptor, this)); + addContracts(providers, registryDescriptor.contracts(), new BoundInstance(registryDescriptor, Optional.of(this))); // add explicit instances config.serviceInstances().forEach((descriptor, instance) -> { if (processedDescriptorTypes.add(descriptor.descriptorType())) { - BoundInstance bi = new BoundInstance(descriptor, instance); + BoundInstance bi = new BoundInstance(descriptor, Optional.of(instance)); addContracts(providers, descriptor.contracts(), bi); } }); @@ -95,29 +96,30 @@ class CoreServiceRegistry implements ServiceRegistry { this.providersByContract = Map.copyOf(providers); } - @SuppressWarnings("unchecked") @Override public T get(TypeName contract) { - var provider = firstProvider(contract) + return this.first(contract) .orElseThrow(() -> new ServiceRegistryException("Contract " + contract.fqName() + " is not supported, there are no service " + "descriptors in this registry that can satisfy it.")); - return (T) provider.instance(); } @SuppressWarnings("unchecked") @Override public Optional first(TypeName contract) { - return firstProvider(contract) - .map(ServiceProvider::instance) + return allProviders(contract) + .stream() + .flatMap(it -> it.instance().stream()) + .findFirst() .map(it -> (T) it); } @SuppressWarnings("unchecked") @Override public List all(TypeName contract) { - return allProviders(contract).stream() - .map(ServiceProvider::instance) + return allProviders(contract) + .stream() + .flatMap(it -> it.instance().stream()) .map(it -> (T) it) .collect(Collectors.toList()); } @@ -155,16 +157,8 @@ private List allProviders(TypeName contract) { return new ArrayList<>(serviceProviders); } - private Optional firstProvider(TypeName contract) { - Set serviceProviders = providersByContract.get(contract); - if (serviceProviders == null) { - return Optional.empty(); - } - ServiceProvider first = serviceProviders.iterator().next(); - return Optional.of(first); - } - - private Object instance(Descriptor descriptor) { + @SuppressWarnings("unchecked") + private Optional instance(Descriptor descriptor) { List dependencies = descriptor.dependencies(); Map collectedDependencies = new HashMap<>(); @@ -180,7 +174,11 @@ private Object instance(Descriptor descriptor) { } } - return descriptor.instantiate(DependencyContext.create(collectedDependencies)); + Object serviceInstance = descriptor.instantiate(DependencyContext.create(collectedDependencies)); + if (descriptor.contracts().contains(TypeNames.SUPPLIER)) { + return Optional.ofNullable(((Supplier) serviceInstance).get()); + } + return Optional.of(serviceInstance); } private Object dependencyNoSupplier(TypeName dependencyType, TypeName contract) { @@ -197,14 +195,14 @@ private Object dependencyNoSupplier(TypeName dependencyType, TypeName contract) private interface ServiceProvider { Descriptor descriptor(); - Object instance(); + Optional instance(); double weight(); TypeName descriptorType(); } - private record BoundInstance(Descriptor descriptor, Object instance) implements ServiceProvider { + private record BoundInstance(Descriptor descriptor, Optional instance) implements ServiceProvider { @Override public double weight() { return descriptor.weight(); @@ -218,17 +216,17 @@ public TypeName descriptorType() { private record BoundDescriptor(CoreServiceRegistry registry, Descriptor descriptor, - LazyValue lazyInstance, + LazyValue> lazyInstance, ReentrantLock lock) implements ServiceProvider { private BoundDescriptor(CoreServiceRegistry registry, Descriptor descriptor, - LazyValue lazyInstance) { + LazyValue> lazyInstance) { this(registry, descriptor, lazyInstance, new ReentrantLock()); } @Override - public Object instance() { + public Optional instance() { if (lazyInstance.isLoaded()) { return lazyInstance.get(); } @@ -258,12 +256,12 @@ public TypeName descriptorType() { private record DiscoveredDescriptor(CoreServiceRegistry registry, DescriptorMetadata metadata, - LazyValue lazyInstance, + LazyValue> lazyInstance, ReentrantLock lock) implements ServiceProvider { private DiscoveredDescriptor(CoreServiceRegistry registry, DescriptorMetadata metadata, - LazyValue lazyInstance) { + LazyValue> lazyInstance) { this(registry, metadata, lazyInstance, new ReentrantLock()); } @@ -273,7 +271,7 @@ public Descriptor descriptor() { } @Override - public Object instance() { + public Optional instance() { if (lazyInstance.isLoaded()) { return lazyInstance.get(); } diff --git a/service/registry/src/main/java/io/helidon/service/registry/Service.java b/service/registry/src/main/java/io/helidon/service/registry/Service.java index 2f46fb5ae41..fd8910667a4 100644 --- a/service/registry/src/main/java/io/helidon/service/registry/Service.java +++ b/service/registry/src/main/java/io/helidon/service/registry/Service.java @@ -49,9 +49,17 @@ private Service() { *
  • {@code Contract} - obtain an instance of a service providing that contract
  • *
  • {@code Optional} - the other service may not be available
  • *
  • {@code List} - obtain all instances of services providing the contract
  • - *
  • {@code Supplier} - and suppliers of all above, to break instantiation chaining, and to support cyclic + *
  • {@code Supplier} - and suppliers of all above, to break instantiation chaining, and to support cyclic * service references, just make sure you call the {@code get} method outside of the constructor
  • * + * + * A service provider may implement the contract in two ways: + *
      + *
    • Direct implementation of interface (or extending an abstract class)
    • + *
    • Implementing a {@link java.util.function.Supplier} of the contract; when using supplier, service registry + * supports the capability to return {@code null} to mark this service as "empty" and not providing anything; such + * a service will be ignored and only other implementations would be used
    • + *
    */ @Documented @Retention(RetentionPolicy.CLASS) From 852af23bf77a7c0224bbf8d7f89f12206d4218fd Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 16 May 2024 00:10:48 +0200 Subject: [PATCH 10/20] OCI integration using Service Registry - support for Region and Authentication Details Provider services. --- integrations/oci/oci/README.md | 43 ++++ integrations/oci/oci/pom.xml | 29 +++ .../integrations/oci/AtnDetailsProvider.java | 30 ++- .../integrations/oci/AtnStrategyConfig.java | 16 ++ .../oci/AtnStrategyConfigFile.java | 18 +- .../oci/AtnStrategyInstancePrincipal.java | 30 ++- .../oci/AtnStrategyResourcePrincipal.java | 18 +- .../ConfigFileStrategyConfigBlueprint.java | 16 ++ .../oci/ConfigStrategyConfigBlueprint.java | 16 ++ .../integrations/oci/OciConfigBlueprint.java | 58 ++++-- .../integrations/oci/OciConfigProvider.java | 68 ++++++- .../integrations/oci/OciConfigSupport.java | 34 ++++ .../integrations/oci/RegionProvider.java | 18 +- .../oci/RegionProviderAtnStrategy.java | 26 ++- .../oci/RegionProviderConfig.java | 24 ++- .../integrations/oci/RegionProviderSdk.java | 51 +++++ .../integrations/oci/SdkRegionProvider.java | 22 --- .../integrations/oci/package-info.java | 21 ++ .../integrations/oci/spi/OciAtnStrategy.java | 41 ++++ .../integrations/oci/spi/OciRegion.java | 36 ++++ .../integrations/oci/spi/package-info.java | 23 +++ .../oci/oci/src/main/java/module-info.java | 47 +++++ .../integrations/oci/OciIntegrationTest.java | 187 ++++++++++++++++++ .../oci/src/test/resources/test-oci-config | 30 +++ 24 files changed, 833 insertions(+), 69 deletions(-) create mode 100644 integrations/oci/oci/README.md create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigSupport.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderSdk.java delete mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/SdkRegionProvider.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/package-info.java create mode 100644 integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/package-info.java create mode 100644 integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java create mode 100644 integrations/oci/oci/src/test/resources/test-oci-config diff --git a/integrations/oci/oci/README.md b/integrations/oci/oci/README.md new file mode 100644 index 00000000000..21e5204a2ca --- /dev/null +++ b/integrations/oci/oci/README.md @@ -0,0 +1,43 @@ +# OCI integration module + +This Helidon module requires Service Registry. + +The module uses (internally) a service of type `OciConfig`. This instance is used to configure OCI integration. +Note that this service can be customized, if a provider with higher weight is created. +The default service implementation uses environment variables, system properties, and a configuration file `oci-config.yaml` on file system, or on classpath (Weight: default - 10). + +This module provides two services that can be used by other modules. + +## Authentication Details Provider + +Any service can have a dependency on `com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider`, and it can be +looked up using Service Registry lookup methods. + +The provider is looked up by using instances of `io.helidon.integrations.oci.spi.OciAtnStrategy`. The first service instance to provide an authentication provider is used, and no other service instance is called. + +The following out-of-the-box implementations exist: + +- Config based provider: uses `OciConfig` to create authentication provider; Weight: default - 10 +- Config file based provider: uses OCI config file (i.e. `~/.oci/config`) to create authentication provider; both file location and profile can be configured through `OciConfig`, Weight: default - 20 +- Resource principal provider: uses resource principal authentication provider; Weight: default - 30 +- Instance principal provider: uses instance principal authentication provider; Weight: default - 40 + +To create a custom instance of authentication details provider, just create a new service for service registry +with default or higher weight that provides an instance of the `AbstractAuthenticationDetailsProvider` +(ServiceRegistry requires setup of annotation processors, see this module's pom file). + +## Region + +Any service can have a dependency on `com.oracle.bmc.Region`, and it can be looked up using Service Registry +lookup methods. + +Region is discovered by using instances of `io.helidon.integrations.oci.spi.OciRegion`. The first service instance to provide a +region is used, and no other service instance is called. + +The following out-of-the-box implementations exists: + +- Config based region provider: uses `OciConfig` to find region (expected key is `oci.region`); Weight: default - 10 +- Authentication provider based region provider: uses authentication provider if it implements `RegionProvider` to find region; Weight: default - 20 +- OCI SDK based region provider: uses `Region.registerFromInstanceMetadataService()` to find region (this has timeout of 30 + seconds, so if we reach this provider, it may block for a while - but only once); Weight: default - 100 + diff --git a/integrations/oci/oci/pom.xml b/integrations/oci/oci/pom.xml index 17f03d135d4..2ab93efb089 100644 --- a/integrations/oci/oci/pom.xml +++ b/integrations/oci/oci/pom.xml @@ -39,10 +39,24 @@ io.helidon.common helidon-common-configurable + + io.helidon.config + helidon-config + com.oracle.oci.sdk oci-java-sdk-common + + io.helidon.config + helidon-config-yaml + test + + + io.helidon.common.testing + helidon-common-testing-junit5 + test + org.hamcrest hamcrest-all @@ -53,6 +67,11 @@ junit-jupiter-api test + + org.slf4j + slf4j-jdk14 + test + @@ -82,6 +101,11 @@ helidon-codegen-helidon-copyright ${helidon.version} + + io.helidon.config + helidon-config-metadata-processor + ${helidon.version} + @@ -105,6 +129,11 @@ helidon-codegen-helidon-copyright ${helidon.version} + + io.helidon.config + helidon-config-metadata-processor + ${helidon.version} + diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnDetailsProvider.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnDetailsProvider.java index cb8598f6b11..e58380e57db 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnDetailsProvider.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnDetailsProvider.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024 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.integrations.oci; import java.util.ArrayList; @@ -25,26 +41,24 @@ class AtnDetailsProvider implements Supplier { - List strategies = new ArrayList<>(); for (OciAtnStrategy atnDetailsProvider : atnDetailsProviders) { Optional provider = atnDetailsProvider.provider(); if (provider.isPresent()) { return provider.get(); } - strategies.add(atnDetailsProvider.strategy()); } - throw new RuntimeException("Cannot discover OCI Authentication Details Provider, none of the strategies" - + " returned a valid provider. Supported strategies: " + strategies); + return null; }); } else { List strategies = new ArrayList<>(); for (OciAtnStrategy atnDetailsProvider : atnDetailsProviders) { if (chosenStrategy.equals(atnDetailsProvider.strategy())) { - providerLazyValue = LazyValue.create(() -> atnDetailsProvider.provider().orElseThrow(() -> - new ConfigException( - "Strategy \"" + chosenStrategy + "\" did not provide an authentication provider, " - + "yet it is requested through configuration."))); + providerLazyValue = LazyValue.create(() -> atnDetailsProvider.provider() + .orElseThrow(() -> new ConfigException("Strategy \"" + + chosenStrategy + + "\" did not provide an authentication provider, " + + "yet it is requested through configuration."))); break; } strategies.add(atnDetailsProvider.strategy()); diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfig.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfig.java index 11c8830acab..8c33237de6f 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfig.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfig.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024 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.integrations.oci; import java.util.Optional; diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfigFile.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfigFile.java index b54d67cc0be..ae39f4e9f34 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfigFile.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyConfigFile.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024 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.integrations.oci; import java.io.IOException; @@ -21,7 +37,7 @@ @Service.Provider class AtnStrategyConfigFile implements OciAtnStrategy { static final String DEFAULT_PROFILE_NAME = "DEFAULT"; - static final String STRATEGY = "config_file"; + static final String STRATEGY = "config-file"; private static final System.Logger LOGGER = System.getLogger(AtnStrategyConfigFile.class.getName()); diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java index 9d9f2fa6c54..47925a189c1 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024 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.integrations.oci; import java.io.IOException; @@ -12,18 +28,21 @@ import io.helidon.integrations.oci.spi.OciAtnStrategy; import io.helidon.service.registry.Service; -import com.oracle.bmc.Region; import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; import com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider; /** - * Config file based authentication strategy, uses the {@link com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider}. + * Instance principal authentication strategy, uses the + * {@link com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider}. */ @Weight(Weighted.DEFAULT_WEIGHT - 40) @Service.Provider class AtnStrategyInstancePrincipal implements OciAtnStrategy { static final String STRATEGY = "instance-principal"; + // we do not use the constant, as it is marked as internal, and we only need the IP address anyway + // see com.oracle.bmc.auth.AbstractFederationClientAuthenticationDetailsProviderBuilder.METADATA_SERVICE_BASE_URL + private static final String IMDS_ADDRESS = "169.254.169.254"; private static final System.Logger LOGGER = System.getLogger(AtnStrategyInstancePrincipal.class.getName()); private final LazyValue> provider; @@ -52,18 +71,17 @@ private static LazyValue> create } private static boolean imdsVailable(OciConfig config) { - String imdsAddress = config.imdsAddress(); Duration timeout = config.imdsTimeout(); try { - if (InetAddress.getByName(imdsAddress) + if (InetAddress.getByName(IMDS_ADDRESS) .isReachable((int) timeout.toMillis())) { - return Region.getRegionFromImds("http://" + imdsAddress + "/opc/v2") != null; + return RegionProviderSdk.regionFromImds() != null; } return false; } catch (IOException e) { LOGGER.log(Level.TRACE, - "imds service is not reachable, or timed out for address: " + imdsAddress + ", instance principal " + "imds service is not reachable, or timed out for address: " + IMDS_ADDRESS + ", instance principal " + "strategy is not available.", e); return false; diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyResourcePrincipal.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyResourcePrincipal.java index 7891892f29e..7c942861a7f 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyResourcePrincipal.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyResourcePrincipal.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024 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.integrations.oci; import java.lang.System.Logger.Level; @@ -13,7 +29,7 @@ import com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider; /** - * Config file based authentication strategy, uses the {@link com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider}. + * Resource authentication strategy, uses the {@link com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider}. */ @Weight(Weighted.DEFAULT_WEIGHT - 30) @Service.Provider diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigFileStrategyConfigBlueprint.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigFileStrategyConfigBlueprint.java index 9c0af884697..7ee84c7fb75 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigFileStrategyConfigBlueprint.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigFileStrategyConfigBlueprint.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024 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.integrations.oci; import java.util.Optional; diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigStrategyConfigBlueprint.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigStrategyConfigBlueprint.java index ef4f44ed5b8..968688f1587 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigStrategyConfigBlueprint.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/ConfigStrategyConfigBlueprint.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024 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.integrations.oci; import java.util.Optional; diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java index d3ef52a5264..c7c68b34728 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024 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.integrations.oci; import java.time.Duration; @@ -7,11 +23,31 @@ import io.helidon.builder.api.Option; import io.helidon.builder.api.Prototype; +import com.oracle.bmc.Region; + +/** + * Meta configuration of OCI integration for Helidon. + *

    + * Allows customization of discovery of authentication details provider and of region. + */ @Prototype.Configured("oci") @Prototype.Blueprint +@Prototype.CustomMethods(OciConfigSupport.class) interface OciConfigBlueprint { + /** + * Default authentication strategy. The default is to use automatic discovery - i.e. cycle through possible + * providers until one yields an authentication details provider instance. + */ String STRATEGY_AUTO = "auto"; - String IMDS_ADDRESS = "169.254.169.254"; + + /** + * Explicit region. The configured region will be used by region provider. + * This may be ignored by authentication detail providers, as in most cases region is provided by them. + * + * @return explicit region + */ + @Option.Configured + Optional region(); /** * Authentication strategy to use. If the configured strategy is not available, an exception @@ -27,7 +63,8 @@ interface OciConfigBlueprint { * {@link com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider} *

  • {@value AtnStrategyConfigFile#STRATEGY} - use configuration file of OCI ({@code home/.oci/config}), uses * {@link com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider}
  • - *
  • {@value AtnStrategyResourcePrincipal#STRATEGY} - use identity of the OCI resource the service is executed on (fn), uses + *
  • {@value AtnStrategyResourcePrincipal#STRATEGY} - use identity of the OCI resource the service is executed on + * (fn), uses * {@link com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider}
  • *
  • {@value AtnStrategyInstancePrincipal#STRATEGY} - use identity of the OCI instance the service is running on, uses * {@link com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider}
  • @@ -36,7 +73,7 @@ interface OciConfigBlueprint { * @return the authentication strategy to apply */ @Option.Configured - @Option.Default("auto") + @Option.Default(STRATEGY_AUTO) String atnStrategy(); /** @@ -56,7 +93,7 @@ interface OciConfigBlueprint { * @return information needed for config {@link #atnStrategy()} */ @Option.Configured("config-strategy") - Optional configStrategyConfig(); + Optional configStrategyConfig(); /** * Config file strategy configuration (if provided and used). @@ -64,18 +101,7 @@ interface OciConfigBlueprint { * @return information to customize config for {@link #atnStrategy()} */ @Option.Configured("config-file-strategy") - Optional configFileStrategyConfig(); - - /** - * The OCI IMDS address or hostname. - *

    - * This configuration property is used to identify the metadata service url. - * - * @return the OCI IMDS hostname - */ - @Option.Configured - @Option.Default(IMDS_ADDRESS) - String imdsAddress(); + Optional configFileStrategyConfig(); /** * The OCI IMDS connection timeout. This is used to auto-detect availability. diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigProvider.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigProvider.java index 80cff6555da..5d0771bc4d9 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigProvider.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigProvider.java @@ -1,21 +1,77 @@ +/* + * Copyright (c) 2024 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.integrations.oci; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Supplier; +import io.helidon.common.Weight; +import io.helidon.common.Weighted; import io.helidon.common.config.Config; +import io.helidon.config.ConfigSources; import io.helidon.service.registry.Service; +// the supplier MUST use fully qualified name, as the type is generated as part of annotation processing +// and the generated contract would be wrong if not @Service.Provider -class OciConfigProvider implements Supplier { - private final OciConfig ociConfig; +@Weight(Weighted.DEFAULT_WEIGHT - 10) +class OciConfigProvider implements Supplier { + private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock(); + private static volatile OciConfig ociConfig; + + OciConfigProvider() { + } - OciConfigProvider(Config config) { - this.ociConfig = config.get("oci").map(OciConfig::create) - .orElseGet(OciConfig::create); + static void config(OciConfig ociConfig) { + LOCK.writeLock().lock(); + try { + OciConfigProvider.ociConfig = ociConfig; + } finally { + LOCK.writeLock().unlock(); + } } @Override public OciConfig get() { - return ociConfig; + LOCK.readLock().lock(); + try { + OciConfig toUse = ociConfig; + if (toUse != null) { + return toUse; + } + } finally { + LOCK.readLock().unlock(); + } + LOCK.writeLock().lock(); + try { + create(); + return ociConfig; + } finally { + LOCK.writeLock().unlock(); + } + } + + private static void create() { + Config config = io.helidon.config.Config.create( + ConfigSources.environmentVariables(), + ConfigSources.systemProperties(), + ConfigSources.file("oci-config.yaml").optional(true), + ConfigSources.classpath("oci-config.yaml").optional(true)); + + ociConfig = OciConfig.create(config); } } diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigSupport.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigSupport.java new file mode 100644 index 00000000000..c410e47d8ce --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigSupport.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 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.integrations.oci; + +import io.helidon.builder.api.Prototype; +import io.helidon.common.config.Config; + +import com.oracle.bmc.Region; + +final class OciConfigSupport { + private OciConfigSupport() { + } + + @Prototype.FactoryMethod + static Region createRegion(Config config) { + return config.asString() + .map(Region::fromRegionCodeOrId) + .get(); + } +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProvider.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProvider.java index d793ce3ef35..8511db6e7a8 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProvider.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProvider.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024 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.integrations.oci; import java.util.List; @@ -26,6 +42,6 @@ public Region get() { return region.get(); } } - throw new RuntimeException("Cannot discover OCI region"); + return null; } } diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderAtnStrategy.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderAtnStrategy.java index 5f60294beae..f8707b31726 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderAtnStrategy.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderAtnStrategy.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024 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.integrations.oci; import java.util.Optional; @@ -11,18 +27,22 @@ import com.oracle.bmc.Region; import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; +import com.oracle.bmc.auth.RegionProvider; @Service.Provider @Weight(Weighted.DEFAULT_WEIGHT - 20) class RegionProviderAtnStrategy implements OciRegion { private final LazyValue> region; - RegionProviderAtnStrategy(Supplier atnProvider) { + RegionProviderAtnStrategy(Supplier> atnProvider) { this.region = LazyValue.create(() -> { var provider = atnProvider.get(); - if (provider instanceof RegionProvider regionProvider) { - return Optional.of(regionProvider.get()); + if (provider.isEmpty()) { + return Optional.empty(); + } + if (provider.get() instanceof RegionProvider regionProvider) { + return Optional.of(regionProvider.getRegion()); } return Optional.empty(); }); diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderConfig.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderConfig.java index 3d9139019dd..1e9ce652772 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderConfig.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderConfig.java @@ -1,11 +1,27 @@ +/* + * Copyright (c) 2024 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.integrations.oci; import java.util.Optional; +import java.util.function.Supplier; import io.helidon.common.LazyValue; import io.helidon.common.Weight; import io.helidon.common.Weighted; -import io.helidon.common.config.Config; import io.helidon.integrations.oci.spi.OciRegion; import io.helidon.service.registry.Service; @@ -16,10 +32,8 @@ class RegionProviderConfig implements OciRegion { private final LazyValue> region; - RegionProviderConfig(Config config) { - this.region = LazyValue.create(() -> config.get("oci.region") - .asString() - .map(Region::fromRegionCodeOrId)); + RegionProviderConfig(Supplier config) { + this.region = LazyValue.create(() -> config.get().region()); } @Override diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderSdk.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderSdk.java new file mode 100644 index 00000000000..cdb3cc7ca7b --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderSdk.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024 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.integrations.oci; + +import java.util.Optional; + +import io.helidon.common.LazyValue; +import io.helidon.common.Weight; +import io.helidon.common.Weighted; +import io.helidon.integrations.oci.spi.OciRegion; +import io.helidon.service.registry.Service; + +import com.oracle.bmc.Region; + +@Service.Provider +@Weight(Weighted.DEFAULT_WEIGHT - 100) +class RegionProviderSdk implements OciRegion { + private final LazyValue> region; + + RegionProviderSdk() { + this.region = LazyValue.create(() -> Optional.ofNullable(regionFromImds())); + } + + /** + * There is a 30 second timeout configured, so this has a relatively low weight. + * We want a different way to get the region if available. + */ + static Region regionFromImds() { + Region.registerFromInstanceMetadataService(); + return Region.getRegionFromImds(); + } + + @Override + public Optional region() { + return region.get(); + } +} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/SdkRegionProvider.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/SdkRegionProvider.java deleted file mode 100644 index 9ab66543e16..00000000000 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/SdkRegionProvider.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.helidon.integrations.oci; - -import java.util.Optional; - -import io.helidon.common.LazyValue; -import io.helidon.common.Weight; -import io.helidon.common.Weighted; -import io.helidon.integrations.oci.spi.OciRegion; -import io.helidon.service.registry.Service; - -import com.oracle.bmc.Region; - -@Service.Provider -@Weight(Weighted.DEFAULT_WEIGHT - 100) -class SdkRegionProvider implements OciRegion { - private final LazyValue> region = LazyValue.create(() -> Optional.ofNullable(Region.getRegionFromImds())); - - @Override - public Optional region() { - return region.get(); - } -} diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/package-info.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/package-info.java new file mode 100644 index 00000000000..98c875dfdab --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 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. + */ + +/** + * Implementation of OCI integrations. + * The only public classes in this package are service descriptors. + */ +package io.helidon.integrations.oci; diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciAtnStrategy.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciAtnStrategy.java index b39ac6a8939..44f324dbfab 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciAtnStrategy.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciAtnStrategy.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024 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.integrations.oci.spi; import java.util.Optional; @@ -6,8 +22,33 @@ import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; +/** + * An OCI Authentication Details Provider service contract. + *

    + * This service is implemented by: + *

      + *
    • Config based strategy
    • + *
    • Config file based strategy
    • + *
    • Resource principal strategy
    • + *
    • Instance principal strategy
    • + *
    + * The first one that provides an instance will be used as the value. + * To customize, create your own service with a default or higher weight. + */ @Service.Contract public interface OciAtnStrategy { + /** + * The strategy name, can be used to explicitly select a strategy using configuration. + * + * @return strategy name + */ String strategy(); + + /** + * Provide an instance of the {@link com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider} to be used + * by other services. + * + * @return authentication details provider, or empty if nothing can be provided + */ Optional provider(); } diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciRegion.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciRegion.java index 51296f6db17..5f6be1ae556 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciRegion.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/OciRegion.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024 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.integrations.oci.spi; import java.util.Optional; @@ -6,7 +22,27 @@ import com.oracle.bmc.Region; +/** + * An OCI region discovery mechanism to provide {@link com.oracle.bmc.Region} as a service in Helidon + * service registry. + *

    + * This service is implemented by: + *

      + *
    • Config based - just put {@code oci.region} to OCI configuration + * (or environment variables/system properties)
    • + *
    • Authentication provider based - if the authentication provider implements a + * {@link com.oracle.bmc.auth.RegionProvider}, the region will be used
    • + *
    • Region from {@link com.oracle.bmc.Region#getRegionFromImds()}
    • + *
    + * The first one that provides an instance will be used as the value. + * To customize, create your own service with a default or higher weight. + */ @Service.Contract public interface OciRegion { + /** + * The region, if it can be provided by this service. + * + * @return OCI region + */ Optional region(); } diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/package-info.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/package-info.java new file mode 100644 index 00000000000..858027753d0 --- /dev/null +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/spi/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 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. + */ + +/** + * Extension points for OCI integration. + * + * @see io.helidon.integrations.oci.spi.OciAtnStrategy + * @see io.helidon.integrations.oci.spi.OciRegion + */ +package io.helidon.integrations.oci.spi; diff --git a/integrations/oci/oci/src/main/java/module-info.java b/integrations/oci/oci/src/main/java/module-info.java index 64f1477ae96..0175cccaa98 100644 --- a/integrations/oci/oci/src/main/java/module-info.java +++ b/integrations/oci/oci/src/main/java/module-info.java @@ -1,9 +1,56 @@ +/* + * Copyright (c) 2024 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. + */ + +/** + * OCI integration module using Helidon Service Registry. + * This core module provides services for {@link com.oracle.bmc.Region} and + * {@link com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider}. + *

    + * The module does not require {@link io.helidon.common.config.Config} service to be available, as it is considered + * a prerequisite for possible config sources. + *

    + * + * To customize configuration of this module, the following options exist: + *

      + *
    • Create a custom service provider that provides {@link io.helidon.integrations.oci.OciConfig}
    • + *
    • Create a file {@code oci-config.yaml} either on classpath, or in the current directory with configuration + * required by {@link io.helidon.integrations.oci.OciConfig}, this also requires YAML config parser on classpath.
    • + *
    • Add environment variables to override configuration options of {@link io.helidon.integrations.oci.OciConfig}, + * such as {@code OCI_ATNSTRATEGY=config_file}
    • + *
    • Add system properties to override configuration options of {@link io.helidon.integrations.oci.OciConfig}, + * such as {@code oci.atnStrategy=config_file}
    • + *
    + * + * To customize authentication details provider, you can implement {@link io.helidon.integrations.oci.spi.OciAtnStrategy} + * service. The out-of-the-box providers have all less than default weight, and are in the + * following order (strategy: description (weight)): + *
      + *
    • {@code config}: Config based authentication details provider - using only configured options (default weight - 10)
    • + *
    • {@code config-file}: Config file based authentication details provider (default weight - 20)
    • + *
    • {@code resource-principal}: Resource principal, used for example by fn (default-weight - 30)
    • + *
    • {@code instance-principal}: Instance principal, used for VMs (default-weight - 40)
    • + *
    + */ module io.helidon.integrations.oci { requires io.helidon.common.configurable; requires io.helidon.service.registry; requires oci.java.sdk.common; requires io.helidon.common.config; requires org.bouncycastle.util; + requires io.helidon.config; exports io.helidon.integrations.oci; exports io.helidon.integrations.oci.spi; diff --git a/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java b/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java new file mode 100644 index 00000000000..20993567a65 --- /dev/null +++ b/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2024 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.integrations.oci; + +import java.util.Optional; + +import io.helidon.common.media.type.MediaTypes; +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import io.helidon.integrations.oci.spi.OciAtnStrategy; +import io.helidon.service.registry.ServiceRegistry; +import io.helidon.service.registry.ServiceRegistryManager; + +import com.oracle.bmc.Region; +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; +import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider; +import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; + +class OciIntegrationTest { + + private ServiceRegistryManager registryManager; + private ServiceRegistry registry; + + void setUp(Config config) { + OciConfigProvider.config(OciConfig.create(config.get("oci"))); + registryManager = ServiceRegistryManager.create(); + registry = registryManager.registry(); + } + + @AfterEach + void tearDown() { + registry = null; + if (registryManager != null) { + registryManager.shutdown(); + } + } + + @Test + void testNoStrategyAvailable() { + Config config = Config.empty(); + setUp(config); + + OciAtnStrategy atnStrategy = registry.get(AtnStrategyConfig.class); + assertThat(atnStrategy.strategy(), is(AtnStrategyConfig.STRATEGY)); + assertThat(atnStrategy.provider(), optionalEmpty()); + + atnStrategy = registry.get(AtnStrategyConfigFile.class); + assertThat(atnStrategy.strategy(), is(AtnStrategyConfigFile.STRATEGY)); + assertThat(atnStrategy.provider(), optionalEmpty()); + + atnStrategy = registry.get(AtnStrategyInstancePrincipal.class); + assertThat(atnStrategy.strategy(), is(AtnStrategyInstancePrincipal.STRATEGY)); + assertThat(atnStrategy.provider(), optionalEmpty()); + + atnStrategy = registry.get(AtnStrategyResourcePrincipal.class); + assertThat(atnStrategy.strategy(), is(AtnStrategyResourcePrincipal.STRATEGY)); + assertThat(atnStrategy.provider(), optionalEmpty()); + + assertThat(registry.first(Region.class), is(Optional.empty())); + assertThat(registry.first(AbstractAuthenticationDetailsProvider.class), is(Optional.empty())); + } + + @Test + void testRegionFromConfig() { + String yamlConfig = """ + oci.region: us-phoenix-1 + """; + Config config = Config.just(ConfigSources.create(yamlConfig, MediaTypes.APPLICATION_YAML)); + setUp(config); + + assertThat(registry.first(Region.class), optionalValue(is(Region.US_PHOENIX_1))); + } + + @Test + void testConfigStrategyAvailable() { + String yamlConfig = """ + oci: + config-strategy: + # region must be real, so it can be parsed + region: us-phoenix-1 + fingerprint: fp + passphrase: passphrase + tenant-id: tenant + user-id: user + """; + Config config = Config.just(ConfigSources.create(yamlConfig, MediaTypes.APPLICATION_YAML)); + setUp(config); + + OciAtnStrategy atnStrategy = registry.get(AtnStrategyConfig.class); + assertThat(atnStrategy.strategy(), is(AtnStrategyConfig.STRATEGY)); + assertThat(atnStrategy.provider(), not(Optional.empty())); + + atnStrategy = registry.get(AtnStrategyConfigFile.class); + assertThat(atnStrategy.strategy(), is(AtnStrategyConfigFile.STRATEGY)); + assertThat(atnStrategy.provider(), optionalEmpty()); + + atnStrategy = registry.get(AtnStrategyInstancePrincipal.class); + assertThat(atnStrategy.strategy(), is(AtnStrategyInstancePrincipal.STRATEGY)); + assertThat(atnStrategy.provider(), optionalEmpty()); + + atnStrategy = registry.get(AtnStrategyResourcePrincipal.class); + assertThat(atnStrategy.strategy(), is(AtnStrategyResourcePrincipal.STRATEGY)); + assertThat(atnStrategy.provider(), optionalEmpty()); + + AbstractAuthenticationDetailsProvider provider = registry.get(AbstractAuthenticationDetailsProvider.class); + + assertThat(provider, instanceOf(SimpleAuthenticationDetailsProvider.class)); + SimpleAuthenticationDetailsProvider auth = (SimpleAuthenticationDetailsProvider) provider; + assertThat(auth.getTenantId(), + equalTo("tenant")); + assertThat(auth.getUserId(), + equalTo("user")); + assertThat(auth.getRegion(), + equalTo(Region.US_PHOENIX_1)); + assertThat(new String(auth.getPassphraseCharacters()), + equalTo("passphrase")); + + assertThat(registry.first(Region.class), optionalValue(is(Region.US_PHOENIX_1))); + } + + @Test + void testConfigFileStrategyAvailable() { + String yamlConfig = """ + oci: + config-file-strategy: + path: src/test/resources/test-oci-config + profile: MY_PROFILE + """; + Config config = Config.just(ConfigSources.create(yamlConfig, MediaTypes.APPLICATION_YAML)); + setUp(config); + + OciAtnStrategy atnStrategy = registry.get(AtnStrategyConfig.class); + assertThat(atnStrategy.strategy(), is(AtnStrategyConfig.STRATEGY)); + assertThat(atnStrategy.provider(), optionalEmpty()); + + atnStrategy = registry.get(AtnStrategyConfigFile.class); + assertThat(atnStrategy.strategy(), is(AtnStrategyConfigFile.STRATEGY)); + assertThat(atnStrategy.provider(), not(Optional.empty())); + + atnStrategy = registry.get(AtnStrategyInstancePrincipal.class); + assertThat(atnStrategy.strategy(), is(AtnStrategyInstancePrincipal.STRATEGY)); + assertThat(atnStrategy.provider(), optionalEmpty()); + + atnStrategy = registry.get(AtnStrategyResourcePrincipal.class); + assertThat(atnStrategy.strategy(), is(AtnStrategyResourcePrincipal.STRATEGY)); + assertThat(atnStrategy.provider(), optionalEmpty()); + + AbstractAuthenticationDetailsProvider provider = registry.get(AbstractAuthenticationDetailsProvider.class); + + assertThat(provider, instanceOf(ConfigFileAuthenticationDetailsProvider.class)); + ConfigFileAuthenticationDetailsProvider auth = (ConfigFileAuthenticationDetailsProvider) provider; + assertThat(auth.getTenantId(), + equalTo("tenant")); + assertThat(auth.getUserId(), + equalTo("user")); + assertThat(auth.getRegion(), + equalTo(Region.US_PHOENIX_1)); + assertThat(new String(auth.getPassphraseCharacters()), + equalTo("passphrase")); + + assertThat(registry.first(Region.class), optionalValue(is(Region.US_PHOENIX_1))); + } +} diff --git a/integrations/oci/oci/src/test/resources/test-oci-config b/integrations/oci/oci/src/test/resources/test-oci-config new file mode 100644 index 00000000000..ebae2ab8787 --- /dev/null +++ b/integrations/oci/oci/src/test/resources/test-oci-config @@ -0,0 +1,30 @@ +# +# Copyright (c) 2024 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. +# + +# Used from io.helidon.integrations.oci.OciAuthenticationDetailsProviderTest +[DEFAULT] +user=defUser +fingerprint=deffp +tenancy=deftenant +region=eu-frankfurt-1 + +[MY_PROFILE] +user=user +fingreprint=fp +tenancy=tenant +region=us-phoenix-1 +key_file=key.pem +pass_phrase=passphrase From c359d622bbd6bcc9f425b460a5b223d278a392b1 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 16 May 2024 00:11:22 +0200 Subject: [PATCH 11/20] Refactor TLS certificates to use Service Registry instead of Helidon Inject. --- integrations/oci/tls-certificates/README.md | 5 +- integrations/oci/tls-certificates/pom.xml | 38 ++++++---- .../DefaultOciCertificatesDownloader.java | 3 + ...aultOciCertificatesTlsManagerProvider.java | 2 +- .../DefaultOciPrivateKeyDownloader.java | 2 +- ...CertificatesTlsManagerConfigBlueprint.java | 2 +- .../spi/OciCertificatesDownloader.java | 2 +- .../spi/OciPrivateKeyDownloader.java | 2 +- .../src/main/java/module-info.java | 2 +- .../OciCertificatesTlsManagerTest.java | 48 +------------ .../TestOciCertificatesDownloader.java | 28 +++++--- .../TestOciPrivateKeyDownloader.java | 21 ++++-- .../tls/certificates/TestingCdiExtension.java | 71 ------------------- .../resources/{oci.yaml => oci-config.yaml} | 2 +- 14 files changed, 72 insertions(+), 156 deletions(-) delete mode 100644 integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestingCdiExtension.java rename integrations/oci/tls-certificates/src/test/resources/{oci.yaml => oci-config.yaml} (91%) diff --git a/integrations/oci/tls-certificates/README.md b/integrations/oci/tls-certificates/README.md index b6c19524c04..cda39e89feb 100644 --- a/integrations/oci/tls-certificates/README.md +++ b/integrations/oci/tls-certificates/README.md @@ -1,6 +1,9 @@ # Helidon Integrations for OCI Key Manager Service -This module contains the **_OciKmsTlsManager_** provider that offers lifecycle and rotation of certificates to be used with Helidon Tls when configured. It is designed specifically to integrate with [Oracle Cloud Infrastructure](https://www.oracle.com/cloud)'s [Certificates](https://www.oracle.com/security/cloud-security/ssl-tls-certificates) Service. +This module contains the **_OciKmsTlsManager_** provider that offers lifecycle and rotation of certificates to be used with Helidon TLS when configured. It is designed specifically to integrate with [Oracle Cloud Infrastructure](https://www.oracle.com/cloud)'s [Certificates](https://www.oracle.com/security/cloud-security/ssl-tls-certificates) Service. + +This module uses the global Service Registry, as it is discovered through service loader, +and as such cannot get dependencies directly from registry. ## Usage Integrating with OCI's Certificates Service from Helidon is a simple matter of configuration. diff --git a/integrations/oci/tls-certificates/pom.xml b/integrations/oci/tls-certificates/pom.xml index bfd2018dd1e..6846f6d98a7 100644 --- a/integrations/oci/tls-certificates/pom.xml +++ b/integrations/oci/tls-certificates/pom.xml @@ -106,16 +106,10 @@ test - io.helidon.microprofile.testing - helidon-microprofile-testing-junit5 + io.helidon.config + helidon-config-yaml test - - io.helidon.microprofile.bundles - helidon-microprofile - test - - @@ -125,37 +119,57 @@ maven-compiler-plugin + + io.helidon.codegen + helidon-codegen-apt + ${helidon.version} + io.helidon.builder helidon-builder-codegen ${helidon.version} + + io.helidon.service + helidon-service-codegen + ${helidon.version} + io.helidon.codegen helidon-codegen-helidon-copyright ${helidon.version} - io.helidon.service - helidon-service-codegen + io.helidon.config + helidon-config-metadata-processor ${helidon.version} + + io.helidon.codegen + helidon-codegen-apt + ${helidon.version} + io.helidon.builder helidon-builder-codegen ${helidon.version} + + io.helidon.service + helidon-service-codegen + ${helidon.version} + io.helidon.codegen helidon-codegen-helidon-copyright ${helidon.version} - io.helidon.service - helidon-service-codegen + io.helidon.config + helidon-config-metadata-processor ${helidon.version} diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesDownloader.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesDownloader.java index 6b96965a0ba..17995c8fbb0 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesDownloader.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesDownloader.java @@ -26,6 +26,8 @@ import java.util.List; import java.util.Objects; +import io.helidon.common.Weight; +import io.helidon.common.Weighted; import io.helidon.common.pki.PemReader; import io.helidon.integrations.oci.tls.certificates.spi.OciCertificatesDownloader; import io.helidon.service.registry.Service; @@ -43,6 +45,7 @@ * Implementation of the {@link OciCertificatesDownloader} that will use OCI's Certificates Service to download certs. */ @Service.Provider +@Weight(Weighted.DEFAULT_WEIGHT - 10) class DefaultOciCertificatesDownloader implements OciCertificatesDownloader { private final AbstractAuthenticationDetailsProvider authProvider; diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManagerProvider.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManagerProvider.java index a11d551fc05..6ae89793450 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManagerProvider.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciCertificatesTlsManagerProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciPrivateKeyDownloader.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciPrivateKeyDownloader.java index 9f4f69b1c3d..99d68e2a7c5 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciPrivateKeyDownloader.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/DefaultOciPrivateKeyDownloader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfigBlueprint.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfigBlueprint.java index fe83c4a0e6a..63c781e6516 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfigBlueprint.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerConfigBlueprint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciCertificatesDownloader.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciCertificatesDownloader.java index 028a415aeb8..6cccafc8e57 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciCertificatesDownloader.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciCertificatesDownloader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciPrivateKeyDownloader.java b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciPrivateKeyDownloader.java index 9dbfdeb9f3b..809f558fff9 100644 --- a/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciPrivateKeyDownloader.java +++ b/integrations/oci/tls-certificates/src/main/java/io/helidon/integrations/oci/tls/certificates/spi/OciPrivateKeyDownloader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/tls-certificates/src/main/java/module-info.java b/integrations/oci/tls-certificates/src/main/java/module-info.java index b2b82d1209d..42f7b8cc4e2 100644 --- a/integrations/oci/tls-certificates/src/main/java/module-info.java +++ b/integrations/oci/tls-certificates/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerTest.java b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerTest.java index cb889262814..88ee911e7c7 100644 --- a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerTest.java +++ b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/OciCertificatesTlsManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -17,15 +17,10 @@ package io.helidon.integrations.oci.tls.certificates; import java.util.Optional; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import io.helidon.common.tls.Tls; import io.helidon.config.Config; import io.helidon.config.ConfigSources; -import io.helidon.inject.api.InjectionServices; -import io.helidon.inject.api.Services; -import io.helidon.microprofile.server.Server; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -43,42 +38,6 @@ void reset() { TestOciCertificatesDownloader.callCount_loadCertificates = 0; TestOciCertificatesDownloader.callCount_loadCACertificate = 0; TestOciPrivateKeyDownloader.callCount = 0; - TestingCdiExtension.shutdownCalled = false; - } - - @Test - void serverRuntime() throws Exception { - Services services = InjectionServices.realizedServices(); - LifecycleHook lifecycleHook = services.lookupFirst(LifecycleHook.class).get(); - CountDownLatch startup = new CountDownLatch(1); - - lifecycleHook.registerStartupConsumer(c -> startup.countDown()); - Server server = startServer(); - boolean res = startup.await(10, TimeUnit.SECONDS); - assertThat(res, - is(true)); - - CountDownLatch shutdown = new CountDownLatch(1); - lifecycleHook.registerShutdownConsumer(c -> shutdown.countDown()); - - int certDownloadCountBaseline = TestOciCertificatesDownloader.callCount_loadCertificates; - int caCertDownloadCountBaseline = TestOciCertificatesDownloader.callCount_loadCACertificate; - int pkDownloadCountBaseLine = TestOciPrivateKeyDownloader.callCount; - try { - assertThat(certDownloadCountBaseline, - equalTo(1)); - assertThat(caCertDownloadCountBaseline, - equalTo(1)); - assertThat(pkDownloadCountBaseLine, - equalTo(1)); - } finally { - server.stop(); - res = shutdown.await(10, TimeUnit.SECONDS); - assertThat(res, - is(true)); - } - - assertThat(TestingCdiExtension.shutdownCalled, is(true)); } @Test @@ -143,9 +102,4 @@ void configIsMonitoredForChange() throws Exception { .get("server.sockets.0.tls.manager.oci-certificates-tls-manager.key-password").asString().asOptional(), is(Optional.of("changed"))); } - - static Server startServer() { - return Server.create().start(); - } - } diff --git a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciCertificatesDownloader.java b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciCertificatesDownloader.java index fe5dd448ddc..bbabfd7205a 100644 --- a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciCertificatesDownloader.java +++ b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciCertificatesDownloader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -22,20 +22,26 @@ import java.security.cert.X509Certificate; import java.util.Objects; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import io.helidon.common.Weight; import io.helidon.common.Weighted; import io.helidon.integrations.oci.tls.certificates.spi.OciCertificatesDownloader; +import io.helidon.service.registry.Service; -import jakarta.inject.Singleton; - -@Singleton +@Service.Provider @Weight(Weighted.DEFAULT_WEIGHT + 1) -class TestOciCertificatesDownloader extends DefaultOciCertificatesDownloader { +class TestOciCertificatesDownloader implements OciCertificatesDownloader { static String version = "1"; - static int callCount_loadCertificates; - static int callCount_loadCACertificate; + static volatile int callCount_loadCertificates; + static volatile int callCount_loadCACertificate; + + private final Supplier realDownloader; + + TestOciCertificatesDownloader(Supplier realDownloader) { + this.realDownloader = realDownloader; + } @Override public Certificates loadCertificates(String certOcid) { @@ -43,13 +49,13 @@ public Certificates loadCertificates(String certOcid) { try { if (OciTestUtils.ociRealUsage()) { - return super.loadCertificates(certOcid); + return realDownloader.get().loadCertificates(certOcid); } else { TimeUnit.MILLISECONDS.sleep(1); // make sure metrics timestamp changes Objects.requireNonNull(certOcid); try (InputStream certIs = TestOciCertificatesDownloader.class.getClassLoader().getResourceAsStream("test-keys/serverCert.pem")) { - return OciCertificatesDownloader.create(version, new X509Certificate[] {toCertificate(certIs)}); + return OciCertificatesDownloader.create(version, new X509Certificate[] {DefaultOciCertificatesDownloader.toCertificate(certIs)}); } catch (Exception e) { throw new RuntimeException(e); } @@ -66,13 +72,13 @@ public X509Certificate loadCACertificate(String caCertOcid) { try { if (OciTestUtils.ociRealUsage()) { - return super.loadCACertificate(caCertOcid); + return realDownloader.get().loadCACertificate(caCertOcid); } else { TimeUnit.MILLISECONDS.sleep(1); // make sure metrics timestamp changes Objects.requireNonNull(caCertOcid); try (InputStream caCertIs = TestOciCertificatesDownloader.class.getClassLoader().getResourceAsStream("test-keys/ca.pem")) { - return toCertificate(caCertIs); + return DefaultOciCertificatesDownloader.toCertificate(caCertIs); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciPrivateKeyDownloader.java b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciPrivateKeyDownloader.java index a0383ac4438..e15fc2e1df8 100644 --- a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciPrivateKeyDownloader.java +++ b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestOciPrivateKeyDownloader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -19,19 +19,26 @@ import java.net.URI; import java.security.PrivateKey; import java.util.Objects; +import java.util.function.Supplier; import io.helidon.common.Weight; import io.helidon.common.Weighted; import io.helidon.common.pki.Keys; import io.helidon.config.Config; +import io.helidon.integrations.oci.tls.certificates.spi.OciPrivateKeyDownloader; +import io.helidon.service.registry.Service; -import jakarta.inject.Singleton; - -@Singleton +@Service.Provider @Weight(Weighted.DEFAULT_WEIGHT + 1) -class TestOciPrivateKeyDownloader extends DefaultOciPrivateKeyDownloader { +class TestOciPrivateKeyDownloader implements OciPrivateKeyDownloader { + + static volatile int callCount; - static int callCount; + private final Supplier realDownloader; + + TestOciPrivateKeyDownloader(Supplier realDownloader) { + this.realDownloader = realDownloader; + } @Override public PrivateKey loadKey(String keyOcid, @@ -40,7 +47,7 @@ public PrivateKey loadKey(String keyOcid, try { if (OciTestUtils.ociRealUsage()) { - return super.loadKey(keyOcid, vaultCryptoEndpoint); + return realDownloader.get().loadKey(keyOcid, vaultCryptoEndpoint); } else { Objects.requireNonNull(keyOcid); Objects.requireNonNull(vaultCryptoEndpoint); diff --git a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestingCdiExtension.java b/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestingCdiExtension.java deleted file mode 100644 index 5f2d27751b4..00000000000 --- a/integrations/oci/tls-certificates/src/test/java/io/helidon/integrations/oci/tls/certificates/TestingCdiExtension.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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. - */ - -package io.helidon.integrations.oci.tls.certificates; - -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; - -import io.helidon.config.Config; -import io.helidon.microprofile.cdi.RuntimeStart; - -import jakarta.annotation.Priority; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.BeforeDestroyed; -import jakarta.enterprise.event.Observes; -import jakarta.enterprise.inject.spi.Extension; -import jakarta.inject.Singleton; - -import static jakarta.interceptor.Interceptor.Priority.PLATFORM_AFTER; - -@Singleton -@SuppressWarnings("unused") -// consider relocating this to somewhere under inject -public class TestingCdiExtension implements Extension, LifecycleHook { - static volatile boolean shutdownCalled; - static final AtomicInteger running = new AtomicInteger(); - static final CopyOnWriteArrayList> startupConsumers = new CopyOnWriteArrayList<>(); - static final CopyOnWriteArrayList> shutdownConsumers = new CopyOnWriteArrayList<>(); - - public TestingCdiExtension() { - } - - void starting(@Observes @RuntimeStart Config config) { - running.incrementAndGet(); - startupConsumers.forEach(c -> c.accept(config)); - startupConsumers.clear(); - shutdownCalled = false; - } - - void stopping(@Observes @Priority(PLATFORM_AFTER) @BeforeDestroyed(ApplicationScoped.class) Object event) { - running.decrementAndGet(); - shutdownConsumers.forEach(c -> c.accept(event)); - shutdownCalled = !shutdownConsumers.isEmpty(); - shutdownConsumers.clear(); - } - - @Override - public void registerStartupConsumer(Consumer consumer) { - startupConsumers.add(consumer); - } - - @Override - public void registerShutdownConsumer(Consumer consumer) { - shutdownConsumers.add(consumer); - } - -} diff --git a/integrations/oci/tls-certificates/src/test/resources/oci.yaml b/integrations/oci/tls-certificates/src/test/resources/oci-config.yaml similarity index 91% rename from integrations/oci/tls-certificates/src/test/resources/oci.yaml rename to integrations/oci/tls-certificates/src/test/resources/oci-config.yaml index 9f2ca2ef77b..431734a29db 100644 --- a/integrations/oci/tls-certificates/src/test/resources/oci.yaml +++ b/integrations/oci/tls-certificates/src/test/resources/oci-config.yaml @@ -1,5 +1,5 @@ # -# Copyright (c) 2023 Oracle and/or its affiliates. +# Copyright (c) 2023, 2024 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. From 3f2ad0705e080576fa2b99e13b38cd0966675694 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 16 May 2024 00:12:04 +0200 Subject: [PATCH 12/20] Move secrets config sources to correct directories. Refactor secrets config sources to use Service Registry (from new `helidon-integrations-oci` module) --- integrations/oci/pom.xml | 4 +- .../pom.xml | 9 +- .../AbstractSecretBundleConfigSource.java | 80 ++-- .../OciSecretsConfigSourceProvider.java | 38 +- .../SecretBundleLazyConfigSource.java | 146 ++----- .../SecretBundleNodeConfigSource.java | 365 +++++++----------- .../secrets/configsource/package-info.java | 2 +- .../src/main/java/module-info.java | 8 +- .../secrets/configsource/IsModifiedTest.java | 2 +- .../oci/secrets/configsource/UsageTest.java | 3 +- .../secrets/configsource/ValueNodeTest.java | 2 +- .../src/test/java/logging.properties | 8 +- .../src/test/resources/meta-config.yaml | 3 +- .../pom.xml | 8 +- .../OciSecretsMpMetaConfigProvider.java | 1 - .../secrets/mp/configsource/package-info.java | 2 +- .../src/main/java/module-info.java | 2 +- .../secrets/mp/configsource/UsageTest.java | 3 +- .../src/test/java/logging.properties | 2 +- .../src/test/resources/mp-meta-config.yaml | 3 +- 20 files changed, 261 insertions(+), 430 deletions(-) rename integrations/oci/{oci-secrets-config-source => secrets-config-source}/pom.xml (94%) rename integrations/oci/{oci-secrets-config-source => secrets-config-source}/src/main/java/io/helidon/integrations/oci/secrets/configsource/AbstractSecretBundleConfigSource.java (70%) rename integrations/oci/{oci-secrets-config-source => secrets-config-source}/src/main/java/io/helidon/integrations/oci/secrets/configsource/OciSecretsConfigSourceProvider.java (86%) rename integrations/oci/{oci-secrets-config-source => secrets-config-source}/src/main/java/io/helidon/integrations/oci/secrets/configsource/SecretBundleLazyConfigSource.java (78%) rename integrations/oci/{oci-secrets-config-source => secrets-config-source}/src/main/java/io/helidon/integrations/oci/secrets/configsource/SecretBundleNodeConfigSource.java (76%) rename integrations/oci/{oci-secrets-config-source => secrets-config-source}/src/main/java/io/helidon/integrations/oci/secrets/configsource/package-info.java (95%) rename integrations/oci/{oci-secrets-config-source => secrets-config-source}/src/main/java/module-info.java (83%) rename integrations/oci/{oci-secrets-config-source => secrets-config-source}/src/test/java/io/helidon/integrations/oci/secrets/configsource/IsModifiedTest.java (97%) rename integrations/oci/{oci-secrets-config-source => secrets-config-source}/src/test/java/io/helidon/integrations/oci/secrets/configsource/UsageTest.java (97%) rename integrations/oci/{oci-secrets-config-source => secrets-config-source}/src/test/java/io/helidon/integrations/oci/secrets/configsource/ValueNodeTest.java (96%) rename integrations/oci/{oci-secrets-config-source => secrets-config-source}/src/test/java/logging.properties (84%) rename integrations/oci/{oci-secrets-config-source => secrets-config-source}/src/test/resources/meta-config.yaml (90%) rename integrations/oci/{oci-secrets-mp-config-source => secrets-mp-config-source}/pom.xml (93%) rename integrations/oci/{oci-secrets-mp-config-source => secrets-mp-config-source}/src/main/java/io/helidon/integrations/oci/secrets/mp/configsource/OciSecretsMpMetaConfigProvider.java (99%) rename integrations/oci/{oci-secrets-mp-config-source => secrets-mp-config-source}/src/main/java/io/helidon/integrations/oci/secrets/mp/configsource/package-info.java (95%) rename integrations/oci/{oci-secrets-mp-config-source => secrets-mp-config-source}/src/main/java/module-info.java (96%) rename integrations/oci/{oci-secrets-mp-config-source => secrets-mp-config-source}/src/test/java/io/helidon/integrations/oci/secrets/mp/configsource/UsageTest.java (97%) rename integrations/oci/{oci-secrets-mp-config-source => secrets-mp-config-source}/src/test/java/logging.properties (92%) rename integrations/oci/{oci-secrets-mp-config-source => secrets-mp-config-source}/src/test/resources/mp-meta-config.yaml (91%) diff --git a/integrations/oci/pom.xml b/integrations/oci/pom.xml index 4c76d5d5817..cf8b46b4133 100644 --- a/integrations/oci/pom.xml +++ b/integrations/oci/pom.xml @@ -35,8 +35,8 @@ oci metrics - oci-secrets-config-source - oci-secrets-mp-config-source + secrets-config-source + secrets-mp-config-source sdk tls-certificates diff --git a/integrations/oci/oci-secrets-config-source/pom.xml b/integrations/oci/secrets-config-source/pom.xml similarity index 94% rename from integrations/oci/oci-secrets-config-source/pom.xml rename to integrations/oci/secrets-config-source/pom.xml index 7c3965e2432..4c864284738 100644 --- a/integrations/oci/oci-secrets-config-source/pom.xml +++ b/integrations/oci/secrets-config-source/pom.xml @@ -50,8 +50,12 @@ helidon-config - io.helidon.integrations.oci.sdk - helidon-integrations-oci-sdk-runtime + io.helidon.service + helidon-service-registry + + + io.helidon.integrations.oci + helidon-integrations-oci com.oracle.oci.sdk @@ -92,7 +96,6 @@ - io.helidon.config helidon-config-yaml-mp diff --git a/integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/AbstractSecretBundleConfigSource.java b/integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/AbstractSecretBundleConfigSource.java similarity index 70% rename from integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/AbstractSecretBundleConfigSource.java rename to integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/AbstractSecretBundleConfigSource.java index f0cc210d931..3e3bf14ffb3 100644 --- a/integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/AbstractSecretBundleConfigSource.java +++ b/integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/AbstractSecretBundleConfigSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -27,12 +27,12 @@ import io.helidon.config.Config; import io.helidon.config.ConfigException; import io.helidon.config.spi.ConfigNode.ValueNode; +import io.helidon.service.registry.GlobalServiceRegistry; -import com.oracle.bmc.auth.BasicAuthenticationDetailsProvider; +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; import com.oracle.bmc.secrets.Secrets; import com.oracle.bmc.secrets.SecretsClient; -import static io.helidon.integrations.oci.sdk.runtime.OciExtension.ociAuthenticationProvider; import static java.lang.System.Logger.Level.WARNING; import static java.nio.charset.StandardCharsets.UTF_8; @@ -41,18 +41,15 @@ * and {@link SecretBundleNodeConfigSource}. * * @param the type of {@link AbstractConfigSourceBuilder} subclass used to build instances of this class - * * @see SecretBundleLazyConfigSource - * * @see SecretBundleNodeConfigSource */ -public abstract sealed class AbstractSecretBundleConfigSource> - extends AbstractConfigSource - permits SecretBundleLazyConfigSource, SecretBundleNodeConfigSource { - - private static final Logger LOGGER = System.getLogger(AbstractSecretBundleConfigSource.class.getName()); +abstract sealed class AbstractSecretBundleConfigSource> + extends AbstractConfigSource + permits SecretBundleLazyConfigSource, SecretBundleNodeConfigSource { static final String VAULT_OCID_PROPERTY_NAME = "vault-ocid"; + private static final Logger LOGGER = System.getLogger(AbstractSecretBundleConfigSource.class.getName()); /** * Creates a new {@link AbstractSecretBundleConfigSource}. @@ -74,8 +71,8 @@ static ValueNode valueNode(String base64EncodedContent, Base64.Decoder base64Dec * @param the builder subclass */ public abstract static sealed class Builder> - extends AbstractConfigSourceBuilder - permits SecretBundleLazyConfigSource.Builder, SecretBundleNodeConfigSource.Builder { + extends AbstractConfigSourceBuilder + permits SecretBundleLazyConfigSource.Builder, SecretBundleNodeConfigSource.Builder { private Supplier secretsSupplier; @@ -90,36 +87,39 @@ protected Builder() { this.secretsSupplier = () -> scb.build(adpSupplier().get()); } + static LazyValue adpSupplier() { + return LazyValue.create(() -> GlobalServiceRegistry.registry() + .get(AbstractAuthenticationDetailsProvider.class)); + } + /** * Configures this {@link Builder} from the supplied meta-configuration. * * @param metaConfig the meta-configuration; must not be {@code null} - * * @return this {@link Builder} - * - * @exception NullPointerException if {@code metaConfig} is {@code null} + * @throws NullPointerException if {@code metaConfig} is {@code null} */ @Override // AbstractConfigSourceBuilder public B config(Config metaConfig) { metaConfig.get("change-watcher") - .asNode() - .ifPresent(n -> { + .asNode() + .ifPresent(n -> { throw new ConfigException("Invalid meta-configuration key: change-watcher: " - + "Change watching is not supported by " - + this.getClass().getName() + " instances"); + + "Change watching is not supported by " + + this.getClass().getName() + " instances"); }); metaConfig.get("vault-ocid") - .asString() - .filter(Predicate.not(String::isBlank)) - .ifPresentOrElse(this::vaultOcid, - () -> { - if (LOGGER.isLoggable(WARNING)) { - LOGGER.log(WARNING, - "No meta-configuration value supplied for " - + metaConfig.key().toString() + "." + VAULT_OCID_PROPERTY_NAME - + "); resulting ConfigSource will be empty"); - } - }); + .asString() + .filter(Predicate.not(String::isBlank)) + .ifPresentOrElse(this::vaultOcid, + () -> { + if (LOGGER.isLoggable(WARNING)) { + LOGGER.log(WARNING, + "No meta-configuration value supplied for " + + metaConfig.key().toString() + "." + VAULT_OCID_PROPERTY_NAME + + "); resulting ConfigSource will be empty"); + } + }); return super.config(metaConfig); } @@ -128,10 +128,8 @@ public B config(Config metaConfig) { * retrieve values. * * @param vaultOcid a valid OCID identifying an OCI vault; must not be {@code null} - * * @return this {@link Builder} - * - * @exception NullPointerException if {@code vaultId} is {@code null} + * @throws NullPointerException if {@code vaultId} is {@code null} */ @SuppressWarnings("unchecked") public B vaultOcid(String vaultOcid) { @@ -139,19 +137,13 @@ public B vaultOcid(String vaultOcid) { return (B) this; } - String vaultOcid() { - return this.vaultOcid; - } - /** * Uses the supplied {@link Supplier} of {@link Secrets} instances, instead of the default one, for * communicating with the OCI Secrets Retrieval API. * * @param secretsSupplier the non-default {@link Supplier} to use; must not be {@code null} - * * @return this {@link Builder} - * - * @exception NullPointerException if {@code secretsSupplier} is {@code null} + * @throws NullPointerException if {@code secretsSupplier} is {@code null} */ @SuppressWarnings("unchecked") public B secretsSupplier(Supplier secretsSupplier) { @@ -159,12 +151,12 @@ public B secretsSupplier(Supplier secretsSupplier) { return (B) this; } - Supplier secretsSupplier() { - return this.secretsSupplier; + String vaultOcid() { + return this.vaultOcid; } - static LazyValue adpSupplier() { - return LazyValue.create(() -> (BasicAuthenticationDetailsProvider) ociAuthenticationProvider().get()); + Supplier secretsSupplier() { + return this.secretsSupplier; } } diff --git a/integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/OciSecretsConfigSourceProvider.java b/integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/OciSecretsConfigSourceProvider.java similarity index 86% rename from integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/OciSecretsConfigSourceProvider.java rename to integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/OciSecretsConfigSourceProvider.java index 3f82b304047..78f27678bc4 100644 --- a/integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/OciSecretsConfigSourceProvider.java +++ b/integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/OciSecretsConfigSourceProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -40,13 +40,12 @@ * *
  • Ensure you have an authentication mechanism set up to connect to OCI (e.g. a valid OCI configuration - * file). Authentication with OCI is accomplished via the {@link - * io.helidon.integrations.oci.sdk.runtime.OciExtension} class; please see its documentation for how and when to set up - * an {@code oci.yaml} classpath resource to further refine the mechanism of authentication.
  • + * file). Authentication with OCI is accomplished via the {@code helidon-integrations-oci} module; + * please see its documentation. * - *
  • Ensure there is a classpath resource present named {@code meta-config.yaml}.
  • + *
  • Ensure there is a classpath resource, or a file present named {@code meta-config.yaml}.
  • * - *
  • Ensure the {@code meta-config.yaml} classpath resource contains a {@code sources} element with a {@code type} of + *
  • Ensure the {@code meta-config.yaml} contains a {@code sources} element with a {@code type} of * {@code oci-secrets} that looks similar to the following, substituting values as appropriate:
    sources:
      *  - type: 'oci-secrets'
      *    properties:
    @@ -62,36 +61,17 @@
     @Weight(300D) // a higher weight than the default (100D)
     public final class OciSecretsConfigSourceProvider implements ConfigSourceProvider {
     
    -
    -    /*
    -     * Static fields.
    -     */
    -
    -
         private static final Set SUPPORTED_TYPES = Set.of("oci-secrets");
     
    -
    -    /*
    -     * Constructors.
    -     */
    -
    -
         /**
          * Creates a new {@link OciSecretsConfigSourceProvider}.
          *
          * @deprecated For use by {@link java.util.ServiceLoader} only.
          */
    -    @Deprecated // For use by java.util.ServiceLoader only.
    +    @Deprecated
         public OciSecretsConfigSourceProvider() {
    -        super();
         }
     
    -
    -    /*
    -     * Instance methods.
    -     */
    -
    -
         /**
          * Creates and returns a non-{@code null} {@link AbstractConfigSource} implementation that sources its values from
          * an Oracle Cloud Infrastructure (OCI)  supported() {
             return SUPPORTED_TYPES;
    @@ -155,7 +135,7 @@ public Set supported() {
          *
          * @deprecated For use by the Helidon Config subsystem only.
          */
    -    @Deprecated // For use by the Helidon Config subsystem only.
    +    @Deprecated
         @Override // ConfigSourceProvider
         public boolean supports(String type) {
             return this.supported().contains(type);
    diff --git a/integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/SecretBundleLazyConfigSource.java b/integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/SecretBundleLazyConfigSource.java
    similarity index 78%
    rename from integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/SecretBundleLazyConfigSource.java
    rename to integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/SecretBundleLazyConfigSource.java
    index e695f48f52d..da5e907814c 100644
    --- a/integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/SecretBundleLazyConfigSource.java
    +++ b/integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/SecretBundleLazyConfigSource.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2023 Oracle and/or its affiliates.
    + * Copyright (c) 2023, 2024 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.
    @@ -45,30 +45,12 @@
      * href="https://docs.oracle.com/en-us/iaas/tools/java/latest/com/oracle/bmc/vault/package-summary.html">Vault APIs.
      */
     public final class SecretBundleLazyConfigSource
    -    extends AbstractSecretBundleConfigSource
    -    implements LazyConfigSource {
    -
    -
    -    /*
    -     * Static fields.
    -     */
    -
    +        extends AbstractSecretBundleConfigSource
    +        implements LazyConfigSource {
     
         private static final Logger LOGGER = System.getLogger(SecretBundleLazyConfigSource.class.getName());
     
    -
    -    /*
    -     * Instance fields.
    -     */
    -
    -
    -    private final Function> nodeFunction;
    -
    -
    -    /*
    -     * Constructors.
    -     */
    -
    +    private final Function> nodeFunction;
     
         private SecretBundleLazyConfigSource(Builder b) {
             super(b);
    @@ -81,24 +63,6 @@ private SecretBundleLazyConfigSource(Builder b) {
             }
         }
     
    -
    -    /*
    -     * Instance methods.
    -     */
    -
    -
    -    @Deprecated // For use by the Helidon Config subsystem only.
    -    @Override // NodeConfigSource
    -    public Optional node(String key) {
    -        return this.nodeFunction.apply(key);
    -    }
    -
    -
    -    /*
    -     * Static methods.
    -     */
    -
    -
         /**
          * Creates and returns a new {@link Builder} for {@linkplain Builder#build() building} {@link
          * SecretBundleLazyConfigSource} instances.
    @@ -109,6 +73,27 @@ public static Builder builder() {
             return new Builder();
         }
     
    +    static Optional node(Supplier secretBundleContentDetailsSupplier) {
    +        Object secretBundleContentDetails = secretBundleContentDetailsSupplier.get();
    +        if (secretBundleContentDetails instanceof Base64SecretBundleContentDetails base64SecretBundleContentDetails) {
    +            return Optional.of(valueNode(base64SecretBundleContentDetails.getContent(), Base64.getDecoder()));
    +        }
    +        return Optional.empty();
    +    }
    +
    +    static GetSecretBundleByNameRequest request(String vaultOcid, String secretName) {
    +        return GetSecretBundleByNameRequest.builder()
    +                .vaultId(vaultOcid)
    +                .secretName(secretName)
    +                .build();
    +    }
    +
    +    @Deprecated // For use by the Helidon Config subsystem only.
    +    @Override // NodeConfigSource
    +    public Optional node(String key) {
    +        return this.nodeFunction.apply(key);
    +    }
    +
         private static Optional node(Pattern acceptPattern,
                                                  LazyValue secretsSupplier,
                                                  String vaultOcid,
    @@ -116,9 +101,9 @@ private static Optional node(Pattern acceptPattern,
             if (!acceptPattern.matcher(secretName).matches()) {
                 if (LOGGER.isLoggable(DEBUG)) {
                     LOGGER.log(DEBUG, "Ignoring ConfigNode request for name "
    -                           + secretName
    -                           + " because it was not matched by "
    -                           + acceptPattern);
    +                        + secretName
    +                        + " because it was not matched by "
    +                        + acceptPattern);
                 }
                 return Optional.empty();
             }
    @@ -140,75 +125,28 @@ private static Object secretBundleContentDetails(Secrets s, String vaultOcid, St
             }
         }
     
    -    static Optional node(Supplier secretBundleContentDetailsSupplier) {
    -        Object secretBundleContentDetails = secretBundleContentDetailsSupplier.get();
    -        if (secretBundleContentDetails instanceof Base64SecretBundleContentDetails base64SecretBundleContentDetails) {
    -            return Optional.of(valueNode(base64SecretBundleContentDetails.getContent(), Base64.getDecoder()));
    -        }
    -        return Optional.empty();
    -    }
    -
    -    static GetSecretBundleByNameRequest request(String vaultOcid, String secretName) {
    -        return GetSecretBundleByNameRequest.builder()
    -            .vaultId(vaultOcid)
    -            .secretName(secretName)
    -            .build();
    -    }
    -
    -
    -    /*
    -     * Inner and nested classes.
    -     */
    -
    -
         /**
          * An {@link AbstractSecretBundleConfigSource.Builder} that {@linkplain #build() builds} {@link
          * SecretBundleLazyConfigSource} instances.
          */
         public static final class Builder extends AbstractSecretBundleConfigSource.Builder {
     
    -
    -        /*
    -         * Static fields.
    -         */
    -
    -
             private static final Pattern ACCEPT_EVERYTHING_PATTERN = Pattern.compile("^.*$");
     
    -
    -        /*
    -         * Instance fields.
    -         */
    -
    -
             private Pattern acceptPattern;
     
    -
    -        /*
    -         * Constructors.
    -         */
    -
    -
             private Builder() {
                 super();
                 this.acceptPattern = ACCEPT_EVERYTHING_PATTERN;
             }
     
    -
    -        /*
    -         * Instance methods.
    -         */
    -
    -
             /**
              * Sets the {@link Pattern} that will dictate which configuration property names are allowed to reach a {@link
              * SecretBundleLazyConfigSource} instance.
              *
              * @param acceptPattern the {@link Pattern}
    -         *
              * @return this {@link Builder}
    -         *
    -         * @exception NullPointerException if {@code acceptPattern} is {@code null}
    +         * @throws NullPointerException if {@code acceptPattern} is {@code null}
              */
             public Builder acceptPattern(Pattern acceptPattern) {
                 this.acceptPattern = Objects.requireNonNull(acceptPattern, "acceptPattern");
    @@ -229,29 +167,25 @@ public SecretBundleLazyConfigSource build() {
              * Configures this {@link Builder} from the supplied meta-configuration.
              *
              * @param metaConfig the meta-configuration; must not be {@code null}
    -         *
              * @return this {@link Builder}
    -         *
    -         * @exception io.helidon.config.ConfigException if a {@code change-watcher} or {@code polling-strategy} is
    -         * specified
    -         *
    -         * @exception NullPointerException if {@code metaConfig} is {@code null}
    -         *
    -         * @exception java.util.regex.PatternSyntaxException if the {@code accept-pattern} key's value could not be
    -         * {@linkplain Pattern#compile(String) compiled}
    +         * @throws io.helidon.config.ConfigException      if a {@code change-watcher} or {@code polling-strategy} is
    +         *                                                specified
    +         * @throws NullPointerException                   if {@code metaConfig} is {@code null}
    +         * @throws java.util.regex.PatternSyntaxException if the {@code accept-pattern} key's value could not be
    +         *                                                {@linkplain Pattern#compile(String) compiled}
              */
             @Override // AbstractSecretBundleConfigSource.Builder
             public Builder config(Config metaConfig) {
                 metaConfig.get("polling-strategy")
    -                .asNode()
    -                .ifPresent(n -> {
    +                    .asNode()
    +                    .ifPresent(n -> {
                             throw new ConfigException("Invalid meta-configuration key: polling-strategy: "
    -                                                  + "Polling is not supported by "
    -                                                  + this.getClass().getName() + " instances");
    +                                                          + "Polling is not supported by "
    +                                                          + this.getClass().getName() + " instances");
                         });
                 metaConfig.get("accept-pattern")
    -                .asString()
    -                .ifPresent(s -> this.acceptPattern(Pattern.compile(s)));
    +                    .asString()
    +                    .ifPresent(s -> this.acceptPattern(Pattern.compile(s)));
                 return super.config(metaConfig);
             }
     
    diff --git a/integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/SecretBundleNodeConfigSource.java b/integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/SecretBundleNodeConfigSource.java
    similarity index 76%
    rename from integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/SecretBundleNodeConfigSource.java
    rename to integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/SecretBundleNodeConfigSource.java
    index 2c987a9edd6..27c7bcf7d60 100644
    --- a/integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/SecretBundleNodeConfigSource.java
    +++ b/integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/SecretBundleNodeConfigSource.java
    @@ -69,37 +69,14 @@
      * href="https://docs.oracle.com/en-us/iaas/tools/java/latest/com/oracle/bmc/vault/package-summary.html">Vault APIs.
      */
     public final class SecretBundleNodeConfigSource
    -    extends AbstractSecretBundleConfigSource
    -    implements NodeConfigSource, PollableSource {
    -
    -
    -    /*
    -     * Static fields.
    -     */
    -
    -
    -    private static final Optional ABSENT_NODE_CONTENT =
    -        Optional.of(NodeContent.builder().node(ObjectNode.empty()).build());
    +        extends AbstractSecretBundleConfigSource
    +        implements NodeConfigSource, PollableSource {
     
         private static final String COMPARTMENT_OCID_PROPERTY_NAME = "compartment-ocid";
    -
         private static final Logger LOGGER = System.getLogger(SecretBundleNodeConfigSource.class.getName());
     
    -
    -    /*
    -     * Instance fields.
    -     */
    -
    -
    -    private final Supplier> loader;
    -
    -    private final Supplier stamper;
    -
    -
    -    /*
    -     * Constructors.
    -     */
    -
    +    private final Supplier> loader;
    +    private final Supplier stamper;
     
         private SecretBundleNodeConfigSource(Builder b) {
             super(b);
    @@ -107,14 +84,14 @@ private SecretBundleNodeConfigSource(Builder b) {
             Supplier vaultsSupplier = Objects.requireNonNull(b.vaultsSupplier, "b.vaultsSupplier");
             String vaultOcid = b.vaultOcid();
             if (b.compartmentOcid == null || vaultOcid == null) {
    -            this.loader = this::absentNodeContent;
    +            this.loader = Optional::empty;
                 this.stamper = Stamp::new;
             } else {
                 ListSecretsRequest listSecretsRequest = ListSecretsRequest.builder()
    -                .compartmentId(b.compartmentOcid)
    -                .lifecycleState(LifecycleState.Active)
    -                .vaultId(vaultOcid)
    -                .build();
    +                    .compartmentId(b.compartmentOcid)
    +                    .lifecycleState(LifecycleState.Active)
    +                    .vaultId(vaultOcid)
    +                    .build();
                 this.loader = () -> this.load(vaultsSupplier, secretsSupplier, listSecretsRequest);
                 this.stamper = () -> toStamp(secretSummaries(vaultsSupplier, listSecretsRequest), secretsSupplier);
             }
    @@ -125,104 +102,17 @@ private SecretBundleNodeConfigSource(Builder b) {
          * Instance methods.
          */
     
    -
         /**
    -     * Returns {@code true} if the values in this {@link SecretBundleNodeConfigSource} have been modified.
    -     *
    -     * @param lastKnownStamp a {@link Stamp}
    +     * Creates and returns a new {@link Builder} for {@linkplain Builder#build() building} {@link
    +     * SecretBundleNodeConfigSource} instances.
          *
    -     * @return {@code true} if modified
    -     */
    -    @Deprecated // For use by the Helidon Config subsystem only.
    -    @Override // PollableSource
    -    public boolean isModified(Stamp lastKnownStamp) {
    -        Stamp stamp = this.stamper.get();
    -        if (!stamp.eTags().equals(lastKnownStamp.eTags())) {
    -            return true;
    -        }
    -        return stamp.earliestExpiration().isBefore(lastKnownStamp.earliestExpiration());
    -    }
    -
    -    @Deprecated // For use by the Helidon Config subsystem only.
    -    @Override // NodeConfigSource
    -    public Optional load() {
    -        return this.loader.get();
    -    }
    -
    -    @Deprecated // For use by the Helidon Config subsystem only.
    -    @Override // PollableSource
    -    public Optional pollingStrategy() {
    -        return super.pollingStrategy();
    -    }
    -
    -    private Optional absentNodeContent() {
    -        return ABSENT_NODE_CONTENT;
    -    }
    -
    -    private Optional load(Supplier vaultsSupplier,
    -                                       Supplier secretsSupplier,
    -                                       ListSecretsRequest listSecretsRequest) {
    -        Collection secretSummaries = secretSummaries(vaultsSupplier, listSecretsRequest);
    -        return this.load(secretSummaries, secretsSupplier);
    -    }
    -
    -    private Optional load(Collection secretSummaries,
    -                                       Supplier secretsSupplier) {
    -        if (secretSummaries.isEmpty()) {
    -            return this.absentNodeContent();
    -        }
    -        ConcurrentMap valueNodes = new ConcurrentHashMap<>();
    -        Set eTags = ConcurrentHashMap.newKeySet();
    -        Collection> tasks = new ArrayList<>(secretSummaries.size());
    -        Base64.Decoder decoder = Base64.getDecoder();
    -        Secrets secrets = secretsSupplier.get();
    -        for (SecretSummary ss : secretSummaries) {
    -            tasks.add(task(valueNodes::put,
    -                           eTags::add,
    -                           ss.getSecretName(),
    -                           secrets::getSecretBundle,
    -                           ss.getId(),
    -                           decoder));
    -        }
    -        completeTasks(tasks, secrets);
    -        ObjectNode.Builder objectNodeBuilder = ObjectNode.builder();
    -        for (Entry e : valueNodes.entrySet()) {
    -            objectNodeBuilder.addValue(e.getKey(), e.getValue());
    -        }
    -        return Optional.of(NodeContent.builder()
    -                           .node(objectNodeBuilder.build())
    -                           .stamp(toStamp(secretSummaries, eTags))
    -                           .build());
    -    }
    -
    -
    -    /*
    -     * Static methods.
    +     * @return a new {@link Builder}
          */
    -
    -
    -    private static Stamp toStamp(Collection secretSummaries,
    -                                 Supplier secretsSupplier) {
    -        if (secretSummaries.isEmpty()) {
    -            return new Stamp();
    -        }
    -        Set eTags = ConcurrentHashMap.newKeySet();
    -        Collection> tasks = new ArrayList<>(secretSummaries.size());
    -        Secrets secrets = secretsSupplier.get();
    -        for (SecretSummary ss : secretSummaries) {
    -            if (ss.getLifecycleState() == LifecycleState.Active) {
    -                tasks.add(() -> {
    -                        GetSecretBundleResponse response = secrets.getSecretBundle(request(ss.getId()));
    -                        eTags.add(response.getEtag());
    -                        return null; // Callable; null is the only possible return value
    -                    });
    -            }
    -        }
    -        completeTasks(tasks, secrets);
    -        return toStamp(secretSummaries, eTags);
    +    public static Builder builder() {
    +        return new Builder();
         }
     
    -    static Stamp toStamp(Collection secretSummaries, Set eTags) {
    +    static Stamp toStamp(Collection secretSummaries, Set eTags) {
             if (secretSummaries.isEmpty()) {
                 return new Stamp();
             }
    @@ -255,34 +145,79 @@ static Stamp toStamp(Collection secretSummaries, Set task(BiConsumer valueNodes,
    +                               Consumer eTags,
    +                               String secretName,
    +                               Function f,
    +                               String secretId,
    +                               Base64.Decoder base64Decoder) {
    +        return () -> {
    +            valueNodes.accept(secretName,
    +                              valueNode(request -> secretBundleContentDetails(request, f, eTags),
    +                                        secretId,
    +                                        base64Decoder));
    +            return null; // Callable; null is the only possible return value
    +        };
    +    }
    +
         /**
    -     * Creates and returns a new {@link Builder} for {@linkplain Builder#build() building} {@link
    -     * SecretBundleNodeConfigSource} instances.
    +     * Returns {@code true} if the values in this {@link SecretBundleNodeConfigSource} have been modified.
          *
    -     * @return a new {@link Builder}
    +     * @param lastKnownStamp a {@link Stamp}
    +     * @return {@code true} if modified
          */
    -    public static Builder builder() {
    -        return new Builder();
    +    @Deprecated // For use by the Helidon Config subsystem only.
    +    @Override // PollableSource
    +    public boolean isModified(Stamp lastKnownStamp) {
    +        Stamp stamp = this.stamper.get();
    +        if (!stamp.eTags().equals(lastKnownStamp.eTags())) {
    +            return true;
    +        }
    +        return stamp.earliestExpiration().isBefore(lastKnownStamp.earliestExpiration());
         }
     
    -    private static void closeUnchecked(AutoCloseable autoCloseable) {
    -        try {
    -            autoCloseable.close();
    -        } catch (RuntimeException e) {
    -            throw e;
    -        } catch (InterruptedException e) {
    -            // (Can legally be thrown by any AutoCloseable. Must preserve interrupt status.)
    -            Thread.currentThread().interrupt();
    -            throw new IllegalStateException(e.getMessage(), e);
    -        } catch (Exception e) {
    -            // (Can legally be thrown by any AutoCloseable.)
    -            throw new IllegalStateException(e.getMessage(), e);
    +    @Deprecated // For use by the Helidon Config subsystem only.
    +    @Override // NodeConfigSource
    +    public Optional load() {
    +        return this.loader.get();
    +    }
    +
    +    @Deprecated // For use by the Helidon Config subsystem only.
    +    @Override // PollableSource
    +    public Optional pollingStrategy() {
    +        return super.pollingStrategy();
    +    }
    +
    +    private static Stamp toStamp(Collection secretSummaries,
    +                                 Supplier secretsSupplier) {
    +        if (secretSummaries.isEmpty()) {
    +            return new Stamp();
             }
    +        Set eTags = ConcurrentHashMap.newKeySet();
    +        Collection> tasks = new ArrayList<>(secretSummaries.size());
    +        Secrets secrets = secretsSupplier.get();
    +        for (SecretSummary ss : secretSummaries) {
    +            if (ss.getLifecycleState() == LifecycleState.Active) {
    +                tasks.add(() -> {
    +                    GetSecretBundleResponse response = secrets.getSecretBundle(request(ss.getId()));
    +                    eTags.add(response.getEtag());
    +                    return null; // Callable; null is the only possible return value
    +                });
    +            }
    +        }
    +        completeTasks(tasks, secrets);
    +        return toStamp(secretSummaries, eTags);
         }
     
    -    private static void completeTasks(Collection> tasks, AutoCloseable autoCloseable) {
    +    private static void completeTasks(Collection> tasks, AutoCloseable autoCloseable) {
             try (ExecutorService es = newVirtualThreadPerTaskExecutor();
    -             autoCloseable) {
    +                autoCloseable) {
                 completeTasks(es, tasks);
             } catch (RuntimeException e) {
                 throw e;
    @@ -296,7 +231,7 @@ private static void completeTasks(Collection> tasks, Au
             }
         }
     
    -    private static void completeTasks(ExecutorService es, Collection> tasks) {
    +    private static void completeTasks(ExecutorService es, Collection> tasks) {
             RuntimeException re = null;
             for (Future future : invokeAllUnchecked(es, tasks)) {
                 try {
    @@ -325,7 +260,7 @@ private static  T futureGetUnchecked(Future future) {
             }
         }
     
    -    private static  List> invokeAllUnchecked(ExecutorService es, Collection> tasks) {
    +    private static  List> invokeAllUnchecked(ExecutorService es, Collection> tasks) {
             try {
                 return es.invokeAll(tasks);
             } catch (InterruptedException e) {
    @@ -334,12 +269,6 @@ private static  List> invokeAllUnchecked(ExecutorService es, Collec
             }
         }
     
    -    static boolean isModified(Stamp pollStamp, Stamp stamp) {
    -        return
    -            !pollStamp.eTags().equals(stamp.eTags())
    -            || stamp.earliestExpiration().isBefore(pollStamp.earliestExpiration());
    -    }
    -
         private static GetSecretBundleRequest request(String secretId) {
             return GetSecretBundleRequest.builder().secretId(secretId).build();
         }
    @@ -347,8 +276,8 @@ private static GetSecretBundleRequest request(String secretId) {
         // Suppress "[try] auto-closeable resource Vaults has a member method close() that could throw
         // InterruptedException" since we handle it.
         @SuppressWarnings("try")
    -    private static Collection secretSummaries(Supplier vaultsSupplier,
    -                                                                       ListSecretsRequest listSecretsRequest) {
    +    private static Collection secretSummaries(Supplier vaultsSupplier,
    +                                                             ListSecretsRequest listSecretsRequest) {
             try (Vaults v = vaultsSupplier.get()) {
                 return v.listSecrets(listSecretsRequest).getItems();
             } catch (RuntimeException e) {
    @@ -363,31 +292,16 @@ private static Collection secretSummaries(Supplier task(BiConsumer valueNodes,
    -                               Consumer eTags,
    -                               String secretName,
    -                               Function f,
    -                               String secretId,
    -                               Base64.Decoder base64Decoder) {
    -        return () -> {
    -            valueNodes.accept(secretName,
    -                              valueNode(request -> secretBundleContentDetails(request, f, eTags),
    -                                        secretId,
    -                                        base64Decoder));
    -            return null; // Callable; null is the only possible return value
    -        };
    -    }
    -
         private static Base64SecretBundleContentDetails
    -        secretBundleContentDetails(GetSecretBundleRequest request,
    -                                   Function f,
    -                                   Consumer eTags) {
    +    secretBundleContentDetails(GetSecretBundleRequest request,
    +                               Function f,
    +                               Consumer eTags) {
             GetSecretBundleResponse response = f.apply(request);
             eTags.accept(response.getEtag());
             return (Base64SecretBundleContentDetails) response.getSecretBundle().getSecretBundleContent();
         }
     
    -    private static ValueNode valueNode(Function f,
    +    private static ValueNode valueNode(Function f,
                                            String secretId,
                                            Base64.Decoder base64Decoder) {
             return valueNode(f.apply(request(secretId)), base64Decoder);
    @@ -397,46 +311,57 @@ private static ValueNode valueNode(Base64SecretBundleContentDetails details, Bas
             return valueNode(details.getContent(), base64Decoder);
         }
     
    +    private Optional load(Supplier vaultsSupplier,
    +                                       Supplier secretsSupplier,
    +                                       ListSecretsRequest listSecretsRequest) {
    +        Collection secretSummaries = secretSummaries(vaultsSupplier, listSecretsRequest);
    +        return this.load(secretSummaries, secretsSupplier);
    +    }
     
    -    /*
    -     * Inner and nested classes.
    -     */
    -
    +    private Optional load(Collection secretSummaries,
    +                                       Supplier secretsSupplier) {
    +        if (secretSummaries.isEmpty()) {
    +            return Optional.empty();
    +        }
    +        ConcurrentMap valueNodes = new ConcurrentHashMap<>();
    +        Set eTags = ConcurrentHashMap.newKeySet();
    +        Collection> tasks = new ArrayList<>();
    +        Base64.Decoder decoder = Base64.getDecoder();
    +        Secrets secrets = secretsSupplier.get();
    +        for (SecretSummary ss : secretSummaries) {
    +            tasks.add(task(valueNodes::put,
    +                           eTags::add,
    +                           ss.getSecretName(),
    +                           secrets::getSecretBundle,
    +                           ss.getId(),
    +                           decoder));
    +        }
    +        completeTasks(tasks, secrets);
    +        ObjectNode.Builder objectNodeBuilder = ObjectNode.builder();
    +        for (Entry e : valueNodes.entrySet()) {
    +            objectNodeBuilder.addValue(e.getKey(), e.getValue());
    +        }
    +        return Optional.of(NodeContent.builder()
    +                                   .node(objectNodeBuilder.build())
    +                                   .stamp(toStamp(secretSummaries, eTags))
    +                                   .build());
    +    }
     
         /**
          * An {@link AbstractConfigSourceBuilder} that {@linkplain #build() builds} {@link SecretBundleNodeConfigSource}
          * instances.
          */
    -    // public static final class Builder extends AbstractConfigSourceBuilder {
         public static final class Builder extends AbstractSecretBundleConfigSource.Builder {
     
    -        /*
    -         * Instance fields.
    -         */
    -
    -
             private String compartmentOcid;
    -
             private Supplier vaultsSupplier;
     
    -
    -        /*
    -         * Constructors.
    -         */
    -
    -
             private Builder() {
                 super();
                 VaultsClient.Builder vcb = VaultsClient.builder();
                 this.vaultsSupplier = () -> vcb.build(adpSupplier().get());
             }
     
    -
    -        /*
    -         * Instance methods.
    -         */
    -
    -
             /**
              * Creates and returns a new {@link SecretBundleNodeConfigSource} instance initialized from the state of this
              * {@link Builder}.
    @@ -452,10 +377,8 @@ public SecretBundleNodeConfigSource build() {
              * SecretBundleNodeConfigSource} will retrieve values.
              *
              * @param compartmentOcid a valid OCID identifying an OCI compartment; must not be {@code null}
    -         *
              * @return this {@link Builder}
    -         *
    -         * @exception NullPointerException if {@code compartmentId} is {@code null}
    +         * @throws NullPointerException if {@code compartmentId} is {@code null}
              */
             public Builder compartmentOcid(String compartmentOcid) {
                 this.compartmentOcid = Objects.requireNonNull(compartmentOcid, "compartmentOcid");
    @@ -466,25 +389,24 @@ public Builder compartmentOcid(String compartmentOcid) {
              * Configures this {@link Builder} from the supplied meta-configuration.
              *
              * @param metaConfig the meta-configuration; must not be {@code null}
    -         *
              * @return this {@link Builder}
    -         *
    -         * @exception NullPointerException if {@code metaConfig} is {@code null}
    +         * @throws NullPointerException if {@code metaConfig} is {@code null}
              */
             @Override // AbstractConfigSourceBuilder
             public Builder config(Config metaConfig) {
                 metaConfig.get("compartment-ocid")
    -                .asString()
    -                .filter(Predicate.not(String::isBlank))
    -                .ifPresentOrElse(this::compartmentOcid,
    -                                 () -> {
    -                                     if (LOGGER.isLoggable(WARNING)) {
    -                                         LOGGER.log(WARNING,
    -                                                    "No meta-configuration value supplied for "
    -                                                    + metaConfig.key().toString() + "." + COMPARTMENT_OCID_PROPERTY_NAME
    -                                                    + "); resulting ConfigSource will be empty");
    -                                     }
    -                                 });
    +                    .asString()
    +                    .filter(Predicate.not(String::isBlank))
    +                    .ifPresentOrElse(this::compartmentOcid,
    +                                     () -> {
    +                                         if (LOGGER.isLoggable(WARNING)) {
    +                                             LOGGER.log(WARNING,
    +                                                        "No meta-configuration value supplied for "
    +                                                                + metaConfig.key()
    +                                                                .toString() + "." + COMPARTMENT_OCID_PROPERTY_NAME
    +                                                                + "); resulting ConfigSource will be empty");
    +                                         }
    +                                     });
                 return super.config(metaConfig);
             }
     
    @@ -498,13 +420,9 @@ public Builder config(Config metaConfig) {
              * super.pollingStrategy(pollingStrategy)} and returns the result.

    * * @param pollingStrategy a {@link PollingStrategy}; must not be {@code null} - * * @return this {@link Builder} - * - * @exception NullPointerException if {@code pollingStrategy} is {@code null} - * + * @throws NullPointerException if {@code pollingStrategy} is {@code null} * @see PollableSource - * * @see PollingStrategy */ public Builder pollingStrategy(PollingStrategy pollingStrategy) { @@ -516,10 +434,8 @@ public Builder pollingStrategy(PollingStrategy pollingStrategy) { * communicating with the OCI Vaults API. * * @param vaultsSupplier the non-default {@link Supplier} to use; must not be {@code null} - * * @return this {@link Builder} - * - * @exception NullPointerException if {@code vaultsSupplier} is {@code null} + * @throws NullPointerException if {@code vaultsSupplier} is {@code null} */ public Builder vaultsSupplier(Supplier vaultsSupplier) { this.vaultsSupplier = Objects.requireNonNull(vaultsSupplier, "vaultsSupplier"); @@ -532,10 +448,9 @@ public Builder vaultsSupplier(Supplier vaultsSupplier) { * A pairing of a {@link Set} of entity tags with an {@link Instant} identifying the earliest expiration * of a Secret indirectly identified by one of those tags. * - * @param eTags a {@link Set} of entity tags - * + * @param eTags a {@link Set} of entity tags * @param earliestExpiration an {@link Instant} identifying the earliest expiration of a Secret indirectly - * identified by one of the supplied tags + * identified by one of the supplied tags */ public record Stamp(Set eTags, Instant earliestExpiration) { @@ -549,7 +464,7 @@ public Stamp() { /** * Creates a new {@link Stamp}. * - * @exception NullPointerException if any argument is {@code null} + * @throws NullPointerException if any argument is {@code null} */ public Stamp { eTags = Set.copyOf(Objects.requireNonNull(eTags, "eTags")); diff --git a/integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/package-info.java b/integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/package-info.java similarity index 95% rename from integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/package-info.java rename to integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/package-info.java index a6da275765c..86b33bab344 100644 --- a/integrations/oci/oci-secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/package-info.java +++ b/integrations/oci/secrets-config-source/src/main/java/io/helidon/integrations/oci/secrets/configsource/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/oci-secrets-config-source/src/main/java/module-info.java b/integrations/oci/secrets-config-source/src/main/java/module-info.java similarity index 83% rename from integrations/oci/oci-secrets-config-source/src/main/java/module-info.java rename to integrations/oci/secrets-config-source/src/main/java/module-info.java index ef604eda5dd..1b9c5e785b7 100644 --- a/integrations/oci/oci-secrets-config-source/src/main/java/module-info.java +++ b/integrations/oci/secrets-config-source/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -30,11 +30,13 @@ requires io.helidon.common; requires transitive io.helidon.config; - requires io.helidon.integrations.oci.sdk.runtime; + requires io.helidon.integrations.oci; + requires io.helidon.service.registry; requires oci.java.sdk.common; requires transitive oci.java.sdk.secrets; requires transitive oci.java.sdk.vault; - provides io.helidon.config.spi.ConfigSourceProvider with io.helidon.integrations.oci.secrets.configsource.OciSecretsConfigSourceProvider; + provides io.helidon.config.spi.ConfigSourceProvider + with io.helidon.integrations.oci.secrets.configsource.OciSecretsConfigSourceProvider; } diff --git a/integrations/oci/oci-secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/IsModifiedTest.java b/integrations/oci/secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/IsModifiedTest.java similarity index 97% rename from integrations/oci/oci-secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/IsModifiedTest.java rename to integrations/oci/secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/IsModifiedTest.java index 63b3045ad1b..bc7257355e5 100644 --- a/integrations/oci/oci-secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/IsModifiedTest.java +++ b/integrations/oci/secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/IsModifiedTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/oci-secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/UsageTest.java b/integrations/oci/secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/UsageTest.java similarity index 97% rename from integrations/oci/oci-secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/UsageTest.java rename to integrations/oci/secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/UsageTest.java index 58b6ca856db..b8c7c4dad47 100644 --- a/integrations/oci/oci-secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/UsageTest.java +++ b/integrations/oci/secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/UsageTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -23,7 +23,6 @@ import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assumptions.assumeFalse; diff --git a/integrations/oci/oci-secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/ValueNodeTest.java b/integrations/oci/secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/ValueNodeTest.java similarity index 96% rename from integrations/oci/oci-secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/ValueNodeTest.java rename to integrations/oci/secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/ValueNodeTest.java index c22640747b5..1b03f499328 100644 --- a/integrations/oci/oci-secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/ValueNodeTest.java +++ b/integrations/oci/secrets-config-source/src/test/java/io/helidon/integrations/oci/secrets/configsource/ValueNodeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/oci-secrets-config-source/src/test/java/logging.properties b/integrations/oci/secrets-config-source/src/test/java/logging.properties similarity index 84% rename from integrations/oci/oci-secrets-config-source/src/test/java/logging.properties rename to integrations/oci/secrets-config-source/src/test/java/logging.properties index 686736250f3..e86fc921f08 100644 --- a/integrations/oci/oci-secrets-config-source/src/test/java/logging.properties +++ b/integrations/oci/secrets-config-source/src/test/java/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2023 Oracle and/or its affiliates. +# Copyright (c) 2023, 2024 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. @@ -13,7 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # -com.oracle.bmc.level = SEVERE + handlers = java.util.logging.ConsoleHandler -io.helidon.integrations.oci.secrets.configsource.level = FINER java.util.logging.ConsoleHandler.level = FINER + +com.oracle.bmc.level = SEVERE +io.helidon.integrations.oci.secrets.configsource.level = INFO \ No newline at end of file diff --git a/integrations/oci/oci-secrets-config-source/src/test/resources/meta-config.yaml b/integrations/oci/secrets-config-source/src/test/resources/meta-config.yaml similarity index 90% rename from integrations/oci/oci-secrets-config-source/src/test/resources/meta-config.yaml rename to integrations/oci/secrets-config-source/src/test/resources/meta-config.yaml index 46c8243e420..78c62e11776 100644 --- a/integrations/oci/oci-secrets-config-source/src/test/resources/meta-config.yaml +++ b/integrations/oci/secrets-config-source/src/test/resources/meta-config.yaml @@ -1,5 +1,5 @@ # -# Copyright (c) 2023 Oracle and/or its affiliates. +# Copyright (c) 2023, 2024 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. @@ -17,6 +17,7 @@ sources: - type: 'system-properties' # for testing - type: 'oci-secrets' properties: # required + optional: true accept-pattern: '^FrancqueSecret$' compartment-ocid: '${compartment-ocid}' lazy: ${lazy} diff --git a/integrations/oci/oci-secrets-mp-config-source/pom.xml b/integrations/oci/secrets-mp-config-source/pom.xml similarity index 93% rename from integrations/oci/oci-secrets-mp-config-source/pom.xml rename to integrations/oci/secrets-mp-config-source/pom.xml index 1ad4bba0449..1ae3ad4c2b0 100644 --- a/integrations/oci/oci-secrets-mp-config-source/pom.xml +++ b/integrations/oci/secrets-mp-config-source/pom.xml @@ -27,7 +27,7 @@ Helidon Integrations OCI Secrets MP Config Source - OCI Secrets Retrieval API MPMetaConfigProvider Implementation + OCI Secrets Retrieval API MpMetaConfigProvider Implementation @@ -59,7 +59,11 @@ - + + io.helidon.config + helidon-config-yaml-mp + test + org.hamcrest hamcrest-core diff --git a/integrations/oci/oci-secrets-mp-config-source/src/main/java/io/helidon/integrations/oci/secrets/mp/configsource/OciSecretsMpMetaConfigProvider.java b/integrations/oci/secrets-mp-config-source/src/main/java/io/helidon/integrations/oci/secrets/mp/configsource/OciSecretsMpMetaConfigProvider.java similarity index 99% rename from integrations/oci/oci-secrets-mp-config-source/src/main/java/io/helidon/integrations/oci/secrets/mp/configsource/OciSecretsMpMetaConfigProvider.java rename to integrations/oci/secrets-mp-config-source/src/main/java/io/helidon/integrations/oci/secrets/mp/configsource/OciSecretsMpMetaConfigProvider.java index 6fa141af55e..a35d04b4b7b 100644 --- a/integrations/oci/oci-secrets-mp-config-source/src/main/java/io/helidon/integrations/oci/secrets/mp/configsource/OciSecretsMpMetaConfigProvider.java +++ b/integrations/oci/secrets-mp-config-source/src/main/java/io/helidon/integrations/oci/secrets/mp/configsource/OciSecretsMpMetaConfigProvider.java @@ -49,7 +49,6 @@ public final class OciSecretsMpMetaConfigProvider implements MpMetaConfigProvide * @deprecated For use by the Helidon Config subsystem only. */ @Deprecated // For java.util.ServiceLoader use only. - @SuppressWarnings("deprecation") public OciSecretsMpMetaConfigProvider() { super(); this.p = new OciSecretsConfigSourceProvider(); diff --git a/integrations/oci/oci-secrets-mp-config-source/src/main/java/io/helidon/integrations/oci/secrets/mp/configsource/package-info.java b/integrations/oci/secrets-mp-config-source/src/main/java/io/helidon/integrations/oci/secrets/mp/configsource/package-info.java similarity index 95% rename from integrations/oci/oci-secrets-mp-config-source/src/main/java/io/helidon/integrations/oci/secrets/mp/configsource/package-info.java rename to integrations/oci/secrets-mp-config-source/src/main/java/io/helidon/integrations/oci/secrets/mp/configsource/package-info.java index 23d08c1da50..2a5871025c0 100644 --- a/integrations/oci/oci-secrets-mp-config-source/src/main/java/io/helidon/integrations/oci/secrets/mp/configsource/package-info.java +++ b/integrations/oci/secrets-mp-config-source/src/main/java/io/helidon/integrations/oci/secrets/mp/configsource/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/oci-secrets-mp-config-source/src/main/java/module-info.java b/integrations/oci/secrets-mp-config-source/src/main/java/module-info.java similarity index 96% rename from integrations/oci/oci-secrets-mp-config-source/src/main/java/module-info.java rename to integrations/oci/secrets-mp-config-source/src/main/java/module-info.java index c9573022abb..8d2d33827ab 100644 --- a/integrations/oci/oci-secrets-mp-config-source/src/main/java/module-info.java +++ b/integrations/oci/secrets-mp-config-source/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/oci-secrets-mp-config-source/src/test/java/io/helidon/integrations/oci/secrets/mp/configsource/UsageTest.java b/integrations/oci/secrets-mp-config-source/src/test/java/io/helidon/integrations/oci/secrets/mp/configsource/UsageTest.java similarity index 97% rename from integrations/oci/oci-secrets-mp-config-source/src/test/java/io/helidon/integrations/oci/secrets/mp/configsource/UsageTest.java rename to integrations/oci/secrets-mp-config-source/src/test/java/io/helidon/integrations/oci/secrets/mp/configsource/UsageTest.java index 2d8363ed652..26d3b1ae08f 100644 --- a/integrations/oci/oci-secrets-mp-config-source/src/test/java/io/helidon/integrations/oci/secrets/mp/configsource/UsageTest.java +++ b/integrations/oci/secrets-mp-config-source/src/test/java/io/helidon/integrations/oci/secrets/mp/configsource/UsageTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. @@ -23,7 +23,6 @@ import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assumptions.assumeFalse; diff --git a/integrations/oci/oci-secrets-mp-config-source/src/test/java/logging.properties b/integrations/oci/secrets-mp-config-source/src/test/java/logging.properties similarity index 92% rename from integrations/oci/oci-secrets-mp-config-source/src/test/java/logging.properties rename to integrations/oci/secrets-mp-config-source/src/test/java/logging.properties index 9587ed14579..2bc1929608e 100644 --- a/integrations/oci/oci-secrets-mp-config-source/src/test/java/logging.properties +++ b/integrations/oci/secrets-mp-config-source/src/test/java/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2023 Oracle and/or its affiliates. +# Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/oci-secrets-mp-config-source/src/test/resources/mp-meta-config.yaml b/integrations/oci/secrets-mp-config-source/src/test/resources/mp-meta-config.yaml similarity index 91% rename from integrations/oci/oci-secrets-mp-config-source/src/test/resources/mp-meta-config.yaml rename to integrations/oci/secrets-mp-config-source/src/test/resources/mp-meta-config.yaml index 9fa225b8c36..0f285278017 100644 --- a/integrations/oci/oci-secrets-mp-config-source/src/test/resources/mp-meta-config.yaml +++ b/integrations/oci/secrets-mp-config-source/src/test/resources/mp-meta-config.yaml @@ -1,5 +1,5 @@ # -# Copyright (c) 2023 Oracle and/or its affiliates. +# Copyright (c) 2023, 2024 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. @@ -21,6 +21,7 @@ sources: - type: 'environment-variables' - type: 'system-properties' - type: 'oci-secrets' + optional: true accept-pattern: '^FrancqueSecret$' compartment-ocid: '${compartment-ocid}' lazy: ${lazy} From b5f708257ae64984be220b543a24d5ff6f25b2ab Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 16 May 2024 00:12:29 +0200 Subject: [PATCH 13/20] Required copyright fixes due to changes in this PR. --- .../oci/sdk/runtime/OciAuthenticationDetailsProvider.java | 2 +- .../helidon/integrations/oci/sdk/runtime/OciAvailability.java | 2 +- .../io/helidon/integrations/oci/sdk/runtime/OciExtension.java | 2 +- integrations/oci/sdk/runtime/src/main/java/module-info.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAuthenticationDetailsProvider.java b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAuthenticationDetailsProvider.java index 1c0ead3bbcd..d2b5a8d400f 100644 --- a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAuthenticationDetailsProvider.java +++ b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAuthenticationDetailsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAvailability.java b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAvailability.java index 9cd8323eff0..3577ce1817f 100644 --- a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAvailability.java +++ b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciAvailability.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciExtension.java b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciExtension.java index 9248e9fca73..a4823d6fafb 100644 --- a/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciExtension.java +++ b/integrations/oci/sdk/runtime/src/main/java/io/helidon/integrations/oci/sdk/runtime/OciExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. diff --git a/integrations/oci/sdk/runtime/src/main/java/module-info.java b/integrations/oci/sdk/runtime/src/main/java/module-info.java index 4648b9fbee4..2ba16341560 100644 --- a/integrations/oci/sdk/runtime/src/main/java/module-info.java +++ b/integrations/oci/sdk/runtime/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 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. From f129981315a9d393e9b8aa081747dcd86fa319a4 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 16 May 2024 15:50:52 +0200 Subject: [PATCH 14/20] Use `helidon.oci` to configure metadata to connect to OCI Remove support for `null` from service implementations, support Optional instead --- .../integrations/oci/AtnDetailsProvider.java | 16 ++++++++-------- .../integrations/oci/OciConfigBlueprint.java | 2 +- .../helidon/integrations/oci/RegionProvider.java | 8 ++++---- .../integrations/oci/OciIntegrationTest.java | 8 ++++---- .../src/test/resources/oci-config.yaml | 3 ++- .../service/registry/CoreServiceRegistry.java | 14 ++++++++++---- .../io/helidon/service/registry/Service.java | 4 ++-- 7 files changed, 31 insertions(+), 24 deletions(-) diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnDetailsProvider.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnDetailsProvider.java index e58380e57db..9a870d6383c 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnDetailsProvider.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnDetailsProvider.java @@ -30,13 +30,13 @@ @Service.Provider @Service.ExternalContracts(AbstractAuthenticationDetailsProvider.class) -class AtnDetailsProvider implements Supplier { +class AtnDetailsProvider implements Supplier> { - private final LazyValue provider; + private final LazyValue> provider; AtnDetailsProvider(OciConfig ociConfig, List atnDetailsProviders) { String chosenStrategy = ociConfig.atnStrategy(); - LazyValue providerLazyValue = null; + LazyValue> providerLazyValue = null; if (OciConfigBlueprint.STRATEGY_AUTO.equals(chosenStrategy)) { // auto, chose from existing @@ -44,21 +44,21 @@ class AtnDetailsProvider implements Supplier provider = atnDetailsProvider.provider(); if (provider.isPresent()) { - return provider.get(); + return provider; } } - return null; + return Optional.empty(); }); } else { List strategies = new ArrayList<>(); for (OciAtnStrategy atnDetailsProvider : atnDetailsProviders) { if (chosenStrategy.equals(atnDetailsProvider.strategy())) { - providerLazyValue = LazyValue.create(() -> atnDetailsProvider.provider() + providerLazyValue = LazyValue.create(() -> Optional.of(atnDetailsProvider.provider() .orElseThrow(() -> new ConfigException("Strategy \"" + chosenStrategy + "\" did not provide an authentication provider, " - + "yet it is requested through configuration."))); + + "yet it is requested through configuration.")))); break; } strategies.add(atnDetailsProvider.strategy()); @@ -75,7 +75,7 @@ class AtnDetailsProvider implements Supplier get() { return provider.get(); } } diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java index c7c68b34728..06f08d9b614 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java @@ -30,7 +30,7 @@ *

    * Allows customization of discovery of authentication details provider and of region. */ -@Prototype.Configured("oci") +@Prototype.Configured("helidon.oci") @Prototype.Blueprint @Prototype.CustomMethods(OciConfigSupport.class) interface OciConfigBlueprint { diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProvider.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProvider.java index 8511db6e7a8..5f156ca6864 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProvider.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProvider.java @@ -27,7 +27,7 @@ @Service.Provider @Service.ExternalContracts(Region.class) -class RegionProvider implements Supplier { +class RegionProvider implements Supplier> { private final List regionProviders; RegionProvider(List regionProviders) { @@ -35,13 +35,13 @@ class RegionProvider implements Supplier { } @Override - public Region get() { + public Optional get() { for (OciRegion regionProvider : regionProviders) { Optional region = regionProvider.region(); if (region.isPresent()) { - return region.get(); + return region; } } - return null; + return Optional.empty(); } } diff --git a/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java b/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java index 20993567a65..80b2e83bf17 100644 --- a/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java +++ b/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java @@ -46,7 +46,7 @@ class OciIntegrationTest { private ServiceRegistry registry; void setUp(Config config) { - OciConfigProvider.config(OciConfig.create(config.get("oci"))); + OciConfigProvider.config(OciConfig.create(config.get("helidon.oci"))); registryManager = ServiceRegistryManager.create(); registry = registryManager.registry(); } @@ -87,7 +87,7 @@ void testNoStrategyAvailable() { @Test void testRegionFromConfig() { String yamlConfig = """ - oci.region: us-phoenix-1 + helidon.oci.region: us-phoenix-1 """; Config config = Config.just(ConfigSources.create(yamlConfig, MediaTypes.APPLICATION_YAML)); setUp(config); @@ -98,7 +98,7 @@ void testRegionFromConfig() { @Test void testConfigStrategyAvailable() { String yamlConfig = """ - oci: + helidon.oci: config-strategy: # region must be real, so it can be parsed region: us-phoenix-1 @@ -145,7 +145,7 @@ void testConfigStrategyAvailable() { @Test void testConfigFileStrategyAvailable() { String yamlConfig = """ - oci: + helidon.oci: config-file-strategy: path: src/test/resources/test-oci-config profile: MY_PROFILE diff --git a/integrations/oci/tls-certificates/src/test/resources/oci-config.yaml b/integrations/oci/tls-certificates/src/test/resources/oci-config.yaml index 431734a29db..a0bc65a2b74 100644 --- a/integrations/oci/tls-certificates/src/test/resources/oci-config.yaml +++ b/integrations/oci/tls-certificates/src/test/resources/oci-config.yaml @@ -14,4 +14,5 @@ # limitations under the License. # -auth-strategies: ["instance-principals", "config-file", "resource-principal", "config"] +helidon.oci: + allowed-atn-strategies: ["instance-principals", "config-file", "resource-principal", "config"] diff --git a/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java b/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java index df159526ff4..80f1365eef9 100644 --- a/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java +++ b/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java @@ -32,7 +32,6 @@ import io.helidon.common.LazyValue; import io.helidon.common.types.TypeName; -import io.helidon.common.types.TypeNames; import io.helidon.service.registry.GeneratedService.Descriptor; /** @@ -157,7 +156,6 @@ private List allProviders(TypeName contract) { return new ArrayList<>(serviceProviders); } - @SuppressWarnings("unchecked") private Optional instance(Descriptor descriptor) { List dependencies = descriptor.dependencies(); Map collectedDependencies = new HashMap<>(); @@ -175,12 +173,20 @@ private Optional instance(Descriptor descriptor) { } Object serviceInstance = descriptor.instantiate(DependencyContext.create(collectedDependencies)); - if (descriptor.contracts().contains(TypeNames.SUPPLIER)) { - return Optional.ofNullable(((Supplier) serviceInstance).get()); + if (serviceInstance instanceof Supplier supplier) { + return fromSupplierValue(supplier.get()); } return Optional.of(serviceInstance); } + @SuppressWarnings({"rawtypes", "unchecked"}) + private Optional fromSupplierValue(Object value) { + if (value instanceof Optional optValue) { + return optValue; + } + return Optional.of(value); + } + private Object dependencyNoSupplier(TypeName dependencyType, TypeName contract) { if (dependencyType.isList()) { return all(contract); diff --git a/service/registry/src/main/java/io/helidon/service/registry/Service.java b/service/registry/src/main/java/io/helidon/service/registry/Service.java index fd8910667a4..58da92b3168 100644 --- a/service/registry/src/main/java/io/helidon/service/registry/Service.java +++ b/service/registry/src/main/java/io/helidon/service/registry/Service.java @@ -57,8 +57,8 @@ private Service() { *
      *
    • Direct implementation of interface (or extending an abstract class)
    • *
    • Implementing a {@link java.util.function.Supplier} of the contract; when using supplier, service registry - * supports the capability to return {@code null} to mark this service as "empty" and not providing anything; such - * a service will be ignored and only other implementations would be used
    • + * supports the capability to return {@link java.util.Optional} in case the service cannot provide a value; such + * a service will be ignored and only other implementations (with lower weight) would be used *
    */ @Documented From b54f7de68b556fd8d82989c08fc7ada037e48232 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 16 May 2024 15:25:42 +0200 Subject: [PATCH 15/20] tmp --- .../java/io/helidon/builder/api/Prototype.java | 14 ++++++++++++++ .../service/registry/ServiceRegistry.java | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/builder/api/src/main/java/io/helidon/builder/api/Prototype.java b/builder/api/src/main/java/io/helidon/builder/api/Prototype.java index 484982f5eb3..2bd3fe96049 100644 --- a/builder/api/src/main/java/io/helidon/builder/api/Prototype.java +++ b/builder/api/src/main/java/io/helidon/builder/api/Prototype.java @@ -442,6 +442,8 @@ public interface OptionDecorator { /** * Add additional interfaces to implement by the prototype. Provide correct types (fully qualified) for generics. */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.CLASS) public @interface Implement { /** * Interfaces to implement, such as {@code java.lang.Comparable}. @@ -451,5 +453,17 @@ public interface OptionDecorator { String[] value(); } + /** + * Generate support for Service Registry ({@code helidon-service-registry}) for any option that is annotated + * as {@link io.helidon.builder.api.Option.Provider}. + * When enabled (i.e. when this annotation is present), the service registry would be used to discover any service + * implementations including implementations discovered by service loader - appropriate {@code service.loader} + * metadata file will be generated for the service provider interfaces. + */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.CLASS) + public @interface RegistrySupport { + boolean value() default true; + } } diff --git a/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry.java b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry.java index db60426d0fa..c59964e569e 100644 --- a/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry.java +++ b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry.java @@ -172,4 +172,22 @@ default Supplier> supplyAll(Class contract) { * @return a supplier of list of instances */ Supplier> supplyAll(TypeName contract); + + /** + * Provide a value for a specific service info instance. + * This method uses instance equality for service info, so be careful to use the singleton instance + * from the service descriptor, or instances provided by {@link #allServices(Class)}. + * + * @param serviceInfo service info instance + * @return value of the service described by the service info provided (always a single value), in case the + * @param type of the expected instance, we just cast to it, so this may cause runtime issues if assigned to invalid + * type + */ + Optional get(ServiceInfo serviceInfo); + + default List allServices(Class contract) { + return allServices(TypeName.create(contract)); + } + + List allServices(TypeName contract); } From 82ee4e9c1f49a8a0b58ae398067e9b082bdff971 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Fri, 17 May 2024 17:09:41 +0200 Subject: [PATCH 16/20] Introducing support for service registry in blueprints (to load providers). A fix for discovered issue with just a single implementation loaded for each service loader service. --- .../io/helidon/builder/api/Prototype.java | 14 + .../builder/codegen/BuilderCodegen.java | 33 ++ .../codegen/GenerateAbstractBuilder.java | 238 +++++++++++--- .../helidon/builder/codegen/TypeContext.java | 8 +- .../io/helidon/builder/codegen/Types.java | 4 + builder/tests/builder/pom.xml | 4 + .../WithProviderRegistryBlueprint.java | 71 ++++ .../builder/src/main/java/module-info.java | 3 +- .../builder/test/ProviderRegistryTest.java | 172 ++++++++++ builder/tests/codegen/pom.xml | 68 ++++ .../io/helidon/builder/codegen/TypesTest.java | 153 +++++++++ builder/tests/pom.xml | 1 + .../service/registry/CoreServiceRegistry.java | 27 +- .../service/registry/GeneratedService.java | 309 ++++++++++++++++++ .../ServiceLoader__ServiceDescriptor.java | 7 + .../service/registry/ServiceRegistry.java | 18 +- .../codegen/ServiceCodegenTypesTest.java | 2 +- .../native-image.properties | 5 +- 18 files changed, 1087 insertions(+), 50 deletions(-) create mode 100644 builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/WithProviderRegistryBlueprint.java create mode 100644 builder/tests/builder/src/test/java/io/helidon/builder/test/ProviderRegistryTest.java create mode 100644 builder/tests/codegen/pom.xml create mode 100644 builder/tests/codegen/src/test/java/io/helidon/builder/codegen/TypesTest.java diff --git a/builder/api/src/main/java/io/helidon/builder/api/Prototype.java b/builder/api/src/main/java/io/helidon/builder/api/Prototype.java index 2bd3fe96049..7c8cccd86c0 100644 --- a/builder/api/src/main/java/io/helidon/builder/api/Prototype.java +++ b/builder/api/src/main/java/io/helidon/builder/api/Prototype.java @@ -164,6 +164,8 @@ public interface ConfiguredBuilder extends Builder { * When enabled (i.e. when this annotation is present), the service registry would be used to discover any service * implementations including implementations discovered by service loader - appropriate {@code service.loader} * metadata file will be generated for the service provider interfaces. + *

    + * Simply add this annotation to your {@link io.helidon.builder.api.Prototype.Blueprint} type to enable service registry. + * Note that if this annotation is NOT present, service registry would not be used, and {@link java.util.ServiceLoader} + * is used instead. + *

    + * When using this annotation, you cannot use {@code serviceRegistry} as a custom option in your blueprint, as it will + * be added by the annotation processor, to allow customization of the registry instance. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) public @interface RegistrySupport { + /** + * Whether to enable (default) or disable (set to {@code false}) registry support for this blueprint. + * + * @return whether the registry support should be enabled + */ boolean value() default true; } } diff --git a/builder/codegen/src/main/java/io/helidon/builder/codegen/BuilderCodegen.java b/builder/codegen/src/main/java/io/helidon/builder/codegen/BuilderCodegen.java index 778058532b0..c8a256d1b18 100644 --- a/builder/codegen/src/main/java/io/helidon/builder/codegen/BuilderCodegen.java +++ b/builder/codegen/src/main/java/io/helidon/builder/codegen/BuilderCodegen.java @@ -22,13 +22,16 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.TreeSet; import java.util.stream.Collectors; import io.helidon.builder.codegen.ValidationTask.ValidateConfiguredType; import io.helidon.codegen.CodegenContext; import io.helidon.codegen.CodegenEvent; import io.helidon.codegen.CodegenException; +import io.helidon.codegen.CodegenFiler; import io.helidon.codegen.CodegenUtil; +import io.helidon.codegen.FilerTextResource; import io.helidon.codegen.RoundContext; import io.helidon.codegen.classmodel.ClassModel; import io.helidon.codegen.classmodel.Javadoc; @@ -51,6 +54,8 @@ class BuilderCodegen implements CodegenExtension { private final Set runtimeTypes = new HashSet<>(); // all blueprint types (for validation) private final Set blueprintTypes = new HashSet<>(); + // all types from service loader that should be supported by ServiceRegistry + private final Set serviceLoaderContracts = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); private final CodegenContext ctx; @@ -83,6 +88,9 @@ public void process(RoundContext roundContext) { public void processingOver(RoundContext roundContext) { process(roundContext); + // now create service.loader + updateServiceLoaderResource(); + // we must collect validation information after all types are generated - so // we also listen on @Generated, so there is another round of annotation processing where we have all // types nice and ready @@ -114,6 +122,23 @@ public void processingOver(RoundContext roundContext) { } } + private void updateServiceLoaderResource() { + CodegenFiler filer = ctx.filer(); + FilerTextResource serviceLoaderResource = filer.textResource("META-INF/helidon/service.loader"); + List lines = new ArrayList<>(serviceLoaderResource.lines()); + if (lines.isEmpty()) { + lines.add("# List of service contracts we want to support either from service registry, or from service loader"); + } + for (String serviceLoaderContract : this.serviceLoaderContracts) { + if (!lines.contains(serviceLoaderContract)) { + lines.add(serviceLoaderContract); + } + } + + serviceLoaderResource.lines(lines); + serviceLoaderResource.write(); + } + private void process(RoundContext roundContext, TypeInfo blueprint) { TypeContext typeContext = TypeContext.create(ctx, blueprint); AnnotationDataBlueprint blueprintDef = typeContext.blueprintData(); @@ -227,6 +252,14 @@ private void process(RoundContext roundContext, TypeInfo blueprint) { classModel, blueprint.typeName(), blueprint.originatingElement().orElse(blueprint.typeName())); + + if (typeContext.typeInfo().supportsServiceRegistry() && typeContext.propertyData().hasProvider()) { + for (PrototypeProperty property : typeContext.propertyData().properties()) { + if (property.configuredOption().provider()) { + this.serviceLoaderContracts.add(property.configuredOption().providerType().genericTypeName().fqName()); + } + } + } } private static void addCreateDefaultMethod(AnnotationDataBlueprint blueprintDef, diff --git a/builder/codegen/src/main/java/io/helidon/builder/codegen/GenerateAbstractBuilder.java b/builder/codegen/src/main/java/io/helidon/builder/codegen/GenerateAbstractBuilder.java index 1ae21fd766a..27a75cd7f0e 100644 --- a/builder/codegen/src/main/java/io/helidon/builder/codegen/GenerateAbstractBuilder.java +++ b/builder/codegen/src/main/java/io/helidon/builder/codegen/GenerateAbstractBuilder.java @@ -220,6 +220,12 @@ private static void builderMethods(InnerClass.Builder classBuilder, TypeContext } TypeName returnType = TypeName.createFromGenericDeclaration("BUILDER"); + + if (typeContext.propertyData().hasProvider() && typeContext.typeInfo().supportsServiceRegistry()) { + // generate setter for service registry + serviceRegistrySetter(classBuilder); + } + // first setters for (PrototypeProperty child : properties) { if (isConfigProperty(child)) { @@ -288,6 +294,37 @@ String host() { } } + private static void serviceRegistrySetter(InnerClass.Builder classBuilder) { + /* + public BUILDER config(Config config) { + this.config = config; + config.get("server").as(String.class).ifPresent(this::server); + return self(); + } + */ + Javadoc javadoc = Javadoc.builder() + .addLine("Provide an explicit registry instance to use. ") + .addLine("

    ") + .addLine("If not configured, the {@link " + + Types.GLOBAL_SERVICE_REGISTRY.fqName() + + "} would be used to discover services.") + .build(); + + Method.Builder builder = Method.builder() + .name("serviceRegistry") + .javadoc(javadoc) + .returnType(TypeArgument.create("BUILDER"), "updated builder instance") + .addParameter(param -> param.name("registry") + .type(Types.SERVICE_REGISTRY) + .description("service registry instance")) + .addContent(Objects.class) + .addContentLine(".requireNonNull(registry);") + .addContentLine("this.serviceRegistry = registry;") + .addContentLine("return self();"); + + classBuilder.addMethod(builder); + } + private static void createConfigMethod(InnerClass.Builder classBuilder, TypeContext typeContext, AnnotationDataConfigured configured, List properties) { @@ -474,6 +511,11 @@ private static void fields(InnerClass.Builder classBuilder, TypeContext typeCont if (isBuilder && (typeContext.configuredData().configured() || hasConfig(typeContext.propertyData().properties()))) { classBuilder.addField(builder -> builder.type(Types.COMMON_CONFIG).name("config")); } + if (isBuilder + && typeContext.typeInfo().supportsServiceRegistry() + && typeContext.propertyData().hasProvider()) { + classBuilder.addField(builder -> builder.type(Types.SERVICE_REGISTRY).name("serviceRegistry")); + } for (PrototypeProperty child : typeContext.propertyData().properties()) { if (isBuilder && child.configuredOption().hasAllowedValues()) { String allowedValues = child.configuredOption().allowedValues() @@ -527,62 +569,46 @@ private static void preBuildPrototypeMethod(InnerClass.Builder classBuilder, .ifPresent(it -> preBuildBuilder.addContentLine("super.preBuildPrototype();")); if (typeContext.propertyData().hasProvider()) { boolean configured = typeContext.configuredData().configured(); + if (configured) { // need to have a non-null config instance - preBuildBuilder.addContentLine("var config = this.config == null ? Config.empty() : this.config;"); + preBuildBuilder.addContent("var config = this.config == null ? ") + .addContent(Types.COMMON_CONFIG) + .addContentLine(".empty() : this.config;"); } + + if (typeContext.typeInfo().supportsServiceRegistry()) { + preBuildBuilder.addContent("var registry = this.serviceRegistry == null ? ") + .addContent(Types.GLOBAL_SERVICE_REGISTRY) + .addContentLine(".registry() : this.serviceRegistry;"); + } + for (PrototypeProperty property : typeContext.propertyData().properties()) { + boolean propertyConfigured = property.configuredOption().configured(); + AnnotationDataOption configuredOption = property.configuredOption(); if (configuredOption.provider()) { boolean defaultDiscoverServices = configuredOption.providerDiscoverServices(); // using a code block, so we can reuse the same variable names for multiple providers - preBuildBuilder.addContentLine("{"); TypeName providerType = configuredOption.providerType(); - preBuildBuilder.addContent("var serviceLoader = ") - .addContent(HelidonServiceLoader.class) - .addContent(".create(") - .addContent(ServiceLoader.class) - .addContent(".load(") - .addContent(providerType.genericTypeName()) - .addContentLine(".class));"); - if (configured) { - TypeName typeName = property.typeHandler().declaredType(); - if (typeName.isList() || typeName.isSet()) { - preBuildBuilder.addContent("this.add") - .addContent(capitalize(property.name())) - .addContent("(discoverServices(config, \"") - .addContent(configuredOption.configKey()) - .addContent("\", serviceLoader, ") - .addContent(providerType.genericTypeName()) - .addContent(".class, ") - .addContent(property.typeHandler().actualType().genericTypeName()) - .addContent(".class, ") - .addContent(property.name()) - .addContent("DiscoverServices, ") - .addContent(property.name()) - .addContentLine("));"); - } else { - preBuildBuilder.addContent("discoverService(config, \"") - .addContent(configuredOption.configKey()) - .addContent("\", serviceLoader, ") - .addContent(providerType) - .addContent(".class, ") - .addContent(property.typeHandler().actualType().genericTypeName()) - .addContent(".class, ") - .addContent(property.name()) - .addContent("DiscoverServices, @java.util.Optional@.ofNullable(") - .addContent(property.name()) - .addContent(")).ifPresent(this::") - .addContent(property.setterName()) - .addContentLine(");"); - } + + if (typeContext.typeInfo().supportsServiceRegistry()) { + serviceRegistryPropertyDiscovery(preBuildBuilder, + property, + propertyConfigured, + configuredOption, + providerType, + defaultDiscoverServices); } else { - if (defaultDiscoverServices) { - preBuildBuilder.addContentLine("this." + property.name() + "(serviceLoader.asList());"); - } + serviceLoaderPropertyDiscovery(preBuildBuilder, + property, + propertyConfigured, + configuredOption, + providerType, + defaultDiscoverServices); } - preBuildBuilder.addContentLine("}"); + } } } @@ -594,6 +620,130 @@ private static void preBuildPrototypeMethod(InnerClass.Builder classBuilder, classBuilder.addMethod(preBuildBuilder); } + private static void serviceLoaderPropertyDiscovery(Method.Builder preBuildBuilder, + PrototypeProperty property, + boolean propertyConfigured, + AnnotationDataOption configuredOption, + TypeName providerType, + boolean defaultDiscoverServices) { + preBuildBuilder.addContentLine("{"); + preBuildBuilder.addContent("var serviceLoader = ") + .addContent(HelidonServiceLoader.class) + .addContent(".create(") + .addContent(ServiceLoader.class) + .addContent(".load(") + .addContent(providerType.genericTypeName()) + .addContentLine(".class));"); + if (propertyConfigured) { + TypeName typeName = property.typeHandler().declaredType(); + if (typeName.isList() || typeName.isSet()) { + preBuildBuilder.addContent("this.add") + .addContent(capitalize(property.name())) + .addContent("(discoverServices(config, \"") + .addContent(configuredOption.configKey()) + .addContent("\", serviceLoader, ") + .addContent(providerType.genericTypeName()) + .addContent(".class, ") + .addContent(property.typeHandler().actualType().genericTypeName()) + .addContent(".class, ") + .addContent(property.name()) + .addContent("DiscoverServices, ") + .addContent(property.name()) + .addContentLine("));"); + } else { + preBuildBuilder.addContent("discoverService(config, \"") + .addContent(configuredOption.configKey()) + .addContent("\", serviceLoader, ") + .addContent(providerType) + .addContent(".class, ") + .addContent(property.typeHandler().actualType().genericTypeName()) + .addContent(".class, ") + .addContent(property.name()) + .addContent("DiscoverServices, ") + .addContent(Optional.class) + .addContent(".ofNullable(") + .addContent(property.name()) + .addContent(")).ifPresent(this::") + .addContent(property.setterName()) + .addContentLine(");"); + } + } else { + if (defaultDiscoverServices) { + preBuildBuilder.addContentLine("this." + property.name() + "(serviceLoader.asList());"); + } + } + preBuildBuilder.addContentLine("}"); + } + + private static void serviceRegistryPropertyDiscovery(Method.Builder preBuildBuilder, + PrototypeProperty property, + boolean propertyConfigured, + AnnotationDataOption configuredOption, + TypeName providerType, + boolean defaultDiscoverServices) { + if (propertyConfigured) { + TypeName typeName = property.typeHandler().declaredType(); + if (typeName.isList() || typeName.isSet()) { + preBuildBuilder.addContent("this.add") + .addContent(capitalize(property.name())) + .addContent("(") + .addContent(Types.GENERATED_SERVICE) + .addContentLine(".discoverServices(config,") + .increaseContentPadding() + .increaseContentPadding() + .increaseContentPadding() + .addContent("\"") + .addContent(configuredOption.configKey()) + .addContentLine("\",") + .addContentLine("registry,") + .addContent(providerType.genericTypeName()) + .addContentLine(".class,") + .addContent(property.typeHandler().actualType().genericTypeName()) + .addContentLine(".class,") + .addContent(property.name()) + .addContentLine("DiscoverServices,") + .addContent(property.name()) + .addContentLine("));") + .decreaseContentPadding() + .decreaseContentPadding() + .decreaseContentPadding(); + } else { + preBuildBuilder + .addContent(Types.GENERATED_SERVICE) + .addContentLine(".discoverService(config,") + .increaseContentPadding() + .increaseContentPadding() + .increaseContentPadding() + .addContent("\"") + .addContent(configuredOption.configKey()) + .addContentLine("\",") + .addContentLine("registry,") + .addContent(providerType) + .addContentLine(".class,") + .addContent(property.typeHandler().actualType().genericTypeName()) + .addContentLine(".class,") + .addContent(property.name()) + .addContentLine("DiscoverServices,") + .addContent(Optional.class) + .addContent(".ofNullable(") + .addContent(property.name()) + .addContentLine("))") + .decreaseContentPadding() + .decreaseContentPadding() + .addContent(".ifPresent(this::") + .addContent(property.setterName()) + .addContentLine(");") + .decreaseContentPadding(); + } + } else { + if (defaultDiscoverServices) { + preBuildBuilder.addContent("this." + property.name() + "(registry.all(") + .addContent(providerType.genericTypeName()) + .addContentLine(".class));"); + } + } + } + private static void validatePrototypeMethod(InnerClass.Builder classBuilder, TypeContext typeContext) { Method.Builder validateBuilder = Method.builder() .name("validatePrototype") diff --git a/builder/codegen/src/main/java/io/helidon/builder/codegen/TypeContext.java b/builder/codegen/src/main/java/io/helidon/builder/codegen/TypeContext.java index b613f44f466..12b74b32555 100644 --- a/builder/codegen/src/main/java/io/helidon/builder/codegen/TypeContext.java +++ b/builder/codegen/src/main/java/io/helidon/builder/codegen/TypeContext.java @@ -203,7 +203,12 @@ static TypeContext create(CodegenContext ctx, TypeInfo blueprint) { .className("Builder") .build(); - TypeInformation typeInformation = new TypeInformation(blueprint, + boolean supportsServiceRegistry = blueprint.findAnnotation(Types.PROTOTYPE_SERVICE_REGISTRY) + .flatMap(Annotation::booleanValue) + .orElse(false); + + TypeInformation typeInformation = new TypeInformation(supportsServiceRegistry, + blueprint, prototype, prototypeBuilder, prototypeImpl, @@ -407,6 +412,7 @@ private static TypeName generatedTypeName(TypeInfo typeInfo) { } record TypeInformation( + boolean supportsServiceRegistry, TypeInfo blueprintType, TypeName prototype, TypeName prototypeBuilder, diff --git a/builder/codegen/src/main/java/io/helidon/builder/codegen/Types.java b/builder/codegen/src/main/java/io/helidon/builder/codegen/Types.java index ea6b79a1c85..8a274abb2cb 100644 --- a/builder/codegen/src/main/java/io/helidon/builder/codegen/Types.java +++ b/builder/codegen/src/main/java/io/helidon/builder/codegen/Types.java @@ -31,6 +31,9 @@ final class Types { static final TypeName ARRAY_LIST = TypeName.create(ArrayList.class); static final TypeName LINKED_HASH_SET = TypeName.create(LinkedHashSet.class); static final TypeName CHAR_ARRAY = TypeName.create(char[].class); + static final TypeName SERVICE_REGISTRY = TypeName.create("io.helidon.service.registry.ServiceRegistry"); + static final TypeName GLOBAL_SERVICE_REGISTRY = TypeName.create("io.helidon.service.registry.GlobalServiceRegistry"); + static final TypeName GENERATED_SERVICE = TypeName.create("io.helidon.service.registry.GeneratedService"); static final TypeName BUILDER_DESCRIPTION = TypeName.create("io.helidon.builder.api.Description"); @@ -48,6 +51,7 @@ final class Types { static final TypeName PROTOTYPE_PROTOTYPE_METHOD = TypeName.create("io.helidon.builder.api.Prototype.PrototypeMethod"); static final TypeName PROTOTYPE_BUILDER_DECORATOR = TypeName.create("io.helidon.builder.api.Prototype.BuilderDecorator"); static final TypeName PROTOTYPE_CONSTANT = TypeName.create("io.helidon.builder.api.Prototype.Constant"); + static final TypeName PROTOTYPE_SERVICE_REGISTRY = TypeName.create("io.helidon.builder.api.Prototype.RegistrySupport"); static final TypeName GENERATED_EQUALITY_UTIL = TypeName.create("io.helidon.builder.api.GeneratedBuilder.EqualityUtil"); static final TypeName RUNTIME_PROTOTYPE = TypeName.create("io.helidon.builder.api.RuntimeType.PrototypedBy"); diff --git a/builder/tests/builder/pom.xml b/builder/tests/builder/pom.xml index 43e8440a93e..0a1c918a941 100644 --- a/builder/tests/builder/pom.xml +++ b/builder/tests/builder/pom.xml @@ -36,6 +36,10 @@ io.helidon.builder helidon-builder-api + + io.helidon.service + helidon-service-registry + com.fasterxml.jackson.core jackson-databind diff --git a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/WithProviderRegistryBlueprint.java b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/WithProviderRegistryBlueprint.java new file mode 100644 index 00000000000..37d6a35c534 --- /dev/null +++ b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/WithProviderRegistryBlueprint.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 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.builder.test.testsubjects; + +import java.util.List; +import java.util.Optional; + +import io.helidon.builder.api.Option; +import io.helidon.builder.api.Prototype; + +@Prototype.Blueprint +@Prototype.Configured +@Prototype.RegistrySupport +interface WithProviderRegistryBlueprint { + @Option.Configured + @Option.Provider(SomeProvider.class) + SomeProvider.SomeService oneDiscover(); + + @Option.Configured + @Option.Provider(value = SomeProvider.class, discoverServices = false) + SomeProvider.SomeService oneNotDiscover(); + + @Option.Configured + @Option.Provider(SomeProvider.class) + Optional optionalDiscover(); + + @Option.Configured + @Option.Provider(value = SomeProvider.class, discoverServices = false) + Optional optionalNotDiscover(); + + @Option.Configured + @Option.Provider(SomeProvider.class) + List listDiscover(); + + @Option.Configured + @Option.Provider(value = SomeProvider.class, discoverServices = false) + List listNotDiscover(); + + /* + The following should always be empty, as there are no implementations + */ + @Option.Configured + @Option.Provider(ProviderNoImpls.class) + Optional optionalNoImplDiscover(); + + @Option.Configured + @Option.Provider(value = ProviderNoImpls.class, discoverServices = false) + Optional optionalNoImplNotDiscover(); + + @Option.Configured + @Option.Provider(ProviderNoImpls.class) + List listNoImplDiscover(); + + @Option.Configured + @Option.Provider(value = ProviderNoImpls.class, discoverServices = false) + List listNoImplNotDiscover(); +} diff --git a/builder/tests/builder/src/main/java/module-info.java b/builder/tests/builder/src/main/java/module-info.java index 1d04dd1056f..f1db16c08c2 100644 --- a/builder/tests/builder/src/main/java/module-info.java +++ b/builder/tests/builder/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. + * Copyright (c) 2022, 2024 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. @@ -24,6 +24,7 @@ requires io.helidon.common; requires io.helidon.common.config; requires io.helidon.builder.api; + requires io.helidon.service.registry; exports io.helidon.builder.test.testsubjects; diff --git a/builder/tests/builder/src/test/java/io/helidon/builder/test/ProviderRegistryTest.java b/builder/tests/builder/src/test/java/io/helidon/builder/test/ProviderRegistryTest.java new file mode 100644 index 00000000000..186bb4da369 --- /dev/null +++ b/builder/tests/builder/src/test/java/io/helidon/builder/test/ProviderRegistryTest.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2024 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.builder.test; + +import java.util.List; + +import io.helidon.builder.test.testsubjects.SomeProvider; +import io.helidon.builder.test.testsubjects.WithProviderRegistry; +import io.helidon.common.Errors; +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ProviderRegistryTest { + private static Config config; + + @BeforeAll + static void beforeAll() { + config = Config.just(ConfigSources.classpath("provider-test.yaml")); + } + + @Test + void testMinimalDefined() { + /* + Only the property that does not discover services and does not return optional is defined in config + */ + WithProviderRegistry value = WithProviderRegistry.create(config.get("min-defined")); + + assertThat(value.oneDiscover().prop(), is("some-1")); + assertThat(value.oneNotDiscover().prop(), is("config")); + + assertThat(value.optionalDiscover().map(SomeProvider.SomeService::prop), optionalValue(is("some-1"))); + assertThat(value.optionalNotDiscover().map(SomeProvider.SomeService::prop), optionalEmpty()); + + assertThat(value.listDiscover(), hasSize(2)); + assertThat(value.listNotDiscover(), hasSize(0)); + + /* + Test all the methods for a provider with no implementations + */ + assertThat(value.optionalNoImplDiscover(), optionalEmpty()); + assertThat(value.optionalNoImplNotDiscover(), optionalEmpty()); + assertThat(value.listNoImplDiscover(), hasSize(0)); + assertThat(value.listNoImplNotDiscover(), hasSize(0)); + } + + @Test + void testAllDefined() { + /* + Everything is customized + */ + WithProviderRegistry value = WithProviderRegistry.create(config.get("all-defined")); + + assertThat(value.oneDiscover().prop(), is("config")); + assertThat(value.oneNotDiscover().prop(), is("config")); + + assertThat(value.optionalDiscover().map(SomeProvider.SomeService::prop), optionalValue(is("config"))); + assertThat(value.optionalNotDiscover().map(SomeProvider.SomeService::prop), optionalValue(is("config"))); + + List services = value.listDiscover(); + assertThat(services, hasSize(2)); + assertThat(services.get(0).prop(), is("config")); + assertThat(services.get(1).prop(), is("config2")); + + services = value.listNotDiscover(); + assertThat(value.listNotDiscover(), hasSize(2)); + assertThat(services.get(0).prop(), is("config")); + assertThat(services.get(1).prop(), is("config2")); + } + + @Test + void testFail() { + /* + Missing the one mandatory option in config + */ + Errors.ErrorMessagesException fail = assertThrows(Errors.ErrorMessagesException.class, + () -> WithProviderRegistry.create(config.get("fail"))); + + assertThat(fail.getMessages(), hasSize(1)); + assertThat(fail.getMessage(), containsString("\"one-not-discover\" must not be null")); + } + + @Test + void testSingleList() { + /* + Single value list (when not using discovery). When discovery is used, all in service loader are discovered, even + if not configured + */ + WithProviderRegistry value = WithProviderRegistry.create(config.get("single-list")); + + assertThat(value.oneNotDiscover().prop(), is("config2")); + + List services = value.listDiscover(); + assertThat(services, hasSize(2)); + assertThat(services.get(0).prop(), is("config")); + assertThat(services.get(1).prop(), is("some-2")); + + services = value.listNotDiscover(); + assertThat(value.listNotDiscover(), hasSize(1)); + assertThat(services.get(0).prop(), is("config2")); + } + + @Test + void testDisabledDiscoveryOnTheCopy() { + SomeProvider.SomeService someService = new DummyService(); + WithProviderRegistry value = WithProviderRegistry.builder() + .listDiscoverDiscoverServices(false) + .oneNotDiscover(someService) //This needs to be set, otherwise validation fails + .build(); + assertThat(value.listDiscover(), is(List.of())); + + WithProviderRegistry copy = WithProviderRegistry.builder() + .from(value) + .build(); + assertThat(copy.listDiscover(), is(List.of())); + } + + @Test + void testDisabledDiscoveryOnTheCopiedBuilder() { + SomeProvider.SomeService someService = new DummyService(); + WithProviderRegistry.Builder value = WithProviderRegistry.builder() + .listDiscoverDiscoverServices(false) + .oneNotDiscover(someService); + + WithProviderRegistry copy = WithProviderRegistry.builder() + .from(value) + .build(); + assertThat(copy.listDiscover(), is(List.of())); + } + + private static class DummyService implements SomeProvider.SomeService { + @Override + public String prop() { + return null; + } + + @Override + public String name() { + return null; + } + + @Override + public String type() { + return null; + } + } + +} diff --git a/builder/tests/codegen/pom.xml b/builder/tests/codegen/pom.xml new file mode 100644 index 00000000000..6d0ae10cf9d --- /dev/null +++ b/builder/tests/codegen/pom.xml @@ -0,0 +1,68 @@ + + + + + + io.helidon.builder.tests + helidon-builder-tests-project + 4.0.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + helidon-builder-tests-codegen + Helidon Builder Tests Codegen + + + + + io.helidon.builder + helidon-builder-codegen + test + + + + io.helidon.common + helidon-common-config + test + + + io.helidon.service + helidon-service-registry + test + + + io.helidon.builder + helidon-builder-api + test + + + org.hamcrest + hamcrest-all + test + + + org.junit.jupiter + junit-jupiter-api + test + + + diff --git a/builder/tests/codegen/src/test/java/io/helidon/builder/codegen/TypesTest.java b/builder/tests/codegen/src/test/java/io/helidon/builder/codegen/TypesTest.java new file mode 100644 index 00000000000..f5c1d35db9c --- /dev/null +++ b/builder/tests/codegen/src/test/java/io/helidon/builder/codegen/TypesTest.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2024 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.builder.codegen; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import io.helidon.builder.api.Description; +import io.helidon.builder.api.GeneratedBuilder; +import io.helidon.builder.api.Option; +import io.helidon.builder.api.Prototype; +import io.helidon.builder.api.RuntimeType; +import io.helidon.common.Generated; +import io.helidon.common.config.Config; +import io.helidon.common.types.TypeName; +import io.helidon.service.registry.GeneratedService; +import io.helidon.service.registry.GlobalServiceRegistry; +import io.helidon.service.registry.ServiceRegistry; + +import org.hamcrest.CoreMatchers; +import org.hamcrest.collection.IsEmptyCollection; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +public class TypesTest { + @Test + void testTypes() { + // it is really important to test ALL constants on the class, so let's use reflection + Field[] declaredFields = Types.class.getDeclaredFields(); + + Set toCheck = new HashSet<>(); + Set checked = new HashSet<>(); + Map fields = new HashMap<>(); + + for (Field declaredField : declaredFields) { + String name = declaredField.getName(); + + assertThat(name + " must be a TypeName", declaredField.getType(), CoreMatchers.sameInstance(TypeName.class)); + assertThat(name + " must be static", Modifier.isStatic(declaredField.getModifiers()), is(true)); + assertThat(name + " must be package local, not public", + Modifier.isPublic(declaredField.getModifiers()), + is(false)); + assertThat(name + " must be package local, not private", + Modifier.isPrivate(declaredField.getModifiers()), + is(false)); + assertThat(name + " must be package local, not protected", + Modifier.isProtected(declaredField.getModifiers()), + is(false)); + assertThat(name + " must be final", Modifier.isFinal(declaredField.getModifiers()), is(true)); + + toCheck.add(name); + fields.put(name, declaredField); + } + + checkField(toCheck, checked, fields, "COMMON_CONFIG", Config.class); + checkField(toCheck, checked, fields, "GENERATED", Generated.class); + checkField(toCheck, checked, fields, "DEPRECATED", Deprecated.class); + checkField(toCheck, checked, fields, "LINKED_HASH_MAP", LinkedHashMap.class); + checkField(toCheck, checked, fields, "ARRAY_LIST", ArrayList.class); + checkField(toCheck, checked, fields, "LINKED_HASH_SET", LinkedHashSet.class); + checkField(toCheck, checked, fields, "CHAR_ARRAY", char[].class); + checkField(toCheck, checked, fields, "SERVICE_REGISTRY", ServiceRegistry.class); + checkField(toCheck, checked, fields, "GLOBAL_SERVICE_REGISTRY", GlobalServiceRegistry.class); + checkField(toCheck, checked, fields, "GENERATED_SERVICE", GeneratedService.class); + checkField(toCheck, checked, fields, "BUILDER_DESCRIPTION", Description.class); + checkField(toCheck, checked, fields, "PROTOTYPE_BLUEPRINT", Prototype.Blueprint.class); + checkField(toCheck, checked, fields, "PROTOTYPE_IMPLEMENT", Prototype.Implement.class); + checkField(toCheck, checked, fields, "PROTOTYPE_API", Prototype.Api.class); + checkField(toCheck, checked, fields, "PROTOTYPE_ANNOTATED", Prototype.Annotated.class); + checkField(toCheck, checked, fields, "PROTOTYPE_FACTORY", Prototype.Factory.class); + checkField(toCheck, checked, fields, "PROTOTYPE_CONFIGURED", Prototype.Configured.class); + checkField(toCheck, checked, fields, "PROTOTYPE_BUILDER", Prototype.Builder.class); + checkField(toCheck, checked, fields, "PROTOTYPE_CONFIGURED_BUILDER", Prototype.ConfiguredBuilder.class); + checkField(toCheck, checked, fields, "PROTOTYPE_CUSTOM_METHODS", Prototype.CustomMethods.class); + checkField(toCheck, checked, fields, "PROTOTYPE_FACTORY_METHOD", Prototype.FactoryMethod.class); + checkField(toCheck, checked, fields, "PROTOTYPE_BUILDER_METHOD", Prototype.BuilderMethod.class); + checkField(toCheck, checked, fields, "PROTOTYPE_PROTOTYPE_METHOD", Prototype.PrototypeMethod.class); + checkField(toCheck, checked, fields, "PROTOTYPE_BUILDER_DECORATOR", Prototype.BuilderDecorator.class); + checkField(toCheck, checked, fields, "PROTOTYPE_CONSTANT", Prototype.Constant.class); + checkField(toCheck, checked, fields, "PROTOTYPE_SERVICE_REGISTRY", Prototype.RegistrySupport.class); + checkField(toCheck, checked, fields, "GENERATED_EQUALITY_UTIL", GeneratedBuilder.EqualityUtil.class); + checkField(toCheck, checked, fields, "RUNTIME_PROTOTYPE", RuntimeType.PrototypedBy.class); + checkField(toCheck, checked, fields, "RUNTIME_PROTOTYPED_BY", RuntimeType.PrototypedBy.class); + checkField(toCheck, checked, fields, "RUNTIME_API", RuntimeType.Api.class); + checkField(toCheck, checked, fields, "OPTION_SAME_GENERIC", Option.SameGeneric.class); + checkField(toCheck, checked, fields, "OPTION_SINGULAR", Option.Singular.class); + checkField(toCheck, checked, fields, "OPTION_CONFIDENTIAL", Option.Confidential.class); + checkField(toCheck, checked, fields, "OPTION_REDUNDANT", Option.Redundant.class); + checkField(toCheck, checked, fields, "OPTION_CONFIGURED", Option.Configured.class); + checkField(toCheck, checked, fields, "OPTION_ACCESS", Option.Access.class); + checkField(toCheck, checked, fields, "OPTION_REQUIRED", Option.Required.class); + checkField(toCheck, checked, fields, "OPTION_PROVIDER", Option.Provider.class); + checkField(toCheck, checked, fields, "OPTION_ALLOWED_VALUES", Option.AllowedValues.class); + checkField(toCheck, checked, fields, "OPTION_ALLOWED_VALUE", Option.AllowedValue.class); + checkField(toCheck, checked, fields, "OPTION_DEFAULT", Option.Default.class); + checkField(toCheck, checked, fields, "OPTION_DEFAULT_INT", Option.DefaultInt.class); + checkField(toCheck, checked, fields, "OPTION_DEFAULT_DOUBLE", Option.DefaultDouble.class); + checkField(toCheck, checked, fields, "OPTION_DEFAULT_BOOLEAN", Option.DefaultBoolean.class); + checkField(toCheck, checked, fields, "OPTION_DEFAULT_LONG", Option.DefaultLong.class); + checkField(toCheck, checked, fields, "OPTION_DEFAULT_METHOD", Option.DefaultMethod.class); + checkField(toCheck, checked, fields, "OPTION_DEFAULT_CODE", Option.DefaultCode.class); + checkField(toCheck, checked, fields, "OPTION_DEPRECATED", Option.Deprecated.class); + checkField(toCheck, checked, fields, "OPTION_TYPE", Option.Type.class); + checkField(toCheck, checked, fields, "OPTION_DECORATOR", Option.Decorator.class); + + assertThat(toCheck, IsEmptyCollection.empty()); + } + + private void checkField(Set namesToCheck, + Set checkedNames, + Map namesToFields, + String name, + Class expectedType) { + Field field = namesToFields.get(name); + assertThat("Field " + name + " does not exist in the class", field, notNullValue()); + try { + namesToCheck.remove(name); + if (checkedNames.add(name)) { + TypeName value = (TypeName) field.get(null); + assertThat("Field " + name, value.fqName(), is(expectedType.getCanonicalName())); + } else { + fail("Field " + name + " is checked more than once.class"); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/builder/tests/pom.xml b/builder/tests/pom.xml index 68c85a67f13..c21c203f44c 100644 --- a/builder/tests/pom.xml +++ b/builder/tests/pom.xml @@ -47,6 +47,7 @@ common-types builder + codegen diff --git a/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java b/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java index 80f1365eef9..b6c2ab98123 100644 --- a/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java +++ b/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistry.java @@ -21,6 +21,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -45,9 +46,11 @@ class CoreServiceRegistry implements ServiceRegistry { .thenComparing(ServiceProvider::descriptorType); private final Map> providersByContract; + private final Map providersByService; CoreServiceRegistry(ServiceRegistryConfig config, ServiceDiscovery serviceDiscovery) { Map> providers = new HashMap<>(); + Map providersByService = new IdentityHashMap<>(); // each just once Set processedDescriptorTypes = new HashSet<>(); @@ -61,6 +64,7 @@ class CoreServiceRegistry implements ServiceRegistry { config.serviceInstances().forEach((descriptor, instance) -> { if (processedDescriptorTypes.add(descriptor.descriptorType())) { BoundInstance bi = new BoundInstance(descriptor, Optional.of(instance)); + providersByService.put(descriptor, bi); addContracts(providers, descriptor.contracts(), bi); } }); @@ -69,6 +73,7 @@ class CoreServiceRegistry implements ServiceRegistry { for (Descriptor descriptor : config.serviceDescriptors()) { if (processedDescriptorTypes.add(descriptor.descriptorType())) { BoundDescriptor bd = new BoundDescriptor(this, descriptor, LazyValue.create(() -> instance(descriptor))); + providersByService.put(descriptor, bd); addContracts(providers, descriptor.contracts(), bd); } } @@ -85,14 +90,16 @@ class CoreServiceRegistry implements ServiceRegistry { } continue; } - if (processedDescriptorTypes.add(descriptorMeta.descriptorType())) { + if (processedDescriptorTypes.add(descriptorMeta.descriptor().serviceType())) { DiscoveredDescriptor dd = new DiscoveredDescriptor(this, descriptorMeta, LazyValue.create(() -> instance(descriptorMeta.descriptor()))); + providersByService.put(descriptorMeta.descriptor(), dd); addContracts(providers, descriptorMeta.contracts(), dd); } } this.providersByContract = Map.copyOf(providers); + this.providersByService = providersByService; } @Override @@ -138,6 +145,24 @@ public Supplier> supplyAll(TypeName contract) { return LazyValue.create(() -> all(contract)); } + @SuppressWarnings("unchecked") + @Override + public Optional get(ServiceInfo serviceInfo) { + return Optional.ofNullable(providersByService.get(serviceInfo)) + .flatMap(ServiceProvider::instance) + .map(it -> (T) it); + } + + @Override + public List allServices(TypeName contract) { + return Optional.ofNullable(providersByContract.get(contract)) + .orElseGet(Set::of) + .stream() + .map(ServiceProvider::descriptor) + .collect(Collectors.toUnmodifiableList()); + + } + private static void addContracts(Map> providers, Set contracts, ServiceProvider provider) { diff --git a/service/registry/src/main/java/io/helidon/service/registry/GeneratedService.java b/service/registry/src/main/java/io/helidon/service/registry/GeneratedService.java index 506c42c56a6..624b9becb40 100644 --- a/service/registry/src/main/java/io/helidon/service/registry/GeneratedService.java +++ b/service/registry/src/main/java/io/helidon/service/registry/GeneratedService.java @@ -16,13 +16,316 @@ package io.helidon.service.registry; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import io.helidon.builder.api.Prototype; +import io.helidon.common.config.Config; +import io.helidon.common.config.ConfigException; +import io.helidon.common.config.ConfiguredProvider; +import io.helidon.common.config.NamedService; + /** * All types in this class are used from generated code for services. */ public final class GeneratedService { + private static final System.Logger PROVIDER_LOGGER = System.getLogger(Prototype.class.getName() + ".provider"); + + /** + * Special configuration key that can be defined on provided options (loaded through ServiceLoader) that defines + * the mapping to a provider. + * Type of service, used to map to {@link io.helidon.common.config.ConfiguredProvider#configKey()}, which is the + * default "type" of a configured provider. It is then used in {@link io.helidon.common.config.NamedService#type()}, + * to allow multiple instances of the same "type" with different "name". + */ + private static final String KEY_SERVICE_TYPE = "type"; + /** + * Special configuration key that can be defined on provided options (loaded through ServiceLoader) that defines + * the name of an instance. + * It is then used in {@link io.helidon.common.config.NamedService#name()} + * to allow multiple instances of the same "type" with different "name". + * In case of object type configurations, name is taken from the configuration node name. + */ + private static final String KEY_SERVICE_NAME = "name"; + /** + * Special configuration key that can be defined on provided options (loaded through ServiceLoader) that defines + * whether a service provider of the type is enabled. + * Each service from a {@link io.helidon.common.config.ConfiguredProvider} can be enabled/disabled through + * configuration. If marked as {@code enabled = false}, the service will be ignored and not added. + */ + private static final String KEY_SERVICE_ENABLED = "enabled"; + private GeneratedService() { } + /** + * Used to discover services from {@link io.helidon.service.registry.ServiceRegistry} for builder options annotated + * with {@link io.helidon.builder.api.Option.Provider}, if the blueprint is annotated with + * {@link io.helidon.builder.api.Prototype.RegistrySupport}. + * + * @param config configuration of the option + * @param configKey configuration key associated with this option + * @param serviceRegistry service registry instance + * @param providerType type of the service provider (contract) + * @param configType type of the configuration + * @param allFromRegistry whether to use all services from the registry + * @param existingValues existing values that was explicitly configured by the user + * @return instances from the user augmented with instances from the registry + * @param type of the service + */ + public static List discoverServices(Config config, + String configKey, + ServiceRegistry serviceRegistry, + Class> providerType, + Class configType, + boolean allFromRegistry, + List existingValues) { + + // type and name is a unique identification of a service - for services already defined on the builder + // do not add them from configuration (as this would duplicate service instances) + Set ignoredServices = new HashSet<>(); + existingValues.forEach(it -> ignoredServices.add(new TypeAndName(it.type(), it.name()))); + + boolean discoverServices = config.get(configKey + "-discover-services").asBoolean().orElse(allFromRegistry); + Config providersConfig = config.get(configKey); + + List configuredServices = new ArrayList<>(); + + // all child nodes of the current node + List serviceConfigList = providersConfig.asNodeList() + .orElseGet(List::of); + boolean isList = providersConfig.isList(); + + for (Config serviceConfig : serviceConfigList) { + configuredServices.add(configuredService(serviceConfig, isList)); + } + + // now we have all service configurations, we can start building up instances + if (providersConfig.isList()) { + // driven by order of declaration in config + return servicesFromList(serviceRegistry, + providerType, + configType, + configuredServices, + discoverServices, + ignoredServices); + } else { + // driven by service loader order + return servicesFromObject(providersConfig, + serviceRegistry, + providerType, + configType, + configuredServices, + discoverServices, + ignoredServices); + } + } + + /** + * Used to discover service from {@link io.helidon.service.registry.ServiceRegistry} for builder options annotated + * with {@link io.helidon.builder.api.Option.Provider}, if the blueprint is annotated with + * {@link io.helidon.builder.api.Prototype.RegistrySupport}. + * + * @param config configuration of the option + * @param configKey configuration key associated with this option + * @param serviceRegistry service registry instance + * @param providerType type of the service provider (contract) + * @param configType type of the configuration + * @param discoverServices whether to discover services from registry + * @param existingValue existing value that was explicitly configured by the user + * @return an instance, if available in the registry, or if provided by the user (user's value wins) + * @param type of the service + */ + public static Optional + discoverService(Config config, + String configKey, + ServiceRegistry serviceRegistry, + Class> providerType, + Class configType, + boolean discoverServices, + Optional existingValue) { + + // there is an explicit configuration for this service, ignore configuration + if (existingValue.isPresent()) { + return Optional.empty(); + } + + // all child nodes of the current node + List serviceConfigList = config.get(configKey).asNodeList() + .orElseGet(List::of); + + // if more than one is configured in config, fail + // if more than one exists in service loader, use the first one + if (serviceConfigList.size() > 1) { + throw new ConfigException("There can only be one provider configured for " + config.key()); + } + + List services = discoverServices(config, + configKey, + serviceRegistry, + providerType, + configType, + discoverServices, + List.of()); + + return services.isEmpty() ? Optional.empty() : Optional.of(services.getFirst()); + } + + private static ConfiguredService configuredService(Config serviceConfig, boolean isList) { + if (isList) { + // order is significant + String type = serviceConfig.get(KEY_SERVICE_TYPE).asString().orElse(null); + String name = serviceConfig.get(KEY_SERVICE_NAME).asString().orElse(type); + boolean enabled = serviceConfig.get(KEY_SERVICE_ENABLED).asBoolean().orElse(true); + + Config usedConfig = serviceConfig; + if (type == null) { + // nested approach (we are on the first node of the list, we need to go deeper) + List configs = serviceConfig.asNodeList().orElseGet(List::of); + if (configs.size() != 1) { + throw new ConfigException( + "Service provider configuration defined as a list must have a single node that is the type, " + + "with children containing the provider configuration. Failed on: " + serviceConfig.key()); + } + usedConfig = configs.get(0); + name = usedConfig.name(); + type = usedConfig.get(KEY_SERVICE_TYPE).asString().orElse(name); + enabled = usedConfig.get(KEY_SERVICE_ENABLED).asBoolean().orElse(enabled); + } + return new ConfiguredService(new TypeAndName(type, name), usedConfig, enabled); + } + // just collect each node, order will be determined by weight + + String name = serviceConfig.name(); // name is the config node name for object types + String type = serviceConfig.get(KEY_SERVICE_TYPE).asString().orElse(name); + boolean enabled = serviceConfig.get(KEY_SERVICE_ENABLED).asBoolean().orElse(true); + + return new ConfiguredService(new TypeAndName(type, name), serviceConfig, enabled); + } + + private static List + servicesFromList(ServiceRegistry serviceRegistry, + Class> providerType, + Class configType, + List configuredServices, + boolean allFromServiceLoader, + Set ignoredServices) { + Map> allProvidersByType = new HashMap<>(); + Map> unusedProvidersByType = new LinkedHashMap<>(); + + serviceRegistry.all(providerType) + .forEach(provider -> { + allProvidersByType.put(provider.configKey(), provider); + unusedProvidersByType.put(provider.configKey(), provider); + }); + + List result = new ArrayList<>(); + + // first add all configured + for (ConfiguredService service : configuredServices) { + TypeAndName typeAndName = service.typeAndName(); + if (!ignoredServices.add(typeAndName)) { + unusedProvidersByType.remove(typeAndName.type()); + + if (PROVIDER_LOGGER.isLoggable(System.Logger.Level.DEBUG)) { + PROVIDER_LOGGER.log(System.Logger.Level.DEBUG, "Service: " + typeAndName + + " is already added in builder, ignoring configured one."); + } + + continue; + } + ConfiguredProvider provider = allProvidersByType.get(typeAndName.type()); + if (provider == null) { + throw new ConfigException("Unknown provider configured. Expecting a provider with type \"" + typeAndName.type() + + "\", but only the following providers are supported: " + + allProvidersByType.keySet() + ", " + + "provider interface: " + providerType.getName() + + ", configured service: " + configType.getName()); + } + unusedProvidersByType.remove(typeAndName.type()); + if (service.enabled()) { + result.add(provider.create(service.serviceConfig(), typeAndName.name())); + } + } + + // then (if desired) add the rest + if (allFromServiceLoader) { + unusedProvidersByType.forEach((type, provider) -> { + if (ignoredServices.add(new TypeAndName(type, type))) { + result.add(provider.create(Config.empty(), type)); + } + }); + } + + return result; + } + + private static List + servicesFromObject(Config providersConfig, + ServiceRegistry serviceRegistry, + Class> providerType, + Class configType, + List configuredServices, + boolean allFromServiceLoader, + Set ignoredServices) { + // order is determined by service loader + Set availableProviders = new HashSet<>(); + Map allConfigs = new HashMap<>(); + configuredServices.forEach(it -> allConfigs.put(it.typeAndName().type, it)); + Set unusedConfigs = new HashSet<>(allConfigs.keySet()); + + List result = new ArrayList<>(); + + List> all = serviceRegistry.all(providerType); + for (ConfiguredProvider provider : all) { + ConfiguredService configuredService = allConfigs.get(provider.configKey()); + availableProviders.add(provider.configKey()); + unusedConfigs.remove(provider.configKey()); + if (configuredService == null) { + if (allFromServiceLoader) { + // even though the specific key does not exist, we want to have the real config tree, so we can get to the + // root of it + // when there is no configuration, the name defaults to the type + String type = provider.configKey(); + if (ignoredServices.add(new TypeAndName(type, type))) { + result.add(provider.create(providersConfig.get(type), type)); + } else { + if (PROVIDER_LOGGER.isLoggable(System.Logger.Level.DEBUG)) { + PROVIDER_LOGGER.log(System.Logger.Level.DEBUG, "Service: " + new TypeAndName(type, type) + + " is already added in builder, ignoring configured one."); + } + } + } + } else { + if (configuredService.enabled()) { + if (ignoredServices.add(configuredService.typeAndName())) { + result.add(provider.create(configuredService.serviceConfig(), + configuredService.typeAndName().name())); + } else { + if (PROVIDER_LOGGER.isLoggable(System.Logger.Level.DEBUG)) { + PROVIDER_LOGGER.log(System.Logger.Level.DEBUG, "Service: " + configuredService.typeAndName() + + " is already added in builder, ignoring configured one."); + } + } + } + } + } + + if (!unusedConfigs.isEmpty()) { + throw new ConfigException("Unknown provider configured. Expected providers with types: " + unusedConfigs + + ", but only the following providers are supported: " + availableProviders + + ", provider interface: " + providerType.getName() + + ", configured service: " + configType.getName()); + } + return result; + } + /** * A descriptor of a service. In addition to providing service metadata, this also allows instantiation * of the service instance, with dependent services as parameters. @@ -42,4 +345,10 @@ default Object instantiate(DependencyContext ctx) { + " or an interface."); } } + + private record TypeAndName(String type, String name) { + } + + private record ConfiguredService(TypeAndName typeAndName, Config serviceConfig, boolean enabled) { + } } diff --git a/service/registry/src/main/java/io/helidon/service/registry/ServiceLoader__ServiceDescriptor.java b/service/registry/src/main/java/io/helidon/service/registry/ServiceLoader__ServiceDescriptor.java index a2a39a053d3..9b4207b3894 100644 --- a/service/registry/src/main/java/io/helidon/service/registry/ServiceLoader__ServiceDescriptor.java +++ b/service/registry/src/main/java/io/helidon/service/registry/ServiceLoader__ServiceDescriptor.java @@ -94,6 +94,13 @@ private ServiceProviderDescriptor(TypeName providerInterface, this.instance = LazyValue.create(this::newInstance); } + @Override + public String toString() { + return providerInterface + "/" + + providerImpl + + "(" + weight + ")"; + } + @Override public TypeName serviceType() { return providerImpl; diff --git a/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry.java b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry.java index c59964e569e..2bf35bf26fb 100644 --- a/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry.java +++ b/service/registry/src/main/java/io/helidon/service/registry/ServiceRegistry.java @@ -179,15 +179,31 @@ default Supplier> supplyAll(Class contract) { * from the service descriptor, or instances provided by {@link #allServices(Class)}. * * @param serviceInfo service info instance - * @return value of the service described by the service info provided (always a single value), in case the + * @return value of the service described by the service info provided (always a single value), as there is support + * for providers that are {@link java.util.function.Supplier} of an instance, and that may return + * {@link java.util.Optional}, we may not get a value, hence we return {@link java.util.Optional} as well * @param type of the expected instance, we just cast to it, so this may cause runtime issues if assigned to invalid * type */ Optional get(ServiceInfo serviceInfo); + /** + * Get all services for a specific contract. The list may be empty if there are no services available. + * To get an instance, use {@link #get(ServiceInfo)}. + * + * @param contract contract we look for + * @return list of service metadata of services that satisfy the provided contract + */ default List allServices(Class contract) { return allServices(TypeName.create(contract)); } + /** + * Get all services for a specific contract. The list may be empty if there are no services available. + * To get an instance, use {@link #get(ServiceInfo)}. + * + * @param contract contract we look for + * @return list of service metadata of services that satisfy the provided contract + */ List allServices(TypeName contract); } diff --git a/service/tests/codegen/src/test/java/io/helidon/service/tests/codegen/ServiceCodegenTypesTest.java b/service/tests/codegen/src/test/java/io/helidon/service/tests/codegen/ServiceCodegenTypesTest.java index 29cf19a2688..c0ccc588d80 100644 --- a/service/tests/codegen/src/test/java/io/helidon/service/tests/codegen/ServiceCodegenTypesTest.java +++ b/service/tests/codegen/src/test/java/io/helidon/service/tests/codegen/ServiceCodegenTypesTest.java @@ -42,7 +42,7 @@ class ServiceCodegenTypesTest { @Test void testTypes() { - // it is really important to test ALL constants on the class, so let's use relfection + // it is really important to test ALL constants on the class, so let's use reflection Field[] declaredFields = ServiceCodegenTypes.class.getDeclaredFields(); Set toCheck = new HashSet<>(); diff --git a/tests/integration/native-image/mp-1/src/main/resources/META-INF/native-image/io.helidon.tests.integration.native-image/helidon-tests-native-image-mp1/native-image.properties b/tests/integration/native-image/mp-1/src/main/resources/META-INF/native-image/io.helidon.tests.integration.native-image/helidon-tests-native-image-mp1/native-image.properties index 8b0df87467f..062926ebbde 100644 --- a/tests/integration/native-image/mp-1/src/main/resources/META-INF/native-image/io.helidon.tests.integration.native-image/helidon-tests-native-image-mp1/native-image.properties +++ b/tests/integration/native-image/mp-1/src/main/resources/META-INF/native-image/io.helidon.tests.integration.native-image/helidon-tests-native-image-mp1/native-image.properties @@ -14,4 +14,7 @@ # limitations under the License. # -Args=--initialize-at-build-time=io.helidon.tests.integration.nativeimage.mp1 +Args=--initialize-at-build-time=io.helidon.tests.integration.nativeimage.mp1 \ + --initialize-at-build-time=io.helidon.common.features \ + --initialize-at-build-time=io.helidon.jersey.client.JerseyClientBuilderListener \ + --initialize-at-build-time=io.helidon.logging.jul.JulMdcPropagator From 74d82570395988f6d4bbedba30123effa8961639 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Fri, 17 May 2024 17:39:18 +0200 Subject: [PATCH 17/20] Copyright fix. --- .../helidon-tests-native-image-mp1/native-image.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/native-image/mp-1/src/main/resources/META-INF/native-image/io.helidon.tests.integration.native-image/helidon-tests-native-image-mp1/native-image.properties b/tests/integration/native-image/mp-1/src/main/resources/META-INF/native-image/io.helidon.tests.integration.native-image/helidon-tests-native-image-mp1/native-image.properties index 062926ebbde..f53634fa17d 100644 --- a/tests/integration/native-image/mp-1/src/main/resources/META-INF/native-image/io.helidon.tests.integration.native-image/helidon-tests-native-image-mp1/native-image.properties +++ b/tests/integration/native-image/mp-1/src/main/resources/META-INF/native-image/io.helidon.tests.integration.native-image/helidon-tests-native-image-mp1/native-image.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2023 Oracle and/or its affiliates. +# Copyright (c) 2023, 2024 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. From adfee52271148f903f079b0504a8d895acbde46e Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Mon, 20 May 2024 17:17:25 +0200 Subject: [PATCH 18/20] Support custom IMDS URI. Support for configurable timeout for calls to IMDS (with a non-zero default). Fix tests to work on a machine that has oci config --- .../oci/AtnStrategyInstancePrincipal.java | 16 +++++++++++---- .../oci/AtnStrategyResourcePrincipal.java | 11 +++++++++- .../integrations/oci/OciConfigBlueprint.java | 20 +++++++++++++++++++ .../integrations/oci/OciConfigSupport.java | 5 +++++ .../integrations/oci/OciIntegrationTest.java | 11 +++++++++- 5 files changed, 57 insertions(+), 6 deletions(-) diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java index 47925a189c1..154c3c6fc18 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.lang.System.Logger.Level; import java.net.InetAddress; +import java.net.URI; import java.time.Duration; import java.util.Optional; @@ -63,18 +64,25 @@ public Optional provider() { private static LazyValue> createProvider(OciConfig config) { return LazyValue.create(() -> { - if (imdsVailable(config)) { - return Optional.of(InstancePrincipalsAuthenticationDetailsProvider.builder().build()); + if (imdsAvailable(config)) { + var builder = InstancePrincipalsAuthenticationDetailsProvider.builder() + .timeoutForEachRetry((int) config.atnTimeout().toMillis()); + + config.imdsBaseUri() + .map(URI::toString) + .ifPresent(builder::metadataBaseUrl); + + return Optional.of(builder.build()); } return Optional.empty(); }); } - private static boolean imdsVailable(OciConfig config) { + private static boolean imdsAvailable(OciConfig config) { Duration timeout = config.imdsTimeout(); try { - if (InetAddress.getByName(IMDS_ADDRESS) + if (InetAddress.getByName(config.imdsBaseUri().map(URI::getHost).orElse(IMDS_ADDRESS)) .isReachable((int) timeout.toMillis())) { return RegionProviderSdk.regionFromImds() != null; } diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyResourcePrincipal.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyResourcePrincipal.java index 7c942861a7f..2b856fc00a5 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyResourcePrincipal.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyResourcePrincipal.java @@ -17,6 +17,7 @@ package io.helidon.integrations.oci; import java.lang.System.Logger.Level; +import java.net.URI; import java.util.Optional; import io.helidon.common.LazyValue; @@ -65,7 +66,15 @@ private static LazyValue> create } return Optional.empty(); } - return Optional.of(ResourcePrincipalAuthenticationDetailsProvider.builder().build()); + var builder = ResourcePrincipalAuthenticationDetailsProvider.builder() + .timeoutForEachRetry((int) config.atnTimeout().toMillis()); + + // we expect the full metadata base URI (including http:// and /opc/v2/) + config.imdsBaseUri() + .map(URI::toString) + .ifPresent(builder::metadataBaseUrl); + + return Optional.of(builder.build()); }); } } diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java index 06f08d9b614..cdc554afbeb 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java @@ -16,6 +16,7 @@ package io.helidon.integrations.oci; +import java.net.URI; import java.time.Duration; import java.util.List; import java.util.Optional; @@ -113,4 +114,23 @@ interface OciConfigBlueprint { @Option.Configured @Option.Default("PT0.1S") Duration imdsTimeout(); + + /** + * The OCI IMDS URI (http URL pointing to the metadata service, if customization needed. + * + * @return the OCI IMDS URI + */ + @Option.Configured + Optional imdsBaseUri(); + + /** + * Timeout of authentication operations, where applicable. + * This is a timeout for each operation (if there are retries, each timeout will be this duration). + * Defaults to 10 seconds. + * + * @return authentication operation timeout + */ + @Option.Configured + @Option.Default("PT10S") + Duration atnTimeout(); } diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigSupport.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigSupport.java index c410e47d8ce..7a09a431279 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigSupport.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigSupport.java @@ -22,6 +22,11 @@ import com.oracle.bmc.Region; final class OciConfigSupport { + /** + * Primary hostname of metadata service. + */ + static final String IMDS_HOSTNAME = "169.254.169.254"; + private OciConfigSupport() { } diff --git a/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java b/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java index 80b2e83bf17..2340e694c7e 100644 --- a/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java +++ b/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java @@ -61,7 +61,13 @@ void tearDown() { @Test void testNoStrategyAvailable() { - Config config = Config.empty(); + String yamlConfig = """ + helidon.oci: + config-file-strategy: + # we must use a file that does not exist, if this machine has actual oci config file + path: src/test/resources/test-oci-config-not-there + """; + Config config = Config.just(ConfigSources.create(yamlConfig, MediaTypes.APPLICATION_YAML)); setUp(config); OciAtnStrategy atnStrategy = registry.get(AtnStrategyConfig.class); @@ -106,6 +112,9 @@ void testConfigStrategyAvailable() { passphrase: passphrase tenant-id: tenant user-id: user + config-file-strategy: + # we must use a file that does not exist, if this machine has actual oci config file + path: src/test/resources/test-oci-config-not-there """; Config config = Config.just(ConfigSources.create(yamlConfig, MediaTypes.APPLICATION_YAML)); setUp(config); From fa0f95851e93c0fdd649e6c9ee1a6e55f506fcaa Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Tue, 21 May 2024 16:01:40 +0200 Subject: [PATCH 19/20] Introduction of OCI integration test Small fix of SDK region provider to make sure metadata endpoint is available to prevent retries Fix of constant location. --- .github/workflows/validate.yml | 8 + integrations/oci/oci/pom.xml | 38 ++- .../oci/AtnStrategyInstancePrincipal.java | 13 +- .../integrations/oci/OciConfigSupport.java | 2 + .../integrations/oci/RegionProviderSdk.java | 21 +- .../integrations/oci/OciIntegrationIT.java | 217 ++++++++++++++++++ .../integrations/oci/OciIntegrationTest.java | 3 + .../test/resources/logging-test.properties | 21 ++ 8 files changed, 309 insertions(+), 14 deletions(-) create mode 100644 integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationIT.java create mode 100644 integrations/oci/oci/src/test/resources/logging-test.properties diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 7e077c32807..c0bde467698 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -72,6 +72,7 @@ jobs: - name: Docs run: etc/scripts/docs.sh build: + environment: pipeline timeout-minutes: 60 strategy: matrix: @@ -86,6 +87,12 @@ jobs: java-version: ${{ env.JAVA_VERSION }} cache: maven - name: Maven build + env: + HELIDON_OCI_IT_REGION: ${{ secrets.HELIDON_OCI_IT_REGION }} + HELIDON_OCI_IT_TENANCY: ${{ secrets.HELIDON_OCI_IT_TENANCY }} + HELIDON_OCI_IT_USER: ${{ secrets.HELIDON_OCI_IT_USER }} + HELIDON_OCI_IT_FINGERPRINT: ${{ secrets.HELIDON_OCI_IT_FINGERPRINT }} + HELIDON_OCI_IT_KEY: ${{ secrets.HELIDON_OCI_IT_KEY }} run: etc/scripts/github-build.sh examples: timeout-minutes: 30 @@ -162,3 +169,4 @@ jobs: run: etc/scripts/test-packaging-jlink.sh - name: Native-Image packaging run: etc/scripts/test-packaging-native.sh + diff --git a/integrations/oci/oci/pom.xml b/integrations/oci/oci/pom.xml index 2ab93efb089..c38f893d5ca 100644 --- a/integrations/oci/oci/pom.xml +++ b/integrations/oci/oci/pom.xml @@ -15,8 +15,8 @@ limitations under the License. --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 io.helidon.integrations.oci @@ -72,10 +72,44 @@ slf4j-jdk14 test + + com.oracle.oci.sdk + oci-java-sdk-objectstorage + test + + + com.oracle.oci.sdk + oci-java-sdk-common-httpclient-jersey3 + test + + + io.helidon.logging + helidon-logging-jul + test + + + org.apache.maven.plugins + maven-failsafe-plugin + + + **/*IT.java + + + + + verify + verify + + integration-test + verify + + + + org.apache.maven.plugins maven-compiler-plugin diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java index 154c3c6fc18..5a9730f108b 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/AtnStrategyInstancePrincipal.java @@ -32,6 +32,8 @@ import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; import com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider; +import static io.helidon.integrations.oci.OciConfigSupport.IMDS_HOSTNAME; + /** * Instance principal authentication strategy, uses the * {@link com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider}. @@ -41,9 +43,6 @@ class AtnStrategyInstancePrincipal implements OciAtnStrategy { static final String STRATEGY = "instance-principal"; - // we do not use the constant, as it is marked as internal, and we only need the IP address anyway - // see com.oracle.bmc.auth.AbstractFederationClientAuthenticationDetailsProviderBuilder.METADATA_SERVICE_BASE_URL - private static final String IMDS_ADDRESS = "169.254.169.254"; private static final System.Logger LOGGER = System.getLogger(AtnStrategyInstancePrincipal.class.getName()); private final LazyValue> provider; @@ -78,18 +77,18 @@ private static LazyValue> create }); } - private static boolean imdsAvailable(OciConfig config) { + static boolean imdsAvailable(OciConfig config) { Duration timeout = config.imdsTimeout(); try { - if (InetAddress.getByName(config.imdsBaseUri().map(URI::getHost).orElse(IMDS_ADDRESS)) + if (InetAddress.getByName(config.imdsBaseUri().map(URI::getHost).orElse(IMDS_HOSTNAME)) .isReachable((int) timeout.toMillis())) { - return RegionProviderSdk.regionFromImds() != null; + return RegionProviderSdk.regionFromImds(config) != null; } return false; } catch (IOException e) { LOGGER.log(Level.TRACE, - "imds service is not reachable, or timed out for address: " + IMDS_ADDRESS + ", instance principal " + "imds service is not reachable, or timed out for address: " + IMDS_HOSTNAME + ", instance principal " + "strategy is not available.", e); return false; diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigSupport.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigSupport.java index 7a09a431279..853e0403939 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigSupport.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigSupport.java @@ -25,6 +25,8 @@ final class OciConfigSupport { /** * Primary hostname of metadata service. */ + // we do not use the constant, as it is marked as internal, and we only need the IP address anyway + // see com.oracle.bmc.auth.AbstractFederationClientAuthenticationDetailsProviderBuilder.METADATA_SERVICE_BASE_URL static final String IMDS_HOSTNAME = "169.254.169.254"; private OciConfigSupport() { diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderSdk.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderSdk.java index cdb3cc7ca7b..9d8034a8abe 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderSdk.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/RegionProviderSdk.java @@ -16,7 +16,9 @@ package io.helidon.integrations.oci; +import java.net.URI; import java.util.Optional; +import java.util.function.Supplier; import io.helidon.common.LazyValue; import io.helidon.common.Weight; @@ -31,17 +33,26 @@ class RegionProviderSdk implements OciRegion { private final LazyValue> region; - RegionProviderSdk() { - this.region = LazyValue.create(() -> Optional.ofNullable(regionFromImds())); + RegionProviderSdk(Supplier config) { + this.region = LazyValue.create(() -> Optional.ofNullable(regionFromImds(config.get()))); } /** * There is a 30 second timeout configured, so this has a relatively low weight. * We want a different way to get the region if available. */ - static Region regionFromImds() { - Region.registerFromInstanceMetadataService(); - return Region.getRegionFromImds(); + static Region regionFromImds(OciConfig ociConfig) { + if (AtnStrategyInstancePrincipal.imdsAvailable(ociConfig)) { + Optional uri = ociConfig.imdsBaseUri(); + return uri.map(URI::toString) + .map(Region::getRegionFromImds) + .orElseGet(() -> { + Region.registerFromInstanceMetadataService(); + return Region.getRegionFromImds(); + }); + + } + return null; } @Override diff --git a/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationIT.java b/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationIT.java new file mode 100644 index 00000000000..8b0c528ced3 --- /dev/null +++ b/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationIT.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2024 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.integrations.oci; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Optional; + +import io.helidon.common.configurable.Resource; +import io.helidon.common.media.type.MediaTypes; +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import io.helidon.logging.common.LogConfig; +import io.helidon.service.registry.ServiceRegistryManager; + +import com.oracle.bmc.Region; +import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider; +import com.oracle.bmc.objectstorage.ObjectStorageClient; +import com.oracle.bmc.objectstorage.requests.GetNamespaceRequest; +import com.oracle.bmc.objectstorage.responses.GetNamespaceResponse; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalPresent; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class OciIntegrationIT { + + private static final String ENV_USER = "HELIDON_OCI_IT_USER"; + private static final String ENV_FINGERPRINT = "HELIDON_OCI_IT_FINGERPRINT"; + private static final String ENV_TENANCY = "HELIDON_OCI_IT_TENANCY"; + private static final String ENV_REGION = "HELIDON_OCI_IT_REGION"; + private static final String ENV_PRIVATE_KEY = "HELIDON_OCI_IT_KEY"; + + private static TestConfiguration testConfig; + private static Path testOciConfig; + private static Path testOciPrivateKey; + + @BeforeAll + static void beforeAll() throws IOException { + LogConfig.configureRuntime(); + + testConfig = testConfiguration(); + createFiles(); + } + + @AfterAll + static void afterAll() throws IOException { + if (testOciPrivateKey != null) { + Files.deleteIfExists(testOciPrivateKey); + } + if (testOciConfig != null) { + Files.deleteIfExists(testOciConfig); + } + } + + @Test + void testRegionFromAtnProvider() { + String yamlConfig = """ + helidon.oci: + config-strategy: + region: ${region} + fingerprint: ${fingerprint} + tenant-id: ${tenant} + user-id: ${user} + privateKey: + file: ${privateKey} + config-file-strategy: + # we must use a file that does not exist, if this machine has actual oci config file + path: src/test/resources/test-oci-config-not-there + """; + + yamlConfig = yamlConfig.replace("${region}", testConfig.region); + yamlConfig = yamlConfig.replace("${fingerprint}", testConfig.fingerprint); + yamlConfig = yamlConfig.replace("${tenant}", testConfig.tenancy); + yamlConfig = yamlConfig.replace("${user}", testConfig.user); + yamlConfig = yamlConfig.replace("${privateKey}", testOciPrivateKey.toAbsolutePath().toString()); + + Config config = Config.just(ConfigSources.create(yamlConfig, MediaTypes.APPLICATION_YAML)); + OciConfigProvider.config(OciConfig.create(config.get("helidon.oci"))); + var registryManager = ServiceRegistryManager.create(); + var registry = registryManager.registry(); + + try { + Region region = registry.get(Region.class); + assertThat(region, is(Region.fromRegionCodeOrId(testConfig.region))); + } finally { + registryManager.shutdown(); + } + } + + @Test + void testConnectConfig() { + AtnStrategyConfig c = new AtnStrategyConfig(OciConfig.builder() + .configStrategyConfig(cfg -> cfg.userId(testConfig.user) + .tenantId(testConfig.tenancy) + .fingerprint(testConfig.fingerprint) + .region(testConfig.region) + .privateKey(Resource.create("privateKey", + testConfig.privateKey))) + .build()); + Optional provider = c.provider(); + + assertThat(provider, optionalPresent()); + AbstractAuthenticationDetailsProvider atnProvider = provider.get(); + + testConnectivity(AtnStrategyConfig.STRATEGY, atnProvider); + } + + @Test + void testConnectConfigFile() { + + // run the test, delete the files when done + String ociConfigPath = testOciConfig.toAbsolutePath().toString(); + AtnStrategyConfigFile f = new AtnStrategyConfigFile(OciConfig.builder() + .configFileStrategyConfig(cfg -> + cfg.path(ociConfigPath)) + .build()); + Optional provider = f.provider(); + + assertThat(provider, optionalPresent()); + AbstractAuthenticationDetailsProvider atnProvider = provider.get(); + + testConnectivity(AtnStrategyConfigFile.STRATEGY, atnProvider); + } + + private static void createFiles() throws IOException { + testOciConfig = Paths.get("target/test-classes/generated-oci-config"); + testOciPrivateKey = Paths.get("target/test-classes/generated-oci-key"); + + Files.createDirectories(testOciConfig.getParent()); + + // create the OCI config file + try (var ociConfigWriter = new PrintWriter(Files.newBufferedWriter(testOciConfig, + StandardOpenOption.CREATE, + StandardOpenOption.WRITE, + StandardOpenOption.TRUNCATE_EXISTING))) { + ociConfigWriter.println("[DEFAULT]"); + ociConfigWriter.print("user="); + ociConfigWriter.println(testConfig.user); + ociConfigWriter.print("tenancy="); + ociConfigWriter.println(testConfig.tenancy); + ociConfigWriter.print("fingerprint="); + ociConfigWriter.println(testConfig.fingerprint); + ociConfigWriter.print("region="); + ociConfigWriter.println(testConfig.region); + ociConfigWriter.println("key_file=" + testOciPrivateKey.toAbsolutePath()); + } + + // create the private key file + try (var ociPrivateKeyWriter = Files.newBufferedWriter(testOciPrivateKey, + StandardOpenOption.CREATE, + StandardOpenOption.WRITE, + StandardOpenOption.TRUNCATE_EXISTING)) { + ociPrivateKeyWriter.write(testConfig.privateKey); + } + + } + + private static TestConfiguration testConfiguration() { + assumeTrue(System.getenv(ENV_USER) != null, "Missing environment variable '" + ENV_USER + "'"); + + String user = System.getenv(ENV_USER); + String fingerprint = System.getenv(ENV_FINGERPRINT); + String tenancy = System.getenv(ENV_TENANCY); + String region = System.getenv(ENV_REGION); + String privateKey = System.getenv(ENV_PRIVATE_KEY); + + assertThat(ENV_USER + " environment variable is required for OCI tests", user, notNullValue()); + assertThat(ENV_FINGERPRINT + " environment variable is required for OCI tests", fingerprint, notNullValue()); + assertThat(ENV_TENANCY + " environment variable is required for OCI tests", tenancy, notNullValue()); + assertThat(ENV_REGION + " environment variable is required for OCI tests", region, notNullValue()); + assertThat(ENV_PRIVATE_KEY + " environment variable is required for OCI tests", privateKey, notNullValue()); + + privateKey = "-----BEGIN PRIVATE KEY-----\n" + + privateKey.replace(' ', '\n') + + "\n-----END PRIVATE KEY-----\n"; + + return new TestConfiguration(user, tenancy, fingerprint, region, privateKey); + } + + private void testConnectivity(String strategy, AbstractAuthenticationDetailsProvider atnProvider) { + try (ObjectStorageClient osc = ObjectStorageClient.builder() + .build(atnProvider)) { + + GetNamespaceResponse namespace = osc.getNamespace(GetNamespaceRequest.builder().build()); + assertThat("Failed to get namespace for " + strategy + " strategy", + namespace.getValue(), + notNullValue()); + } + } + + private record TestConfiguration(String user, String tenancy, String fingerprint, String region, String privateKey) { + } +} diff --git a/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java b/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java index 2340e694c7e..513f465bde2 100644 --- a/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java +++ b/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationTest.java @@ -94,6 +94,9 @@ void testNoStrategyAvailable() { void testRegionFromConfig() { String yamlConfig = """ helidon.oci.region: us-phoenix-1 + config-file-strategy: + # we must use a file that does not exist, if this machine has actual oci config file + path: src/test/resources/test-oci-config-not-there """; Config config = Config.just(ConfigSources.create(yamlConfig, MediaTypes.APPLICATION_YAML)); setUp(config); diff --git a/integrations/oci/oci/src/test/resources/logging-test.properties b/integrations/oci/oci/src/test/resources/logging-test.properties new file mode 100644 index 00000000000..af2b395acad --- /dev/null +++ b/integrations/oci/oci/src/test/resources/logging-test.properties @@ -0,0 +1,21 @@ +# +# Copyright (c) 2024 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. +# + + +#All attributes details +handlers=io.helidon.logging.jul.HelidonConsoleHandler +java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s: %5$s%6$s%n +.level=INFO From a2462da7c529cfbcd12fcdf19ae81a0640edebbf Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Tue, 21 May 2024 16:03:11 +0200 Subject: [PATCH 20/20] build test commit (to be reverted) --- .github/workflows/validate.yml | 129 +----------------- .../integrations/oci/OciIntegrationIT.java | 3 - 2 files changed, 1 insertion(+), 131 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index c0bde467698..b7e3c497fb0 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -17,60 +17,6 @@ concurrency: cancel-in-progress: true jobs: - copyright: - timeout-minutes: 10 - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v4.1.0 - with: - distribution: ${{ env.JAVA_DISTRO }} - java-version: ${{ env.JAVA_VERSION }} - cache: maven - - name: Copyright - run: etc/scripts/copyright.sh - checkstyle: - timeout-minutes: 10 - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v4.1.0 - with: - distribution: ${{ env.JAVA_DISTRO }} - java-version: ${{ env.JAVA_VERSION }} - cache: maven - - name: Checkstyle - run: etc/scripts/checkstyle.sh - spotbugs: - timeout-minutes: 45 - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v4.1.0 - with: - distribution: ${{ env.JAVA_DISTRO }} - java-version: ${{ env.JAVA_VERSION }} - cache: maven - - name: Spotbugs - run: etc/scripts/spotbugs.sh - docs: - timeout-minutes: 30 - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v4.1.0 - with: - distribution: ${{ env.JAVA_DISTRO }} - java-version: ${{ env.JAVA_VERSION }} - cache: maven - - name: Docs - run: etc/scripts/docs.sh build: environment: pipeline timeout-minutes: 60 @@ -93,80 +39,7 @@ jobs: HELIDON_OCI_IT_USER: ${{ secrets.HELIDON_OCI_IT_USER }} HELIDON_OCI_IT_FINGERPRINT: ${{ secrets.HELIDON_OCI_IT_FINGERPRINT }} HELIDON_OCI_IT_KEY: ${{ secrets.HELIDON_OCI_IT_KEY }} - run: etc/scripts/github-build.sh - examples: - timeout-minutes: 30 - strategy: - matrix: - os: [ ubuntu-20.04, macos-14 ] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v4.1.0 - with: - distribution: ${{ env.JAVA_DISTRO }} - java-version: ${{ env.JAVA_VERSION }} - cache: maven - - name: Maven build run: | mvn -B -e "-Dmaven.test.skip=true" $MAVEN_HTTP_ARGS -DskipTests -Ppipeline install - cd examples + cd integrations/oci/oci mvn -B verify - mp-tck: - timeout-minutes: 60 - name: "MicroProfile TCKs" - strategy: - matrix: - os: [ ubuntu-20.04 ] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v4.1.0 - with: - distribution: ${{ env.JAVA_DISTRO }} - java-version: ${{ env.JAVA_VERSION }} - cache: maven - - name: Maven build - run: etc/scripts/mp-tck.sh - archetypes: - timeout-minutes: 45 - strategy: - matrix: - os: [ ubuntu-20.04 ] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v4.1.0 - with: - distribution: ${{ env.JAVA_DISTRO }} - java-version: ${{ env.JAVA_VERSION }} - cache: maven - - name: Test archetypes - run: etc/scripts/test-archetypes.sh - packaging: - timeout-minutes: 60 - strategy: - matrix: - os: [ ubuntu-20.04, macos-14] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - uses: graalvm/setup-graalvm@v1 - with: - java-version: 21 - distribution: graalvm-community - github-token: ${{ secrets.GITHUB_TOKEN }} - native-image-job-reports: true - cache: maven - - name: Build Helidon - run: etc/scripts/github-compile.sh - - name: JAR packaging - run: etc/scripts/test-packaging-jar.sh - - name: JLink packaging - run: etc/scripts/test-packaging-jlink.sh - - name: Native-Image packaging - run: etc/scripts/test-packaging-native.sh - diff --git a/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationIT.java b/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationIT.java index 8b0c528ced3..e4b421a401f 100644 --- a/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationIT.java +++ b/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciIntegrationIT.java @@ -44,7 +44,6 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assumptions.assumeTrue; public class OciIntegrationIT { @@ -180,8 +179,6 @@ private static void createFiles() throws IOException { } private static TestConfiguration testConfiguration() { - assumeTrue(System.getenv(ENV_USER) != null, "Missing environment variable '" + ENV_USER + "'"); - String user = System.getenv(ENV_USER); String fingerprint = System.getenv(ENV_FINGERPRINT); String tenancy = System.getenv(ENV_TENANCY);