diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 565307fd9b7..85d47032d56 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -115,3 +115,47 @@ jobs:
with:
name: io-helidon-artifacts-${{ needs.create-tag.outputs.version }}
path: staging
+ resolve-all:
+ needs: [ create-tag, deploy ]
+ timeout-minutes: 30
+ runs-on: ubuntu-20.04
+ name: resolve-all
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: '0'
+ ref: ${{ needs.create-tag.outputs.tag }}
+ - uses: ./.github/actions/common
+ with:
+ run: |
+ mvn ${MAVEN_ARGS} -N \
+ -Possrh-staging \
+ -Dartifact=io.helidon:helidon-all:${{ needs.create-tag.outputs.version }}:pom \
+ dependency:get
+ smoketest:
+ needs: [ create-tag, deploy ]
+ timeout-minutes: 30
+ strategy:
+ matrix:
+ archetype:
+ - bare-se
+ - bare-mp
+ - quickstart-se
+ - quickstart-mp
+ - database-se
+ - database-mp
+ runs-on: ubuntu-20.04
+ name: smoketest/${{ matrix.archetype }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: '0'
+ ref: ${{ needs.create-tag.outputs.tag }}
+ - uses: ./.github/actions/common
+ with:
+ run: |
+ ./etc/scripts/smoketest.sh \
+ --clean \
+ --staged \
+ --version=${{ needs.create-tag.outputs.version }} \
+ --archetype=${{ matrix.archetype }}
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 a7ad18404ff..2336ba4426e 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
@@ -255,7 +255,7 @@ private void process(RoundContext roundContext, TypeInfo blueprint) {
roundContext.addGeneratedType(prototype,
classModel,
blueprint.typeName(),
- blueprint.originatingElement().orElse(blueprint.typeName()));
+ blueprint.originatingElementValue());
if (typeContext.typeInfo().supportsServiceRegistry() && typeContext.propertyData().hasProvider()) {
for (PrototypeProperty property : typeContext.propertyData().properties()) {
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 07f13ac5c29..3aab5620bdf 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
@@ -43,6 +43,8 @@ public interface Annotated {
*
* 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.
+ *
+ * This method does not return annotations on super types or interfaces!
*
* @return list of all meta annotations of this element
*/
@@ -85,11 +87,10 @@ default Optional findAnnotation(TypeName annotationType) {
* @see #findAnnotation(TypeName)
*/
default Annotation annotation(TypeName annotationType) {
- return findAnnotation(annotationType).orElseThrow(() -> new NoSuchElementException("Annotation " + annotationType + " "
- + "is not present. Guard "
- + "with hasAnnotation(), or "
- + "use findAnnotation() "
- + "instead"));
+ return findAnnotation(annotationType)
+ .orElseThrow(() -> new NoSuchElementException("Annotation " + annotationType + " is not present. "
+ + "Guard with hasAnnotation(), "
+ + "or use findAnnotation() instead"));
}
/**
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 3017fdd6526..b51fb659b6d 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
@@ -79,6 +79,15 @@ interface AnnotationBlueprint {
@Option.Singular
Map values();
+ /**
+ * A list of inherited annotations (from the whole hierarchy).
+ *
+ * @return list of all annotations declared on the annotation type, or inherited from them
+ */
+ @Option.Redundant
+ @Option.Singular
+ List metaAnnotations();
+
/**
* The value property.
*
@@ -653,4 +662,19 @@ default > Optional> enumValues(String property, Class<
return AnnotationSupport.asEnums(typeName(), values(), property, type);
}
+ /**
+ * Check if {@link io.helidon.common.types.Annotation#metaAnnotations()} contains an annotation of the provided type.
+ *
+ * Note: we ignore {@link java.lang.annotation.Target}, {@link java.lang.annotation.Inherited},
+ * {@link java.lang.annotation.Documented}, and {@link java.lang.annotation.Retention}.
+ *
+ * @param annotationType type of annotation
+ * @return {@code true} if the annotation is declared on this annotation, or is inherited from a declared annotation
+ */
+ default boolean hasMetaAnnotation(TypeName annotationType) {
+ return metaAnnotations()
+ .stream()
+ .map(Annotation::typeName)
+ .anyMatch(annotationType::equals);
+ }
}
diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/AnnotationSupport.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/AnnotationSupport.java
index 52501d9d95e..b21a8a1a917 100644
--- a/builder/tests/common-types/src/main/java/io/helidon/common/types/AnnotationSupport.java
+++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/AnnotationSupport.java
@@ -532,6 +532,14 @@ private static String asString(TypeName typeName, String property, Object value)
return str;
}
+ if (value instanceof TypeName tn) {
+ return tn.fqName();
+ }
+
+ if (value instanceof EnumValue ev) {
+ return ev.name();
+ }
+
if (value instanceof List>) {
throw new IllegalArgumentException(typeName.fqName() + " property " + property
+ " is a list, cannot be converted to String");
@@ -619,22 +627,30 @@ private static Class> asClass(TypeName typeName, String property, Object value
if (value instanceof Class> theClass) {
return theClass;
}
- if (value instanceof String str) {
- try {
- return Class.forName(str);
- } catch (ClassNotFoundException e) {
+
+ String className = switch (value) {
+ case TypeName tn -> tn.name();
+ case String str -> str;
+ default -> {
throw new IllegalArgumentException(typeName.fqName() + " property " + property
- + " of type String and value \"" + str + "\""
+ + " of type " + value.getClass().getName()
+ " cannot be converted to Class");
}
- }
+ };
- throw new IllegalArgumentException(typeName.fqName() + " property " + property
- + " of type " + value.getClass().getName()
- + " cannot be converted to Class");
+ try {
+ return Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException(typeName.fqName() + " property " + property
+ + " of type String and value \"" + className + "\""
+ + " cannot be converted to Class");
+ }
}
private static TypeName asTypeName(TypeName typeName, String property, Object value) {
+ if (value instanceof TypeName tn) {
+ return tn;
+ }
if (value instanceof Class> theClass) {
return TypeName.create(theClass);
}
diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/ElementSignature.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/ElementSignature.java
new file mode 100644
index 00000000000..a167313516d
--- /dev/null
+++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/ElementSignature.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.common.types;
+
+import java.util.List;
+
+/**
+ * Signature of a {@link io.helidon.common.types.TypedElementInfo}.
+ *
+ * The {@link io.helidon.common.types.TypedElementInfo#signature()} is intended to compare
+ * fields, methods, and constructors across type hierarchy - for example when looking for a method
+ * that we override.
+ *
+ * The following information is used for equals and hash-code:
+ *
+ * Field: field name
+ * Constructor: parameter types
+ * Method: method name, parameter types
+ * Parameter: this signature is not useful, as we cannot depend on parameter names
+ *
+ *
+ * The signature has well-defined {@code hashCode} and {@code equals} methods,
+ * so it can be safely used as a key in a {@link java.util.Map}.
+ *
+ * This interface is sealed, an instance can only be obtained
+ * from {@link io.helidon.common.types.TypedElementInfo#signature()}.
+ *
+ * @see #text()
+ */
+public sealed interface ElementSignature permits ElementSignatures.FieldSignature,
+ ElementSignatures.MethodSignature,
+ ElementSignatures.ParameterSignature,
+ ElementSignatures.NoSignature {
+ /**
+ * Type of the element. Resolves as follows:
+ *
+ * Field: type of the field
+ * Constructor: void
+ * Method: method return type
+ * Parameter: parameter type
+ *
+ *
+ * @return type of this element, never used for equals or hashCode
+ */
+ TypeName type();
+
+ /**
+ * Name of the element. For constructor, this always returns {@code },
+ * for parameters, this method may return the real parameter name or an index
+ * parameter name depending on the source of the information (during annotation processing,
+ * this would be the actual parameter name, when classpath scanning, this would be something like
+ * {@code param0}.
+ *
+ * @return name of this element
+ */
+ String name();
+
+ /**
+ * Types of parameters if this represents a method or a constructor,
+ * empty {@link java.util.List} otherwise.
+ *
+ * @return parameter types
+ */
+ List parameterTypes();
+
+ /**
+ * A text representation of this signature.
+ *
+ *
+ * Field: field name (such as {@code myNiceField}
+ * Constructor: comma separated parameter types (no generics) in parentheses (such as
+ * {@code (java.lang.String,java.util.List)})
+ * Method: method name, parameter types (no generics) in parentheses (such as
+ * {@code methodName(java.lang.String,java.util.List)}
+ * Parameter: parameter name (such as {@code myParameter} or {@code param0} - not very useful, as parameter names
+ * are not carried over to compiled code in Java
+ *
+ *
+ * @return text representation
+ */
+ String text();
+}
diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/ElementSignatures.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/ElementSignatures.java
new file mode 100644
index 00000000000..4f5be563f29
--- /dev/null
+++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/ElementSignatures.java
@@ -0,0 +1,268 @@
+/*
+ * 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.common.types;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+final class ElementSignatures {
+ private ElementSignatures() {
+ }
+
+ static ElementSignature createNone() {
+ return new NoSignature();
+ }
+
+ static ElementSignature createField(TypeName type,
+ String name) {
+ Objects.requireNonNull(type);
+ Objects.requireNonNull(name);
+ return new FieldSignature(type, name);
+ }
+
+ static ElementSignature createConstructor(List parameters) {
+ Objects.requireNonNull(parameters);
+ return new MethodSignature(TypeNames.PRIMITIVE_VOID,
+ "",
+ parameters);
+ }
+
+ static ElementSignature createMethod(TypeName returnType, String name, List parameters) {
+ Objects.requireNonNull(returnType);
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(parameters);
+ return new MethodSignature(returnType,
+ name,
+ parameters);
+ }
+
+ static ElementSignature createParameter(TypeName type, String name) {
+ Objects.requireNonNull(type);
+ Objects.requireNonNull(name);
+ return new ParameterSignature(type, name);
+ }
+
+ static final class FieldSignature implements ElementSignature {
+ private final TypeName type;
+ private final String name;
+
+ private FieldSignature(TypeName type, String name) {
+ this.type = type;
+ this.name = name;
+ }
+
+ @Override
+ public TypeName type() {
+ return type;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public List parameterTypes() {
+ return List.of();
+ }
+
+ @Override
+ public String text() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return type.resolvedName() + " " + name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof FieldSignature that)) {
+ return false;
+ }
+ return Objects.equals(name, that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(name);
+ }
+ }
+
+ static final class MethodSignature implements ElementSignature {
+ private final TypeName type;
+ private final String name;
+ private final List parameters;
+ private final String text;
+ private final boolean constructor;
+
+ private MethodSignature(TypeName type,
+ String name,
+ List parameters) {
+ this.type = type;
+ this.name = name;
+ this.parameters = parameters;
+ if (name.equals("")) {
+ this.constructor = true;
+ this.text = parameterTypesSection(parameters, ",", TypeName::fqName);
+ } else {
+ this.constructor = false;
+ this.text = name + parameterTypesSection(parameters, ",", TypeName::fqName);
+ }
+
+ }
+
+ @Override
+ public TypeName type() {
+ return type;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public List parameterTypes() {
+ return parameters;
+ }
+
+ @Override
+ public String text() {
+ return text;
+ }
+
+ @Override
+ public String toString() {
+ if (constructor) {
+ return text;
+ } else {
+ return type.resolvedName() + " " + name + parameterTypesSection(parameters,
+ ", ",
+ TypeName::resolvedName);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof MethodSignature that)) {
+ return false;
+ }
+ return Objects.equals(name, that.name) && Objects.equals(parameters, that.parameters);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, parameters);
+ }
+ }
+
+ static final class ParameterSignature implements ElementSignature {
+ private final TypeName type;
+ private final String name;
+
+ private ParameterSignature(TypeName type, String name) {
+ this.type = type;
+ this.name = name;
+ }
+
+ @Override
+ public TypeName type() {
+ return type;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public List parameterTypes() {
+ return List.of();
+ }
+
+ @Override
+ public String text() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return type.resolvedName() + " " + name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ParameterSignature that)) {
+ return false;
+ }
+ return name.equals(that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(name);
+ }
+ }
+
+ static final class NoSignature implements ElementSignature {
+ @Override
+ public TypeName type() {
+ return TypeNames.PRIMITIVE_VOID;
+ }
+
+ @Override
+ public String name() {
+ return "";
+ }
+
+ @Override
+ public String text() {
+ return "";
+ }
+
+ @Override
+ public String toString() {
+ return text();
+ }
+
+ @Override
+ public List parameterTypes() {
+ return List.of();
+ }
+ }
+
+ private static String parameterTypesSection(List parameters,
+ String delimiter,
+ Function typeMapper) {
+ return parameters.stream()
+ .map(typeMapper)
+ .collect(Collectors.joining(delimiter, "(", ")"));
+ }
+}
diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/EnumValue.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/EnumValue.java
index 34fa57b6990..3a405d354f2 100644
--- a/builder/tests/common-types/src/main/java/io/helidon/common/types/EnumValue.java
+++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/EnumValue.java
@@ -16,6 +16,8 @@
package io.helidon.common.types;
+import java.util.Objects;
+
/**
* When creating an {@link io.helidon.common.types.Annotation}, we may need to create an enum value
* without access to the enumeration.
@@ -31,17 +33,24 @@ public interface EnumValue {
* @return enum value
*/
static EnumValue create(TypeName enumType, String enumName) {
- return new EnumValue() {
- @Override
- public TypeName type() {
- return enumType;
- }
+ Objects.requireNonNull(enumType);
+ Objects.requireNonNull(enumName);
+ return new EnumValueImpl(enumType, enumName);
+ }
+
+ /**
+ * Create a new enum value.
+ *
+ * @param type enum type
+ * @param value enum value constant
+ * @return new enum value
+ * @param type of the enum
+ */
+ static > EnumValue create(Class type, T value) {
+ Objects.requireNonNull(type);
+ Objects.requireNonNull(value);
- @Override
- public String name() {
- return enumName;
- }
- };
+ return new EnumValueImpl(TypeName.create(type), value.name());
}
/**
diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/EnumValueImpl.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/EnumValueImpl.java
new file mode 100644
index 00000000000..f2205b2452e
--- /dev/null
+++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/EnumValueImpl.java
@@ -0,0 +1,60 @@
+/*
+ * 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.common.types;
+
+import java.util.Objects;
+
+final class EnumValueImpl implements EnumValue {
+ private final TypeName type;
+ private final String name;
+
+ EnumValueImpl(TypeName type, String name) {
+ this.type = type;
+ this.name = name;
+ }
+
+ @Override
+ public TypeName type() {
+ return type;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof EnumValue enumValue)) {
+ return false;
+ }
+ return Objects.equals(type, enumValue.type()) && Objects.equals(name, enumValue.name());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, name);
+ }
+
+ @Override
+ public String toString() {
+ return type.fqName() + "." + name;
+ }
+}
diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/Modifier.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/Modifier.java
index 87a1e75e4db..7d36ffc7ece 100644
--- a/builder/tests/common-types/src/main/java/io/helidon/common/types/Modifier.java
+++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/Modifier.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.
@@ -39,7 +39,23 @@ public enum Modifier {
/**
* The {@code final} modifier.
*/
- FINAL("final");
+ FINAL("final"),
+ /**
+ * The {@code transient} modifier.
+ */
+ TRANSIENT("transient"),
+ /**
+ * The {@code volatile} modifier.
+ */
+ VOLATILE("volatile"),
+ /**
+ * The {@code synchronized} modifier.
+ */
+ SYNCHRONIZED("synchronized"),
+ /**
+ * The {@code native} modifier.
+ */
+ NATIVE("native");
private final String modifierName;
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 4bb3da96300..704c34d139c 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
@@ -16,10 +16,13 @@
package io.helidon.common.types;
+import java.util.ArrayDeque;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Queue;
import java.util.Set;
import io.helidon.builder.api.Option;
@@ -228,6 +231,54 @@ default Optional metaAnnotation(TypeName annotation, TypeName metaAn
@Option.Redundant
Optional originatingElement();
+ /**
+ * The element used to create this instance, or {@link io.helidon.common.types.TypeInfo#typeName()} if none provided.
+ * The type of the object depends on the environment we are in - it may be an {@code TypeElement} in annotation processing,
+ * or a {@code ClassInfo} when using classpath scanning.
+ *
+ * @return originating element, or the type of this type info
+ */
+ default Object originatingElementValue() {
+ return originatingElement().orElseGet(this::typeName);
+ }
+
+ /**
+ * Checks if the current type implements, or extends the provided type.
+ * This method analyzes the whole dependency tree of the current type.
+ *
+ * @param typeName type of interface to check
+ * @return the super type info, or interface type info matching the provided type, with appropriate generic declarations
+ */
+ default Optional findInHierarchy(TypeName typeName) {
+ if (typeName.equals(typeName())) {
+ return Optional.of((TypeInfo) this);
+ }
+ // scan super types
+ Optional superClass = superTypeInfo();
+ if (superClass.isPresent() && !superClass.get().typeName().equals(TypeNames.OBJECT)) {
+ var superType = superClass.get();
+ var foundInSuper = superType.findInHierarchy(typeName);
+ if (foundInSuper.isPresent()) {
+ return foundInSuper;
+ }
+ }
+ // nope, let's try interfaces
+ Queue interfaces = new ArrayDeque<>(interfaceTypeInfo());
+ Set processed = new HashSet<>();
+
+ while (!interfaces.isEmpty()) {
+ TypeInfo type = interfaces.remove();
+ // make sure we process each type only once
+ if (processed.add(type.typeName())) {
+ if (typeName.equals(type.typeName())) {
+ return Optional.of(type);
+ }
+ interfaces.addAll(type.interfaceTypeInfo());
+ }
+ }
+ return Optional.empty();
+ }
+
/**
* Uses {@link io.helidon.common.types.TypeInfo#referencedModuleNames()} to determine if the module name is known for the
* given type.
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 62fe59da5a3..f539e5849af 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,8 +16,10 @@
package io.helidon.common.types;
+import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
@@ -73,10 +75,18 @@ 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.Documented}.
+ */
+ public static final TypeName DOCUMENTED = TypeName.create(Documented.class);
/**
* Type name for {@link java.lang.annotation.Inherited}.
*/
public static final TypeName INHERITED = TypeName.create(Inherited.class);
+ /**
+ * Type name for {@link java.lang.annotation.Target}.
+ */
+ public static final TypeName TARGET = TypeName.create(Target.class);
/*
Primitive types and their boxed counterparts
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 b52ba88a4bd..3de312eb13c 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
@@ -167,4 +167,25 @@ interface TypedElementInfoBlueprint extends Annotated {
*/
@Option.Redundant
Optional originatingElement();
+
+ /**
+ * The element used to create this instance, or {@link io.helidon.common.types.TypedElementInfo#signature()}
+ * if none provided.
+ * The type of the object depends on the environment we are in - it may be an {@code TypeElement} in annotation processing,
+ * or a {@code MethodInfo} (and such) when using classpath scanning.
+ *
+ * @return originating element, or the signature of this element
+ */
+ default Object originatingElementValue() {
+ return originatingElement().orElseGet(this::signature);
+ }
+
+ /**
+ * Signature of this element.
+ *
+ * @return signature of this element
+ * @see io.helidon.common.types.ElementSignature
+ */
+ @Option.Access("")
+ ElementSignature signature();
}
diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypedElementInfoSupport.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypedElementInfoSupport.java
index 9edf23abd11..0cba7edaff8 100644
--- a/builder/tests/common-types/src/main/java/io/helidon/common/types/TypedElementInfoSupport.java
+++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/TypedElementInfoSupport.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.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -60,10 +61,67 @@ static class BuilderDecorator implements Prototype.BuilderDecorator target) {
-/*
+ backwardCompatibility(target);
+ constructorName(target);
+ signature(target);
+ }
+
+ private void signature(TypedElementInfo.BuilderBase, ?> target) {
+ if (target.kind().isEmpty()) {
+ // this will fail when validating
+ target.signature(ElementSignatures.createNone());
+ return;
+ } else {
+ target.signature(signature(target, target.kind().get()));
+ }
+ }
+
+ private ElementSignature signature(TypedElementInfo.BuilderBase, ?> target, ElementKind elementKind) {
+ if (elementKind == ElementKind.CONSTRUCTOR) {
+ return ElementSignatures.createConstructor(toTypes(target.parameterArguments()));
+ }
+ // for everything else we need the type (it is required)
+ if (target.typeName().isEmpty() || target.elementName().isEmpty()) {
+ return ElementSignatures.createNone();
+ }
+
+ TypeName typeName = target.typeName().get();
+ String name = target.elementName().get();
+
+ if (elementKind == ElementKind.FIELD
+ || elementKind == ElementKind.RECORD_COMPONENT
+ || elementKind == ElementKind.ENUM_CONSTANT) {
+ return ElementSignatures.createField(typeName, name);
+ }
+ if (elementKind == ElementKind.METHOD) {
+ return ElementSignatures.createMethod(typeName, name, toTypes(target.parameterArguments()));
+ }
+ if (elementKind == ElementKind.PARAMETER) {
+ return ElementSignatures.createParameter(typeName, name);
+ }
+ return ElementSignatures.createNone();
+ }
+
+ private List toTypes(List typedElementInfos) {
+ return typedElementInfos.stream()
+ .map(TypedElementInfo::typeName)
+ .collect(Collectors.toUnmodifiableList());
+ }
+
+ private void constructorName(TypedElementInfo.BuilderBase, ?> target) {
+ Optional elementKind = target.kind();
+ if (elementKind.isPresent()) {
+ if (elementKind.get() == ElementKind.CONSTRUCTOR) {
+ target.elementName("");
+ }
+ }
+ }
+
+ @SuppressWarnings("removal")
+ private void backwardCompatibility(TypedElementInfo.BuilderBase, ?> target) {
+ /*
Backward compatibility for deprecated methods.
*/
if (target.kind().isEmpty() && target.elementTypeKind().isPresent()) {
@@ -103,14 +161,6 @@ public void decorate(TypedElementInfo.BuilderBase, ?> target) {
target.addModifier(typeModifier.modifierName());
}
target.addModifier(target.accessModifier().get().modifierName());
-
-
- Optional elementKind = target.kind();
- if (elementKind.isPresent()) {
- if (elementKind.get() == ElementKind.CONSTRUCTOR) {
- target.elementName("");
- }
- }
}
}
}
diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/package-info.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/package-info.java
new file mode 100644
index 00000000000..7851acd401b
--- /dev/null
+++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ * 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.
+ */
+
+/**
+ * Subset of Builder's SPI types that are useful for runtime. Used in the ConfigBean builder, etc., that require a minimal set of
+ * types present at runtime.
+ */
+package io.helidon.common.types;
diff --git a/codegen/apt/src/main/java/io/helidon/codegen/apt/AptAnnotationFactory.java b/codegen/apt/src/main/java/io/helidon/codegen/apt/AptAnnotationFactory.java
index 45d998f2bc6..5f080864e79 100644
--- a/codegen/apt/src/main/java/io/helidon/codegen/apt/AptAnnotationFactory.java
+++ b/codegen/apt/src/main/java/io/helidon/codegen/apt/AptAnnotationFactory.java
@@ -16,8 +16,11 @@
package io.helidon.codegen.apt;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
@@ -26,6 +29,7 @@
import io.helidon.common.types.Annotation;
import io.helidon.common.types.TypeName;
+import io.helidon.common.types.TypeNames;
/**
* Factory for annotations.
@@ -48,7 +52,43 @@ public static Annotation createAnnotation(AnnotationMirror am,
.orElseThrow(() -> new IllegalArgumentException("Cannot create annotation for non-existent type: "
+ am.getAnnotationType()));
- return Annotation.create(val, extractAnnotationValues(am, elements));
+ // ignore these annotations, unless one of them was explicitly requested
+ var set = new HashSet();
+ set.add(TypeNames.INHERITED);
+ set.add(TypeNames.TARGET);
+ set.add(TypeNames.RETENTION);
+ set.add(TypeNames.DOCUMENTED);
+ set.remove(val);
+
+ return createAnnotation(elements, am, set)
+ .orElseThrow();
+ }
+
+ private static Optional createAnnotation(Elements elements, AnnotationMirror am, Set processedTypes) {
+ TypeName val = AptTypeFactory.createTypeName(am.getAnnotationType())
+ .orElseThrow(() -> new IllegalArgumentException("Cannot create annotation for non-existent type: "
+ + am.getAnnotationType()));
+
+ if (processedTypes.contains(val)) {
+ return Optional.empty();
+ }
+
+ Annotation.Builder builder = Annotation.builder();
+
+ elements.getAllAnnotationMirrors(am.getAnnotationType().asElement())
+ .stream()
+ .map(it -> {
+ var newProcessed = new HashSet<>(processedTypes);
+ newProcessed.add(val);
+ return createAnnotation(elements, it, newProcessed);
+ })
+ .flatMap(Optional::stream)
+ .forEach(builder::addMetaAnnotation);
+
+ return Optional.of(builder
+ .typeName(val)
+ .values(extractAnnotationValues(am, elements))
+ .build());
}
/**
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 df4cc10db60..9efa87e8e72 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
@@ -40,6 +40,7 @@
import io.helidon.codegen.FilerTextResource;
import io.helidon.codegen.IndentType;
import io.helidon.codegen.classmodel.ClassModel;
+import io.helidon.common.types.TypeName;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -73,6 +74,23 @@ public Path writeSourceFile(ClassModel classModel, Object... originatingElements
}
}
+ @Override
+ public Path writeSourceFile(TypeName type, String content, Object... originatingElements) {
+ Element[] elements = toElements(originatingElements);
+
+ try {
+ JavaFileObject sourceFile = filer.createSourceFile(type.fqName(), elements);
+ try (Writer os = sourceFile.openWriter()) {
+ os.write(content);
+ }
+ return Path.of(sourceFile.toUri());
+ } catch (IOException e) {
+ throw new CodegenException("Failed to write source file for type: " + type,
+ e,
+ originatingElement(elements, type));
+ }
+ }
+
@Override
public Path writeResource(byte[] resource, String location, Object... originatingElements) {
Element[] elements = toElements(originatingElements);
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 d5055caae68..26dc36a78fa 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
@@ -106,7 +106,7 @@ static void addCreateElement(ContentBuilder> contentBuilder, TypedElementInfo
static void addCreateAnnotation(ContentBuilder> contentBuilder, Annotation annotation) {
Map values = annotation.values();
- if (values.isEmpty()) {
+ if (values.isEmpty() && annotation.metaAnnotations().isEmpty()) {
// Annotation.create(TypeName.create("my.type.AnnotationType"))
contentBuilder.addContent(ANNOTATION)
.addContent(".create(")
@@ -136,6 +136,16 @@ static void addCreateAnnotation(ContentBuilder> contentBuilder, Annotation ann
contentBuilder.addContentLine(")");
});
+ // .addMetaAnnotation(...)
+ annotation.metaAnnotations()
+ .forEach(it -> contentBuilder.addContent(".addMetaAnnotation(")
+ .increaseContentPadding()
+ .increaseContentPadding()
+ .addContentCreate(it)
+ .addContentLine(")")
+ .decreaseContentPadding()
+ .decreaseContentPadding());
+
// .build()
contentBuilder.addContentLine(".build()")
.decreaseContentPadding()
diff --git a/codegen/class-model/src/test/java/io/helidon/codegen/classmodel/AnnotationTest.java b/codegen/class-model/src/test/java/io/helidon/codegen/classmodel/AnnotationTest.java
index 85aea878fc1..0e95c163447 100644
--- a/codegen/class-model/src/test/java/io/helidon/codegen/classmodel/AnnotationTest.java
+++ b/codegen/class-model/src/test/java/io/helidon/codegen/classmodel/AnnotationTest.java
@@ -57,6 +57,37 @@ void testPrintEnumValue() {
private String name;"""));
}
+ @Test
+ void testMetaAnnotation() {
+ Field field = Field.builder()
+ .accessModifier(AccessModifier.PRIVATE)
+ .type(Annotation.class)
+ .name("annotation")
+ .addContentCreate(Annotation.builder()
+ .typeName(ANNOTATION_TYPE)
+ .putValue("value", "someValue")
+ .addMetaAnnotation(Annotation.builder()
+ .typeName(ANNOTATION_TYPE)
+ .putValue("value", "string")
+ .build())
+ .build())
+ .build();
+ String text = write(field);
+
+ String expected = """
+ private Annotation annotation = Annotation.builder()
+ .typeName(TypeName.create("org.junit.jupiter.api.Test"))
+ .putValue("value", "someValue")
+ .addMetaAnnotation(Annotation.builder()
+ .typeName(TypeName.create("org.junit.jupiter.api.Test"))
+ .putValue("value", "string")
+ .build()
+ )
+ .build();""";
+
+ assertThat(text, is(expected));
+ }
+
@Test
void testContentCreateEnumValue() {
TypeName enumType = TypeName.create(TestEnum.class);
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 d8381653f8f..abd03ec7392 100644
--- a/codegen/codegen/src/main/java/io/helidon/codegen/CodegenFiler.java
+++ b/codegen/codegen/src/main/java/io/helidon/codegen/CodegenFiler.java
@@ -37,11 +37,21 @@ public interface CodegenFiler {
*
* @param classModel class model to write out
* @param originatingElements elements that caused this type to be generated
- * (you can use {@link io.helidon.common.types.TypeInfo#originatingElement()} for example
+ * (you can use {@link io.helidon.common.types.TypeInfo#originatingElementValue()})
* @return written path, we expect to always run on local file system
*/
Path writeSourceFile(ClassModel classModel, Object... originatingElements);
+ /**
+ * Write a source file using string content.
+ *
+ * @param type type of the file to generate
+ * @param content source code to write
+ * @param originatingElements elements that caused this type to be generated
+ * @return written path, we expect to always run on local file system
+ */
+ Path writeSourceFile(TypeName type, String content, Object... originatingElements);
+
/**
* Write a resource file.
*
diff --git a/codegen/codegen/src/main/java/io/helidon/codegen/CodegenValidator.java b/codegen/codegen/src/main/java/io/helidon/codegen/CodegenValidator.java
index ff3d21ebb2d..97b61cc9da1 100644
--- a/codegen/codegen/src/main/java/io/helidon/codegen/CodegenValidator.java
+++ b/codegen/codegen/src/main/java/io/helidon/codegen/CodegenValidator.java
@@ -53,8 +53,7 @@ public static String validateUri(TypeName enclosingType,
+ property + "(): "
+ "\"" + value + "\" cannot be parsed. Invalid URI.",
e,
- element.originatingElement().orElseGet(() -> enclosingType.fqName() + "."
- + element.elementName()));
+ element.originatingElementValue());
}
}
@@ -84,8 +83,7 @@ public static String validateDuration(TypeName enclosingType,
+ " expression such as 'PT1S' (1 second), 'PT0.1S' (tenth of a second)."
+ " Please check javadoc of " + Duration.class.getName() + " class.",
e,
- element.originatingElement().orElseGet(() -> enclosingType.fqName() + "."
- + element.elementName()));
+ element.originatingElementValue());
}
}
}
diff --git a/codegen/scan/src/main/java/io/helidon/codegen/scan/ScanAnnotationFactory.java b/codegen/scan/src/main/java/io/helidon/codegen/scan/ScanAnnotationFactory.java
index 6861e4d6b2d..8d4c1738f24 100644
--- a/codegen/scan/src/main/java/io/helidon/codegen/scan/ScanAnnotationFactory.java
+++ b/codegen/scan/src/main/java/io/helidon/codegen/scan/ScanAnnotationFactory.java
@@ -18,19 +18,23 @@
import java.lang.reflect.Array;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import io.helidon.common.types.Annotation;
import io.helidon.common.types.EnumValue;
import io.helidon.common.types.TypeName;
+import io.helidon.common.types.TypeNames;
import io.github.classgraph.AnnotationClassRef;
import io.github.classgraph.AnnotationEnumValue;
import io.github.classgraph.AnnotationInfo;
import io.github.classgraph.AnnotationParameterValue;
import io.github.classgraph.AnnotationParameterValueList;
+import io.github.classgraph.ClassInfo;
/**
* Factory for annotations.
@@ -50,7 +54,44 @@ public static Annotation createAnnotation(ScanContext ctx,
AnnotationInfo am) {
TypeName typeName = ScanTypeFactory.create(am.getClassInfo());
- return Annotation.create(typeName, extractAnnotationValues(ctx, am));
+ // ignore these annotations, unless one of them was explicitly requested
+ var set = new HashSet();
+ set.add(TypeNames.INHERITED);
+ set.add(TypeNames.TARGET);
+ set.add(TypeNames.RETENTION);
+ set.add(TypeNames.DOCUMENTED);
+ set.remove(typeName);
+
+ return createAnnotation(ctx, am, set)
+ .orElseThrow();
+ }
+
+ private static Optional createAnnotation(ScanContext ctx, AnnotationInfo am, HashSet processedTypes) {
+ ClassInfo classInfo = am.getClassInfo();
+ if (classInfo == null) {
+ // cannot analyze this annotation
+ return Optional.empty();
+ }
+ TypeName typeName = ScanTypeFactory.create(classInfo);
+
+ if (processedTypes.contains(typeName)) {
+ return Optional.empty();
+ }
+ var builder = Annotation.builder();
+
+ classInfo.getAnnotationInfo()
+ .stream()
+ .map(it -> {
+ var newProcessed = new HashSet<>(processedTypes);
+ newProcessed.add(typeName);
+ return createAnnotation(ctx, it, newProcessed);
+ })
+ .flatMap(Optional::stream)
+ .forEach(builder::addMetaAnnotation);
+
+ return Optional.of(builder.typeName(typeName)
+ .values(extractAnnotationValues(ctx, am))
+ .build());
}
/**
diff --git a/common/features/features/src/main/java/io/helidon/common/features/FeatureCatalog.java b/common/features/features/src/main/java/io/helidon/common/features/FeatureCatalog.java
index 6fd7f937c01..35db736c3a4 100644
--- a/common/features/features/src/main/java/io/helidon/common/features/FeatureCatalog.java
+++ b/common/features/features/src/main/java/io/helidon/common/features/FeatureCatalog.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2022 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.
@@ -52,7 +52,8 @@ static List features(ClassLoader classLoader) {
}
String module = props.getProperty("m");
if (module == null) {
- LOGGER.log(Level.WARNING, "Got module descriptor with no module name. Available properties: " + props);
+ LOGGER.log(Level.WARNING, "Got module descriptor with no module name. Available properties: " + props
+ + " at " + url);
continue;
}
FeatureDescriptor.Builder builder = FeatureDescriptor.builder();
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 292368d72de..3aab5620bdf 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
@@ -43,6 +43,8 @@ public interface Annotated {
*
* 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.
+ *
+ * This method does not return annotations on super types or interfaces!
*
* @return list of all meta annotations of this element
*/
diff --git a/common/types/src/main/java/io/helidon/common/types/Annotation.java b/common/types/src/main/java/io/helidon/common/types/Annotation.java
index f08439985d6..bd27d407599 100644
--- a/common/types/src/main/java/io/helidon/common/types/Annotation.java
+++ b/common/types/src/main/java/io/helidon/common/types/Annotation.java
@@ -17,8 +17,10 @@
package io.helidon.common.types;
import java.lang.reflect.Type;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -153,7 +155,9 @@ static Annotation create(TypeName annoTypeName, Map values) {
*/
abstract class BuilderBase, PROTOTYPE extends Annotation> implements Prototype.Builder {
+ private final List metaAnnotations = new ArrayList<>();
private final Map values = new LinkedHashMap<>();
+ private boolean isMetaAnnotationsMutated;
private TypeName typeName;
/**
@@ -171,6 +175,10 @@ protected BuilderBase() {
public BUILDER from(Annotation prototype) {
typeName(prototype.typeName());
addValues(prototype.values());
+ if (!isMetaAnnotationsMutated) {
+ metaAnnotations.clear();
+ }
+ addMetaAnnotations(prototype.metaAnnotations());
return self();
}
@@ -182,7 +190,15 @@ public BUILDER from(Annotation prototype) {
*/
public BUILDER from(Annotation.BuilderBase, ?> builder) {
builder.typeName().ifPresent(this::typeName);
- addValues(builder.values());
+ addValues(builder.values);
+ if (isMetaAnnotationsMutated) {
+ if (builder.isMetaAnnotationsMutated) {
+ addMetaAnnotations(builder.metaAnnotations);
+ }
+ } else {
+ metaAnnotations.clear();
+ addMetaAnnotations(builder.metaAnnotations);
+ }
return self();
}
@@ -293,6 +309,64 @@ public BUILDER putValue(String key, Object value) {
return self();
}
+ /**
+ * A list of inherited annotations (from the whole hierarchy).
+ *
+ * @param metaAnnotations list of all annotations declared on the annotation type, or inherited from them
+ * @return updated builder instance
+ * @see #metaAnnotations()
+ */
+ public BUILDER metaAnnotations(List extends Annotation> metaAnnotations) {
+ Objects.requireNonNull(metaAnnotations);
+ isMetaAnnotationsMutated = true;
+ this.metaAnnotations.clear();
+ this.metaAnnotations.addAll(metaAnnotations);
+ return self();
+ }
+
+ /**
+ * A list of inherited annotations (from the whole hierarchy).
+ *
+ * @param metaAnnotations list of all annotations declared on the annotation type, or inherited from them
+ * @return updated builder instance
+ * @see #metaAnnotations()
+ */
+ public BUILDER addMetaAnnotations(List extends Annotation> metaAnnotations) {
+ Objects.requireNonNull(metaAnnotations);
+ isMetaAnnotationsMutated = true;
+ this.metaAnnotations.addAll(metaAnnotations);
+ return self();
+ }
+
+ /**
+ * A list of inherited annotations (from the whole hierarchy).
+ *
+ * @param metaAnnotation list of all annotations declared on the annotation type, or inherited from them
+ * @return updated builder instance
+ * @see #metaAnnotations()
+ */
+ public BUILDER addMetaAnnotation(Annotation metaAnnotation) {
+ Objects.requireNonNull(metaAnnotation);
+ this.metaAnnotations.add(metaAnnotation);
+ isMetaAnnotationsMutated = true;
+ return self();
+ }
+
+ /**
+ * A list of inherited annotations (from the whole hierarchy).
+ *
+ * @param consumer list of all annotations declared on the annotation type, or inherited from them
+ * @return updated builder instance
+ * @see #metaAnnotations()
+ */
+ public BUILDER addMetaAnnotation(Consumer consumer) {
+ Objects.requireNonNull(consumer);
+ var builder = Annotation.builder();
+ consumer.accept(builder);
+ this.metaAnnotations.add(builder.build());
+ return self();
+ }
+
/**
* The type name, e.g., {@link java.util.Objects} -> "java.util.Objects".
*
@@ -311,6 +385,15 @@ public Map values() {
return values;
}
+ /**
+ * A list of inherited annotations (from the whole hierarchy).
+ *
+ * @return the meta annotations
+ */
+ public List metaAnnotations() {
+ return metaAnnotations;
+ }
+
@Override
public String toString() {
return "AnnotationBuilder{"
@@ -341,6 +424,7 @@ protected void validatePrototype() {
*/
protected static class AnnotationImpl implements Annotation {
+ private final List metaAnnotations;
private final Map values;
private final TypeName typeName;
@@ -352,6 +436,7 @@ protected static class AnnotationImpl implements Annotation {
protected AnnotationImpl(Annotation.BuilderBase, ?> builder) {
this.typeName = builder.typeName().get();
this.values = Collections.unmodifiableMap(new LinkedHashMap<>(builder.values()));
+ this.metaAnnotations = List.copyOf(builder.metaAnnotations());
}
@Override
@@ -369,6 +454,11 @@ public Map values() {
return values;
}
+ @Override
+ public List metaAnnotations() {
+ return metaAnnotations;
+ }
+
@Override
public String toString() {
return "Annotation{"
@@ -386,7 +476,7 @@ public boolean equals(Object o) {
return false;
}
return Objects.equals(typeName, other.typeName())
- && Objects.equals(values, other.values());
+ && Objects.equals(values, other.values());
}
@Override
diff --git a/common/types/src/main/java/io/helidon/common/types/AnnotationBlueprint.java b/common/types/src/main/java/io/helidon/common/types/AnnotationBlueprint.java
index 13e2a99d5f6..b51fb659b6d 100644
--- a/common/types/src/main/java/io/helidon/common/types/AnnotationBlueprint.java
+++ b/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.
@@ -79,6 +79,15 @@ interface AnnotationBlueprint {
@Option.Singular
Map values();
+ /**
+ * A list of inherited annotations (from the whole hierarchy).
+ *
+ * @return list of all annotations declared on the annotation type, or inherited from them
+ */
+ @Option.Redundant
+ @Option.Singular
+ List metaAnnotations();
+
/**
* The value property.
*
@@ -653,4 +662,19 @@ default > Optional> enumValues(String property, Class<
return AnnotationSupport.asEnums(typeName(), values(), property, type);
}
+ /**
+ * Check if {@link io.helidon.common.types.Annotation#metaAnnotations()} contains an annotation of the provided type.
+ *
+ * Note: we ignore {@link java.lang.annotation.Target}, {@link java.lang.annotation.Inherited},
+ * {@link java.lang.annotation.Documented}, and {@link java.lang.annotation.Retention}.
+ *
+ * @param annotationType type of annotation
+ * @return {@code true} if the annotation is declared on this annotation, or is inherited from a declared annotation
+ */
+ default boolean hasMetaAnnotation(TypeName annotationType) {
+ return metaAnnotations()
+ .stream()
+ .map(Annotation::typeName)
+ .anyMatch(annotationType::equals);
+ }
}
diff --git a/common/types/src/main/java/io/helidon/common/types/AnnotationSupport.java b/common/types/src/main/java/io/helidon/common/types/AnnotationSupport.java
index 242940fb555..b21a8a1a917 100644
--- a/common/types/src/main/java/io/helidon/common/types/AnnotationSupport.java
+++ b/common/types/src/main/java/io/helidon/common/types/AnnotationSupport.java
@@ -628,18 +628,15 @@ private static Class> asClass(TypeName typeName, String property, Object value
return theClass;
}
- String className;
-
- if (value instanceof TypeName tn) {
- className = tn.fqName();
- } else if (value instanceof String str) {
- className = str;
- } else {
-
- throw new IllegalArgumentException(typeName.fqName() + " property " + property
- + " of type " + value.getClass().getName()
- + " cannot be converted to Class");
- }
+ String className = switch (value) {
+ case TypeName tn -> tn.name();
+ case String str -> str;
+ default -> {
+ throw new IllegalArgumentException(typeName.fqName() + " property " + property
+ + " of type " + value.getClass().getName()
+ + " cannot be converted to Class");
+ }
+ };
try {
return Class.forName(className);
diff --git a/common/types/src/main/java/io/helidon/common/types/ElementSignature.java b/common/types/src/main/java/io/helidon/common/types/ElementSignature.java
new file mode 100644
index 00000000000..a167313516d
--- /dev/null
+++ b/common/types/src/main/java/io/helidon/common/types/ElementSignature.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.common.types;
+
+import java.util.List;
+
+/**
+ * Signature of a {@link io.helidon.common.types.TypedElementInfo}.
+ *
+ * The {@link io.helidon.common.types.TypedElementInfo#signature()} is intended to compare
+ * fields, methods, and constructors across type hierarchy - for example when looking for a method
+ * that we override.
+ *
+ * The following information is used for equals and hash-code:
+ *
+ * Field: field name
+ * Constructor: parameter types
+ * Method: method name, parameter types
+ * Parameter: this signature is not useful, as we cannot depend on parameter names
+ *
+ *
+ * The signature has well-defined {@code hashCode} and {@code equals} methods,
+ * so it can be safely used as a key in a {@link java.util.Map}.
+ *
+ * This interface is sealed, an instance can only be obtained
+ * from {@link io.helidon.common.types.TypedElementInfo#signature()}.
+ *
+ * @see #text()
+ */
+public sealed interface ElementSignature permits ElementSignatures.FieldSignature,
+ ElementSignatures.MethodSignature,
+ ElementSignatures.ParameterSignature,
+ ElementSignatures.NoSignature {
+ /**
+ * Type of the element. Resolves as follows:
+ *
+ * Field: type of the field
+ * Constructor: void
+ * Method: method return type
+ * Parameter: parameter type
+ *
+ *
+ * @return type of this element, never used for equals or hashCode
+ */
+ TypeName type();
+
+ /**
+ * Name of the element. For constructor, this always returns {@code },
+ * for parameters, this method may return the real parameter name or an index
+ * parameter name depending on the source of the information (during annotation processing,
+ * this would be the actual parameter name, when classpath scanning, this would be something like
+ * {@code param0}.
+ *
+ * @return name of this element
+ */
+ String name();
+
+ /**
+ * Types of parameters if this represents a method or a constructor,
+ * empty {@link java.util.List} otherwise.
+ *
+ * @return parameter types
+ */
+ List parameterTypes();
+
+ /**
+ * A text representation of this signature.
+ *
+ *
+ * Field: field name (such as {@code myNiceField}
+ * Constructor: comma separated parameter types (no generics) in parentheses (such as
+ * {@code (java.lang.String,java.util.List)})
+ * Method: method name, parameter types (no generics) in parentheses (such as
+ * {@code methodName(java.lang.String,java.util.List)}
+ * Parameter: parameter name (such as {@code myParameter} or {@code param0} - not very useful, as parameter names
+ * are not carried over to compiled code in Java
+ *
+ *
+ * @return text representation
+ */
+ String text();
+}
diff --git a/common/types/src/main/java/io/helidon/common/types/ElementSignatures.java b/common/types/src/main/java/io/helidon/common/types/ElementSignatures.java
new file mode 100644
index 00000000000..4f5be563f29
--- /dev/null
+++ b/common/types/src/main/java/io/helidon/common/types/ElementSignatures.java
@@ -0,0 +1,268 @@
+/*
+ * 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.common.types;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+final class ElementSignatures {
+ private ElementSignatures() {
+ }
+
+ static ElementSignature createNone() {
+ return new NoSignature();
+ }
+
+ static ElementSignature createField(TypeName type,
+ String name) {
+ Objects.requireNonNull(type);
+ Objects.requireNonNull(name);
+ return new FieldSignature(type, name);
+ }
+
+ static ElementSignature createConstructor(List parameters) {
+ Objects.requireNonNull(parameters);
+ return new MethodSignature(TypeNames.PRIMITIVE_VOID,
+ "",
+ parameters);
+ }
+
+ static ElementSignature createMethod(TypeName returnType, String name, List parameters) {
+ Objects.requireNonNull(returnType);
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(parameters);
+ return new MethodSignature(returnType,
+ name,
+ parameters);
+ }
+
+ static ElementSignature createParameter(TypeName type, String name) {
+ Objects.requireNonNull(type);
+ Objects.requireNonNull(name);
+ return new ParameterSignature(type, name);
+ }
+
+ static final class FieldSignature implements ElementSignature {
+ private final TypeName type;
+ private final String name;
+
+ private FieldSignature(TypeName type, String name) {
+ this.type = type;
+ this.name = name;
+ }
+
+ @Override
+ public TypeName type() {
+ return type;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public List parameterTypes() {
+ return List.of();
+ }
+
+ @Override
+ public String text() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return type.resolvedName() + " " + name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof FieldSignature that)) {
+ return false;
+ }
+ return Objects.equals(name, that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(name);
+ }
+ }
+
+ static final class MethodSignature implements ElementSignature {
+ private final TypeName type;
+ private final String name;
+ private final List parameters;
+ private final String text;
+ private final boolean constructor;
+
+ private MethodSignature(TypeName type,
+ String name,
+ List parameters) {
+ this.type = type;
+ this.name = name;
+ this.parameters = parameters;
+ if (name.equals("")) {
+ this.constructor = true;
+ this.text = parameterTypesSection(parameters, ",", TypeName::fqName);
+ } else {
+ this.constructor = false;
+ this.text = name + parameterTypesSection(parameters, ",", TypeName::fqName);
+ }
+
+ }
+
+ @Override
+ public TypeName type() {
+ return type;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public List parameterTypes() {
+ return parameters;
+ }
+
+ @Override
+ public String text() {
+ return text;
+ }
+
+ @Override
+ public String toString() {
+ if (constructor) {
+ return text;
+ } else {
+ return type.resolvedName() + " " + name + parameterTypesSection(parameters,
+ ", ",
+ TypeName::resolvedName);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof MethodSignature that)) {
+ return false;
+ }
+ return Objects.equals(name, that.name) && Objects.equals(parameters, that.parameters);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, parameters);
+ }
+ }
+
+ static final class ParameterSignature implements ElementSignature {
+ private final TypeName type;
+ private final String name;
+
+ private ParameterSignature(TypeName type, String name) {
+ this.type = type;
+ this.name = name;
+ }
+
+ @Override
+ public TypeName type() {
+ return type;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public List parameterTypes() {
+ return List.of();
+ }
+
+ @Override
+ public String text() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return type.resolvedName() + " " + name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ParameterSignature that)) {
+ return false;
+ }
+ return name.equals(that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(name);
+ }
+ }
+
+ static final class NoSignature implements ElementSignature {
+ @Override
+ public TypeName type() {
+ return TypeNames.PRIMITIVE_VOID;
+ }
+
+ @Override
+ public String name() {
+ return "";
+ }
+
+ @Override
+ public String text() {
+ return "";
+ }
+
+ @Override
+ public String toString() {
+ return text();
+ }
+
+ @Override
+ public List parameterTypes() {
+ return List.of();
+ }
+ }
+
+ private static String parameterTypesSection(List parameters,
+ String delimiter,
+ Function typeMapper) {
+ return parameters.stream()
+ .map(typeMapper)
+ .collect(Collectors.joining(delimiter, "(", ")"));
+ }
+}
diff --git a/common/types/src/main/java/io/helidon/common/types/EnumValueImpl.java b/common/types/src/main/java/io/helidon/common/types/EnumValueImpl.java
index 76bd62f24dc..f2205b2452e 100644
--- a/common/types/src/main/java/io/helidon/common/types/EnumValueImpl.java
+++ b/common/types/src/main/java/io/helidon/common/types/EnumValueImpl.java
@@ -52,4 +52,9 @@ public boolean equals(Object o) {
public int hashCode() {
return Objects.hash(type, name);
}
+
+ @Override
+ public String toString() {
+ return type.fqName() + "." + name;
+ }
}
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 d47b9f04df1..cc027638df8 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
@@ -75,6 +75,11 @@ abstract class BuilderBase elementModifiers = new LinkedHashSet<>();
private final Set modifiers = new LinkedHashSet<>();
private AccessModifier accessModifier;
+ private boolean isAnnotationsMutated;
+ private boolean isElementInfoMutated;
+ private boolean isInheritedAnnotationsMutated;
+ private boolean isInterfaceTypeInfoMutated;
+ private boolean isOtherElementInfoMutated;
private ElementKind kind;
private Object originatingElement;
private String description;
@@ -90,7 +95,7 @@ protected BuilderBase() {
}
/**
- * Update this builder from an existing prototype instance.
+ * Update this builder from an existing prototype instance. This method disables automatic service discovery.
*
* @param prototype existing prototype to update this builder from
* @return updated builder instance
@@ -100,18 +105,33 @@ public BUILDER from(TypeInfo prototype) {
description(prototype.description());
typeKind(prototype.typeKind());
kind(prototype.kind());
+ if (!isElementInfoMutated) {
+ elementInfo.clear();
+ }
addElementInfo(prototype.elementInfo());
+ if (!isOtherElementInfoMutated) {
+ otherElementInfo.clear();
+ }
addOtherElementInfo(prototype.otherElementInfo());
addReferencedTypeNamesToAnnotations(prototype.referencedTypeNamesToAnnotations());
addReferencedModuleNames(prototype.referencedModuleNames());
superTypeInfo(prototype.superTypeInfo());
+ if (!isInterfaceTypeInfoMutated) {
+ interfaceTypeInfo.clear();
+ }
addInterfaceTypeInfo(prototype.interfaceTypeInfo());
addModifiers(prototype.modifiers());
addElementModifiers(prototype.elementModifiers());
accessModifier(prototype.accessModifier());
module(prototype.module());
originatingElement(prototype.originatingElement());
+ if (!isAnnotationsMutated) {
+ annotations.clear();
+ }
addAnnotations(prototype.annotations());
+ if (!isInheritedAnnotationsMutated) {
+ inheritedAnnotations.clear();
+ }
addInheritedAnnotations(prototype.inheritedAnnotations());
return self();
}
@@ -127,19 +147,54 @@ public BUILDER from(TypeInfo.BuilderBase, ?> builder) {
builder.description().ifPresent(this::description);
builder.typeKind().ifPresent(this::typeKind);
builder.kind().ifPresent(this::kind);
- addElementInfo(builder.elementInfo());
- addOtherElementInfo(builder.otherElementInfo());
- addReferencedTypeNamesToAnnotations(builder.referencedTypeNamesToAnnotations());
- addReferencedModuleNames(builder.referencedModuleNames());
+ if (isElementInfoMutated) {
+ if (builder.isElementInfoMutated) {
+ addElementInfo(builder.elementInfo);
+ }
+ } else {
+ elementInfo.clear();
+ addElementInfo(builder.elementInfo);
+ }
+ if (isOtherElementInfoMutated) {
+ if (builder.isOtherElementInfoMutated) {
+ addOtherElementInfo(builder.otherElementInfo);
+ }
+ } else {
+ otherElementInfo.clear();
+ addOtherElementInfo(builder.otherElementInfo);
+ }
+ addReferencedTypeNamesToAnnotations(builder.referencedTypeNamesToAnnotations);
+ addReferencedModuleNames(builder.referencedModuleNames);
builder.superTypeInfo().ifPresent(this::superTypeInfo);
- addInterfaceTypeInfo(builder.interfaceTypeInfo());
- addModifiers(builder.modifiers());
- addElementModifiers(builder.elementModifiers());
+ if (isInterfaceTypeInfoMutated) {
+ if (builder.isInterfaceTypeInfoMutated) {
+ addInterfaceTypeInfo(builder.interfaceTypeInfo);
+ }
+ } else {
+ interfaceTypeInfo.clear();
+ addInterfaceTypeInfo(builder.interfaceTypeInfo);
+ }
+ addModifiers(builder.modifiers);
+ addElementModifiers(builder.elementModifiers);
builder.accessModifier().ifPresent(this::accessModifier);
builder.module().ifPresent(this::module);
builder.originatingElement().ifPresent(this::originatingElement);
- addAnnotations(builder.annotations());
- addInheritedAnnotations(builder.inheritedAnnotations());
+ if (isAnnotationsMutated) {
+ if (builder.isAnnotationsMutated) {
+ addAnnotations(builder.annotations);
+ }
+ } else {
+ annotations.clear();
+ addAnnotations(builder.annotations);
+ }
+ if (isInheritedAnnotationsMutated) {
+ if (builder.isInheritedAnnotationsMutated) {
+ addInheritedAnnotations(builder.inheritedAnnotations);
+ }
+ } else {
+ inheritedAnnotations.clear();
+ addInheritedAnnotations(builder.inheritedAnnotations);
+ }
return self();
}
@@ -262,6 +317,7 @@ public BUILDER kind(ElementKind kind) {
*/
public BUILDER elementInfo(List extends TypedElementInfo> elementInfo) {
Objects.requireNonNull(elementInfo);
+ isElementInfoMutated = true;
this.elementInfo.clear();
this.elementInfo.addAll(elementInfo);
return self();
@@ -276,6 +332,7 @@ public BUILDER elementInfo(List extends TypedElementInfo> elementInfo) {
*/
public BUILDER addElementInfo(List extends TypedElementInfo> elementInfo) {
Objects.requireNonNull(elementInfo);
+ isElementInfoMutated = true;
this.elementInfo.addAll(elementInfo);
return self();
}
@@ -290,6 +347,7 @@ public BUILDER addElementInfo(List extends TypedElementInfo> elementInfo) {
public BUILDER addElementInfo(TypedElementInfo elementInfo) {
Objects.requireNonNull(elementInfo);
this.elementInfo.add(elementInfo);
+ isElementInfoMutated = true;
return self();
}
@@ -318,6 +376,7 @@ public BUILDER addElementInfo(Consumer consumer) {
*/
public BUILDER otherElementInfo(List extends TypedElementInfo> otherElementInfo) {
Objects.requireNonNull(otherElementInfo);
+ isOtherElementInfoMutated = true;
this.otherElementInfo.clear();
this.otherElementInfo.addAll(otherElementInfo);
return self();
@@ -333,6 +392,7 @@ public BUILDER otherElementInfo(List extends TypedElementInfo> otherElementInf
*/
public BUILDER addOtherElementInfo(List extends TypedElementInfo> otherElementInfo) {
Objects.requireNonNull(otherElementInfo);
+ isOtherElementInfoMutated = true;
this.otherElementInfo.addAll(otherElementInfo);
return self();
}
@@ -348,6 +408,7 @@ public BUILDER addOtherElementInfo(List extends TypedElementInfo> otherElement
public BUILDER addOtherElementInfo(TypedElementInfo otherElementInfo) {
Objects.requireNonNull(otherElementInfo);
this.otherElementInfo.add(otherElementInfo);
+ isOtherElementInfoMutated = true;
return self();
}
@@ -374,8 +435,8 @@ public BUILDER addOtherElementInfo(Consumer consumer)
* @return updated builder instance
* @see #referencedTypeNamesToAnnotations()
*/
- public BUILDER referencedTypeNamesToAnnotations(Map extends TypeName,
- List> referencedTypeNamesToAnnotations) {
+ public BUILDER referencedTypeNamesToAnnotations(
+ Map extends TypeName, List> referencedTypeNamesToAnnotations) {
Objects.requireNonNull(referencedTypeNamesToAnnotations);
this.referencedTypeNamesToAnnotations.clear();
this.referencedTypeNamesToAnnotations.putAll(referencedTypeNamesToAnnotations);
@@ -539,6 +600,7 @@ public BUILDER superTypeInfo(Consumer consumer) {
*/
public BUILDER interfaceTypeInfo(List extends TypeInfo> interfaceTypeInfo) {
Objects.requireNonNull(interfaceTypeInfo);
+ isInterfaceTypeInfoMutated = true;
this.interfaceTypeInfo.clear();
this.interfaceTypeInfo.addAll(interfaceTypeInfo);
return self();
@@ -553,6 +615,7 @@ public BUILDER interfaceTypeInfo(List extends TypeInfo> interfaceTypeInfo) {
*/
public BUILDER addInterfaceTypeInfo(List extends TypeInfo> interfaceTypeInfo) {
Objects.requireNonNull(interfaceTypeInfo);
+ isInterfaceTypeInfoMutated = true;
this.interfaceTypeInfo.addAll(interfaceTypeInfo);
return self();
}
@@ -567,6 +630,7 @@ public BUILDER addInterfaceTypeInfo(List extends TypeInfo> interfaceTypeInfo)
public BUILDER addInterfaceTypeInfo(TypeInfo interfaceTypeInfo) {
Objects.requireNonNull(interfaceTypeInfo);
this.interfaceTypeInfo.add(interfaceTypeInfo);
+ isInterfaceTypeInfoMutated = true;
return self();
}
@@ -744,6 +808,7 @@ public BUILDER originatingElement(Object originatingElement) {
*/
public BUILDER annotations(List extends Annotation> annotations) {
Objects.requireNonNull(annotations);
+ isAnnotationsMutated = true;
this.annotations.clear();
this.annotations.addAll(annotations);
return self();
@@ -760,6 +825,7 @@ public BUILDER annotations(List extends Annotation> annotations) {
*/
public BUILDER addAnnotations(List extends Annotation> annotations) {
Objects.requireNonNull(annotations);
+ isAnnotationsMutated = true;
this.annotations.addAll(annotations);
return self();
}
@@ -776,6 +842,7 @@ public BUILDER addAnnotations(List extends Annotation> annotations) {
public BUILDER addAnnotation(Annotation annotation) {
Objects.requireNonNull(annotation);
this.annotations.add(annotation);
+ isAnnotationsMutated = true;
return self();
}
@@ -802,6 +869,8 @@ public BUILDER addAnnotation(Consumer consumer) {
*
* 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.
+ *
+ * This method does not return annotations on super types or interfaces!
*
* @param inheritedAnnotations list of all meta annotations of this element
* @return updated builder instance
@@ -809,6 +878,7 @@ public BUILDER addAnnotation(Consumer consumer) {
*/
public BUILDER inheritedAnnotations(List extends Annotation> inheritedAnnotations) {
Objects.requireNonNull(inheritedAnnotations);
+ isInheritedAnnotationsMutated = true;
this.inheritedAnnotations.clear();
this.inheritedAnnotations.addAll(inheritedAnnotations);
return self();
@@ -820,6 +890,8 @@ public BUILDER inheritedAnnotations(List extends Annotation> inheritedAnnotati
*
* 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.
+ *
+ * This method does not return annotations on super types or interfaces!
*
* @param inheritedAnnotations list of all meta annotations of this element
* @return updated builder instance
@@ -827,6 +899,7 @@ public BUILDER inheritedAnnotations(List extends Annotation> inheritedAnnotati
*/
public BUILDER addInheritedAnnotations(List extends Annotation> inheritedAnnotations) {
Objects.requireNonNull(inheritedAnnotations);
+ isInheritedAnnotationsMutated = true;
this.inheritedAnnotations.addAll(inheritedAnnotations);
return self();
}
@@ -837,6 +910,8 @@ public BUILDER addInheritedAnnotations(List extends Annotation> inheritedAnnot
*
* 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.
+ *
+ * This method does not return annotations on super types or interfaces!
*
* @param inheritedAnnotation list of all meta annotations of this element
* @return updated builder instance
@@ -845,6 +920,7 @@ public BUILDER addInheritedAnnotations(List extends Annotation> inheritedAnnot
public BUILDER addInheritedAnnotation(Annotation inheritedAnnotation) {
Objects.requireNonNull(inheritedAnnotation);
this.inheritedAnnotations.add(inheritedAnnotation);
+ isInheritedAnnotationsMutated = true;
return self();
}
@@ -854,6 +930,8 @@ public BUILDER addInheritedAnnotation(Annotation inheritedAnnotation) {
*
* 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.
+ *
+ * This method does not return annotations on super types or interfaces!
*
* @param consumer list of all meta annotations of this element
* @return updated builder instance
@@ -1049,6 +1127,8 @@ public List annotations() {
*
* 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.
+ *
+ * This method does not return annotations on super types or interfaces!
*
* @return the inherited annotations
*/
diff --git a/common/types/src/main/java/io/helidon/common/types/TypeInfoBlueprint.java b/common/types/src/main/java/io/helidon/common/types/TypeInfoBlueprint.java
index 4bb3da96300..704c34d139c 100644
--- a/common/types/src/main/java/io/helidon/common/types/TypeInfoBlueprint.java
+++ b/common/types/src/main/java/io/helidon/common/types/TypeInfoBlueprint.java
@@ -16,10 +16,13 @@
package io.helidon.common.types;
+import java.util.ArrayDeque;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Queue;
import java.util.Set;
import io.helidon.builder.api.Option;
@@ -228,6 +231,54 @@ default Optional metaAnnotation(TypeName annotation, TypeName metaAn
@Option.Redundant
Optional originatingElement();
+ /**
+ * The element used to create this instance, or {@link io.helidon.common.types.TypeInfo#typeName()} if none provided.
+ * The type of the object depends on the environment we are in - it may be an {@code TypeElement} in annotation processing,
+ * or a {@code ClassInfo} when using classpath scanning.
+ *
+ * @return originating element, or the type of this type info
+ */
+ default Object originatingElementValue() {
+ return originatingElement().orElseGet(this::typeName);
+ }
+
+ /**
+ * Checks if the current type implements, or extends the provided type.
+ * This method analyzes the whole dependency tree of the current type.
+ *
+ * @param typeName type of interface to check
+ * @return the super type info, or interface type info matching the provided type, with appropriate generic declarations
+ */
+ default Optional findInHierarchy(TypeName typeName) {
+ if (typeName.equals(typeName())) {
+ return Optional.of((TypeInfo) this);
+ }
+ // scan super types
+ Optional superClass = superTypeInfo();
+ if (superClass.isPresent() && !superClass.get().typeName().equals(TypeNames.OBJECT)) {
+ var superType = superClass.get();
+ var foundInSuper = superType.findInHierarchy(typeName);
+ if (foundInSuper.isPresent()) {
+ return foundInSuper;
+ }
+ }
+ // nope, let's try interfaces
+ Queue interfaces = new ArrayDeque<>(interfaceTypeInfo());
+ Set processed = new HashSet<>();
+
+ while (!interfaces.isEmpty()) {
+ TypeInfo type = interfaces.remove();
+ // make sure we process each type only once
+ if (processed.add(type.typeName())) {
+ if (typeName.equals(type.typeName())) {
+ return Optional.of(type);
+ }
+ interfaces.addAll(type.interfaceTypeInfo());
+ }
+ }
+ return Optional.empty();
+ }
+
/**
* Uses {@link io.helidon.common.types.TypeInfo#referencedModuleNames()} to determine if the module name is known for the
* given type.
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 62fe59da5a3..f539e5849af 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,8 +16,10 @@
package io.helidon.common.types;
+import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
@@ -73,10 +75,18 @@ 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.Documented}.
+ */
+ public static final TypeName DOCUMENTED = TypeName.create(Documented.class);
/**
* Type name for {@link java.lang.annotation.Inherited}.
*/
public static final TypeName INHERITED = TypeName.create(Inherited.class);
+ /**
+ * Type name for {@link java.lang.annotation.Target}.
+ */
+ public static final TypeName TARGET = TypeName.create(Target.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 905b540268f..9560152bd7e 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
@@ -79,7 +79,13 @@ abstract class BuilderBase elementModifiers = new LinkedHashSet<>();
private final Set modifiers = new LinkedHashSet<>();
private AccessModifier accessModifier;
+ private boolean isAnnotationsMutated;
+ private boolean isComponentTypesMutated;
+ private boolean isElementTypeAnnotationsMutated;
+ private boolean isInheritedAnnotationsMutated;
+ private boolean isParameterArgumentsMutated;
private ElementKind kind;
+ private ElementSignature signature;
private Object originatingElement;
private String defaultValue;
private String description;
@@ -95,7 +101,7 @@ protected BuilderBase() {
}
/**
- * Update this builder from an existing prototype instance.
+ * Update this builder from an existing prototype instance. This method disables automatic service discovery.
*
* @param prototype existing prototype to update this builder from
* @return updated builder instance
@@ -107,16 +113,32 @@ public BUILDER from(TypedElementInfo prototype) {
elementTypeKind(prototype.elementTypeKind());
kind(prototype.kind());
defaultValue(prototype.defaultValue());
+ if (!isElementTypeAnnotationsMutated) {
+ elementTypeAnnotations.clear();
+ }
addElementTypeAnnotations(prototype.elementTypeAnnotations());
+ if (!isComponentTypesMutated) {
+ componentTypes.clear();
+ }
addComponentTypes(prototype.componentTypes());
addModifiers(prototype.modifiers());
addElementModifiers(prototype.elementModifiers());
accessModifier(prototype.accessModifier());
enclosingType(prototype.enclosingType());
+ if (!isParameterArgumentsMutated) {
+ parameterArguments.clear();
+ }
addParameterArguments(prototype.parameterArguments());
addThrowsChecked(prototype.throwsChecked());
originatingElement(prototype.originatingElement());
+ signature(prototype.signature());
+ if (!isAnnotationsMutated) {
+ annotations.clear();
+ }
addAnnotations(prototype.annotations());
+ if (!isInheritedAnnotationsMutated) {
+ inheritedAnnotations.clear();
+ }
addInheritedAnnotations(prototype.inheritedAnnotations());
return self();
}
@@ -134,17 +156,53 @@ public BUILDER from(TypedElementInfo.BuilderBase, ?> builder) {
builder.elementTypeKind().ifPresent(this::elementTypeKind);
builder.kind().ifPresent(this::kind);
builder.defaultValue().ifPresent(this::defaultValue);
- addElementTypeAnnotations(builder.elementTypeAnnotations());
- addComponentTypes(builder.componentTypes());
- addModifiers(builder.modifiers());
- addElementModifiers(builder.elementModifiers());
+ if (isElementTypeAnnotationsMutated) {
+ if (builder.isElementTypeAnnotationsMutated) {
+ addElementTypeAnnotations(builder.elementTypeAnnotations);
+ }
+ } else {
+ elementTypeAnnotations.clear();
+ addElementTypeAnnotations(builder.elementTypeAnnotations);
+ }
+ if (isComponentTypesMutated) {
+ if (builder.isComponentTypesMutated) {
+ addComponentTypes(builder.componentTypes);
+ }
+ } else {
+ componentTypes.clear();
+ addComponentTypes(builder.componentTypes);
+ }
+ addModifiers(builder.modifiers);
+ addElementModifiers(builder.elementModifiers);
builder.accessModifier().ifPresent(this::accessModifier);
builder.enclosingType().ifPresent(this::enclosingType);
- addParameterArguments(builder.parameterArguments());
- addThrowsChecked(builder.throwsChecked());
+ if (isParameterArgumentsMutated) {
+ if (builder.isParameterArgumentsMutated) {
+ addParameterArguments(builder.parameterArguments);
+ }
+ } else {
+ parameterArguments.clear();
+ addParameterArguments(builder.parameterArguments);
+ }
+ addThrowsChecked(builder.throwsChecked);
builder.originatingElement().ifPresent(this::originatingElement);
- addAnnotations(builder.annotations());
- addInheritedAnnotations(builder.inheritedAnnotations());
+ builder.signature().ifPresent(this::signature);
+ if (isAnnotationsMutated) {
+ if (builder.isAnnotationsMutated) {
+ addAnnotations(builder.annotations);
+ }
+ } else {
+ annotations.clear();
+ addAnnotations(builder.annotations);
+ }
+ if (isInheritedAnnotationsMutated) {
+ if (builder.isInheritedAnnotationsMutated) {
+ addInheritedAnnotations(builder.inheritedAnnotations);
+ }
+ } else {
+ inheritedAnnotations.clear();
+ addInheritedAnnotations(builder.inheritedAnnotations);
+ }
return self();
}
@@ -286,7 +344,7 @@ public BUILDER defaultValue(String 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()}.
*
* @param elementTypeAnnotations the list of annotations on this element's (return) type.
* @return updated builder instance
@@ -294,13 +352,14 @@ public BUILDER defaultValue(String defaultValue) {
*/
public BUILDER elementTypeAnnotations(List extends Annotation> elementTypeAnnotations) {
Objects.requireNonNull(elementTypeAnnotations);
+ isElementTypeAnnotationsMutated = true;
this.elementTypeAnnotations.clear();
this.elementTypeAnnotations.addAll(elementTypeAnnotations);
return self();
}
/**
- * 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()}.
*
* @param elementTypeAnnotations the list of annotations on this element's (return) type.
* @return updated builder instance
@@ -308,6 +367,7 @@ public BUILDER elementTypeAnnotations(List extends Annotation> elementTypeAnno
*/
public BUILDER addElementTypeAnnotations(List extends Annotation> elementTypeAnnotations) {
Objects.requireNonNull(elementTypeAnnotations);
+ isElementTypeAnnotationsMutated = true;
this.elementTypeAnnotations.addAll(elementTypeAnnotations);
return self();
}
@@ -321,6 +381,7 @@ public BUILDER addElementTypeAnnotations(List extends Annotation> elementTypeA
*/
public BUILDER componentTypes(List extends TypeName> componentTypes) {
Objects.requireNonNull(componentTypes);
+ isComponentTypesMutated = true;
this.componentTypes.clear();
this.componentTypes.addAll(componentTypes);
return self();
@@ -335,6 +396,7 @@ public BUILDER componentTypes(List extends TypeName> componentTypes) {
*/
public BUILDER addComponentTypes(List extends TypeName> componentTypes) {
Objects.requireNonNull(componentTypes);
+ isComponentTypesMutated = true;
this.componentTypes.addAll(componentTypes);
return self();
}
@@ -493,6 +555,7 @@ public BUILDER enclosingType(Consumer consumer) {
*/
public BUILDER parameterArguments(List extends TypedElementInfo> parameterArguments) {
Objects.requireNonNull(parameterArguments);
+ isParameterArgumentsMutated = true;
this.parameterArguments.clear();
this.parameterArguments.addAll(parameterArguments);
return self();
@@ -509,6 +572,7 @@ public BUILDER parameterArguments(List extends TypedElementInfo> parameterArgu
*/
public BUILDER addParameterArguments(List extends TypedElementInfo> parameterArguments) {
Objects.requireNonNull(parameterArguments);
+ isParameterArgumentsMutated = true;
this.parameterArguments.addAll(parameterArguments);
return self();
}
@@ -525,6 +589,7 @@ public BUILDER addParameterArguments(List extends TypedElementInfo> parameterA
public BUILDER addParameterArgument(TypedElementInfo parameterArgument) {
Objects.requireNonNull(parameterArgument);
this.parameterArguments.add(parameterArgument);
+ isParameterArgumentsMutated = true;
return self();
}
@@ -609,6 +674,7 @@ public BUILDER originatingElement(Object originatingElement) {
*/
public BUILDER annotations(List extends Annotation> annotations) {
Objects.requireNonNull(annotations);
+ isAnnotationsMutated = true;
this.annotations.clear();
this.annotations.addAll(annotations);
return self();
@@ -625,6 +691,7 @@ public BUILDER annotations(List extends Annotation> annotations) {
*/
public BUILDER addAnnotations(List extends Annotation> annotations) {
Objects.requireNonNull(annotations);
+ isAnnotationsMutated = true;
this.annotations.addAll(annotations);
return self();
}
@@ -641,6 +708,7 @@ public BUILDER addAnnotations(List extends Annotation> annotations) {
public BUILDER addAnnotation(Annotation annotation) {
Objects.requireNonNull(annotation);
this.annotations.add(annotation);
+ isAnnotationsMutated = true;
return self();
}
@@ -667,6 +735,8 @@ public BUILDER addAnnotation(Consumer consumer) {
*
* 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.
+ *
+ * This method does not return annotations on super types or interfaces!
*
* @param inheritedAnnotations list of all meta annotations of this element
* @return updated builder instance
@@ -674,6 +744,7 @@ public BUILDER addAnnotation(Consumer consumer) {
*/
public BUILDER inheritedAnnotations(List extends Annotation> inheritedAnnotations) {
Objects.requireNonNull(inheritedAnnotations);
+ isInheritedAnnotationsMutated = true;
this.inheritedAnnotations.clear();
this.inheritedAnnotations.addAll(inheritedAnnotations);
return self();
@@ -685,6 +756,8 @@ public BUILDER inheritedAnnotations(List extends Annotation> inheritedAnnotati
*
* 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.
+ *
+ * This method does not return annotations on super types or interfaces!
*
* @param inheritedAnnotations list of all meta annotations of this element
* @return updated builder instance
@@ -692,6 +765,7 @@ public BUILDER inheritedAnnotations(List extends Annotation> inheritedAnnotati
*/
public BUILDER addInheritedAnnotations(List extends Annotation> inheritedAnnotations) {
Objects.requireNonNull(inheritedAnnotations);
+ isInheritedAnnotationsMutated = true;
this.inheritedAnnotations.addAll(inheritedAnnotations);
return self();
}
@@ -702,6 +776,8 @@ public BUILDER addInheritedAnnotations(List extends Annotation> inheritedAnnot
*
* 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.
+ *
+ * This method does not return annotations on super types or interfaces!
*
* @param inheritedAnnotation list of all meta annotations of this element
* @return updated builder instance
@@ -710,6 +786,7 @@ public BUILDER addInheritedAnnotations(List extends Annotation> inheritedAnnot
public BUILDER addInheritedAnnotation(Annotation inheritedAnnotation) {
Objects.requireNonNull(inheritedAnnotation);
this.inheritedAnnotations.add(inheritedAnnotation);
+ isInheritedAnnotationsMutated = true;
return self();
}
@@ -719,6 +796,8 @@ public BUILDER addInheritedAnnotation(Annotation inheritedAnnotation) {
*
* 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.
+ *
+ * This method does not return annotations on super types or interfaces!
*
* @param consumer list of all meta annotations of this element
* @return updated builder instance
@@ -794,7 +873,7 @@ public 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 element type annotations
*/
@@ -888,6 +967,17 @@ public Optional originatingElement() {
return Optional.ofNullable(originatingElement);
}
+ /**
+ * Signature of this element.
+ *
+ * @return the signature
+ * @see io.helidon.common.types.ElementSignature
+ * @see #signature()
+ */
+ public Optional signature() {
+ return Optional.ofNullable(signature);
+ }
+
/**
* List of declared and known annotations for this element.
* Note that "known" implies that the annotation is visible, which depends
@@ -905,6 +995,8 @@ public List annotations() {
*
* 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.
+ *
+ * This method does not return annotations on super types or interfaces!
*
* @return the inherited annotations
*/
@@ -939,6 +1031,9 @@ protected void validatePrototype() {
if (accessModifier == null) {
collector.fatal(getClass(), "Property \"accessModifier\" must not be null, but not set");
}
+ if (signature == null) {
+ collector.fatal(getClass(), "Property \"signature\" must not be null, but not set");
+ }
collector.collect().checkValid();
}
@@ -999,6 +1094,20 @@ BUILDER originatingElement(Optional> originatingElement) {
return self();
}
+ /**
+ * Signature of this element.
+ *
+ * @param signature signature of this element
+ * @return updated builder instance
+ * @see io.helidon.common.types.ElementSignature
+ * @see #signature()
+ */
+ BUILDER signature(ElementSignature signature) {
+ Objects.requireNonNull(signature);
+ this.signature = signature;
+ return self();
+ }
+
/**
* Generated implementation of the prototype, can be extended by descendant prototype implementations.
*/
@@ -1006,6 +1115,7 @@ protected static class TypedElementInfoImpl implements TypedElementInfo {
private final AccessModifier accessModifier;
private final ElementKind kind;
+ private final ElementSignature signature;
private final List annotations;
private final List elementTypeAnnotations;
private final List inheritedAnnotations;
@@ -1043,6 +1153,7 @@ protected TypedElementInfoImpl(TypedElementInfo.BuilderBase, ?> builder) {
this.parameterArguments = List.copyOf(builder.parameterArguments());
this.throwsChecked = Collections.unmodifiableSet(new LinkedHashSet<>(builder.throwsChecked()));
this.originatingElement = builder.originatingElement();
+ this.signature = builder.signature().get();
this.annotations = List.copyOf(builder.annotations());
this.inheritedAnnotations = List.copyOf(builder.inheritedAnnotations());
}
@@ -1132,6 +1243,11 @@ public Optional originatingElement() {
return originatingElement;
}
+ @Override
+ public ElementSignature signature() {
+ return signature;
+ }
+
@Override
public List annotations() {
return annotations;
@@ -1156,6 +1272,7 @@ public boolean equals(Object o) {
&& Objects.equals(enclosingType, other.enclosingType())
&& Objects.equals(parameterArguments, other.parameterArguments())
&& Objects.equals(throwsChecked, other.throwsChecked())
+ && Objects.equals(signature, other.signature())
&& Objects.equals(annotations, other.annotations())
&& Objects.equals(inheritedAnnotations, other.inheritedAnnotations());
}
@@ -1168,6 +1285,7 @@ public int hashCode() {
enclosingType,
parameterArguments,
throwsChecked,
+ signature,
annotations,
inheritedAnnotations);
}
diff --git a/common/types/src/main/java/io/helidon/common/types/TypedElementInfoBlueprint.java b/common/types/src/main/java/io/helidon/common/types/TypedElementInfoBlueprint.java
index b52ba88a4bd..3de312eb13c 100644
--- a/common/types/src/main/java/io/helidon/common/types/TypedElementInfoBlueprint.java
+++ b/common/types/src/main/java/io/helidon/common/types/TypedElementInfoBlueprint.java
@@ -167,4 +167,25 @@ interface TypedElementInfoBlueprint extends Annotated {
*/
@Option.Redundant
Optional originatingElement();
+
+ /**
+ * The element used to create this instance, or {@link io.helidon.common.types.TypedElementInfo#signature()}
+ * if none provided.
+ * The type of the object depends on the environment we are in - it may be an {@code TypeElement} in annotation processing,
+ * or a {@code MethodInfo} (and such) when using classpath scanning.
+ *
+ * @return originating element, or the signature of this element
+ */
+ default Object originatingElementValue() {
+ return originatingElement().orElseGet(this::signature);
+ }
+
+ /**
+ * Signature of this element.
+ *
+ * @return signature of this element
+ * @see io.helidon.common.types.ElementSignature
+ */
+ @Option.Access("")
+ ElementSignature signature();
}
diff --git a/common/types/src/main/java/io/helidon/common/types/TypedElementInfoSupport.java b/common/types/src/main/java/io/helidon/common/types/TypedElementInfoSupport.java
index 9edf23abd11..0cba7edaff8 100644
--- a/common/types/src/main/java/io/helidon/common/types/TypedElementInfoSupport.java
+++ b/common/types/src/main/java/io/helidon/common/types/TypedElementInfoSupport.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.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -60,10 +61,67 @@ static class BuilderDecorator implements Prototype.BuilderDecorator target) {
-/*
+ backwardCompatibility(target);
+ constructorName(target);
+ signature(target);
+ }
+
+ private void signature(TypedElementInfo.BuilderBase, ?> target) {
+ if (target.kind().isEmpty()) {
+ // this will fail when validating
+ target.signature(ElementSignatures.createNone());
+ return;
+ } else {
+ target.signature(signature(target, target.kind().get()));
+ }
+ }
+
+ private ElementSignature signature(TypedElementInfo.BuilderBase, ?> target, ElementKind elementKind) {
+ if (elementKind == ElementKind.CONSTRUCTOR) {
+ return ElementSignatures.createConstructor(toTypes(target.parameterArguments()));
+ }
+ // for everything else we need the type (it is required)
+ if (target.typeName().isEmpty() || target.elementName().isEmpty()) {
+ return ElementSignatures.createNone();
+ }
+
+ TypeName typeName = target.typeName().get();
+ String name = target.elementName().get();
+
+ if (elementKind == ElementKind.FIELD
+ || elementKind == ElementKind.RECORD_COMPONENT
+ || elementKind == ElementKind.ENUM_CONSTANT) {
+ return ElementSignatures.createField(typeName, name);
+ }
+ if (elementKind == ElementKind.METHOD) {
+ return ElementSignatures.createMethod(typeName, name, toTypes(target.parameterArguments()));
+ }
+ if (elementKind == ElementKind.PARAMETER) {
+ return ElementSignatures.createParameter(typeName, name);
+ }
+ return ElementSignatures.createNone();
+ }
+
+ private List toTypes(List typedElementInfos) {
+ return typedElementInfos.stream()
+ .map(TypedElementInfo::typeName)
+ .collect(Collectors.toUnmodifiableList());
+ }
+
+ private void constructorName(TypedElementInfo.BuilderBase, ?> target) {
+ Optional elementKind = target.kind();
+ if (elementKind.isPresent()) {
+ if (elementKind.get() == ElementKind.CONSTRUCTOR) {
+ target.elementName("");
+ }
+ }
+ }
+
+ @SuppressWarnings("removal")
+ private void backwardCompatibility(TypedElementInfo.BuilderBase, ?> target) {
+ /*
Backward compatibility for deprecated methods.
*/
if (target.kind().isEmpty() && target.elementTypeKind().isPresent()) {
@@ -103,14 +161,6 @@ public void decorate(TypedElementInfo.BuilderBase, ?> target) {
target.addModifier(typeModifier.modifierName());
}
target.addModifier(target.accessModifier().get().modifierName());
-
-
- Optional elementKind = target.kind();
- if (elementKind.isPresent()) {
- if (elementKind.get() == ElementKind.CONSTRUCTOR) {
- target.elementName("");
- }
- }
}
}
}
diff --git a/common/types/src/test/java/io/helidon/common/types/SignatureTest.java b/common/types/src/test/java/io/helidon/common/types/SignatureTest.java
new file mode 100644
index 00000000000..2dce94997b9
--- /dev/null
+++ b/common/types/src/test/java/io/helidon/common/types/SignatureTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.common.types;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+
+class SignatureTest {
+
+ @Test
+ void testMethodSignature() {
+ TypedElementInfo m1 = TypedElementInfo.builder()
+ .kind(ElementKind.METHOD)
+ .elementName("method")
+ .typeName(TypeNames.STRING)
+ .build();
+
+ TypedElementInfo m2 = TypedElementInfo.builder()
+ .kind(ElementKind.METHOD)
+ .elementName("method")
+ .typeName(TypeNames.STRING)
+ .build();
+
+ TypedElementInfo m3 = TypedElementInfo.builder()
+ .kind(ElementKind.METHOD)
+ .elementName("method2")
+ .typeName(TypeNames.STRING)
+ .build();
+
+ TypedElementInfo m4 = TypedElementInfo.builder()
+ .kind(ElementKind.METHOD)
+ .elementName("method")
+ .typeName(TypeNames.STRING)
+ .parameterArguments(List.of(TypedElementInfo.builder()
+ .kind(ElementKind.PARAMETER)
+ .typeName(TypeNames.STRING)
+ .elementName("param1")
+ .buildPrototype()))
+ .build();
+
+ ElementSignature s1 = m1.signature();
+ ElementSignature s2 = m2.signature();
+ ElementSignature s3 = m3.signature();
+ ElementSignature s4 = m4.signature();
+
+ // this is specified in Javadoc and must not be changed
+ assertThat(s1.text(), is("method()"));
+ assertThat(s2.text(), is("method()"));
+ assertThat(s3.text(), is("method2()"));
+ assertThat(s4.text(), is("method(java.lang.String)"));
+
+ assertThat(s1, is(s2));
+ assertThat(s1, not(s3));
+ assertThat(s1, not(s4));
+
+ assertThat(s1.hashCode(), is(s2.hashCode()));
+
+ assertThat(s1.name(), is("method"));
+ assertThat(s1.type(), is(TypeNames.STRING));
+ assertThat(s1.parameterTypes(), is(List.of()));
+ }
+
+ @Test
+ void testConstructorSignature() {
+ TypedElementInfo m1 = TypedElementInfo.builder()
+ .kind(ElementKind.CONSTRUCTOR)
+ .typeName(TypeNames.STRING)
+ .build();
+
+ TypedElementInfo m2 = TypedElementInfo.builder()
+ .kind(ElementKind.CONSTRUCTOR)
+ .typeName(TypeNames.STRING)
+ .build();
+
+ TypedElementInfo m3 = TypedElementInfo.builder()
+ .kind(ElementKind.CONSTRUCTOR)
+ .typeName(TypeNames.STRING)
+ .parameterArguments(List.of(TypedElementInfo.builder()
+ .kind(ElementKind.PARAMETER)
+ .typeName(TypeNames.STRING)
+ .elementName("param1")
+ .buildPrototype()))
+ .build();
+
+ ElementSignature s1 = m1.signature();
+ ElementSignature s2 = m2.signature();
+ ElementSignature s3 = m3.signature();
+
+ // this is specified in Javadoc and must not be changed
+ assertThat(s1.text(), is("()"));
+ assertThat(s2.text(), is("()"));
+ assertThat(s3.text(), is("(java.lang.String)"));
+
+ assertThat(s1, is(s2));
+ assertThat(s1, not(s3));
+
+ assertThat(s1.hashCode(), is(s2.hashCode()));
+
+ assertThat(s1.name(), is(""));
+ assertThat(s1.type(), is(TypeNames.PRIMITIVE_VOID));
+ assertThat(s1.parameterTypes(), is(List.of()));
+ }
+
+ @Test
+ void testFieldSignature() {
+ TypedElementInfo m1 = TypedElementInfo.builder()
+ .kind(ElementKind.FIELD)
+ .elementName("field")
+ .typeName(TypeNames.STRING)
+ .build();
+
+ TypedElementInfo m2 = TypedElementInfo.builder()
+ .kind(ElementKind.FIELD)
+ .elementName("field")
+ .typeName(TypeNames.STRING)
+ .build();
+
+ TypedElementInfo m3 = TypedElementInfo.builder()
+ .kind(ElementKind.FIELD)
+ .elementName("field2")
+ .typeName(TypeNames.STRING)
+ .build();
+
+ ElementSignature s1 = m1.signature();
+ ElementSignature s2 = m2.signature();
+ ElementSignature s3 = m3.signature();
+
+ // this is specified in Javadoc and must not be changed
+ assertThat(s1.text(), is("field"));
+ assertThat(s2.text(), is("field"));
+ assertThat(s3.text(), is("field2"));
+
+ assertThat(s1, is(s2));
+ assertThat(s1, not(s3));
+
+ assertThat(s1.hashCode(), is(s2.hashCode()));
+
+ assertThat(s1.name(), is("field"));
+ assertThat(s1.type(), is(TypeNames.STRING));
+ assertThat(s1.parameterTypes(), is(List.of()));
+ }
+}
diff --git a/common/types/src/test/java/io/helidon/common/types/TypeInfoTest.java b/common/types/src/test/java/io/helidon/common/types/TypeInfoTest.java
new file mode 100644
index 00000000000..f00f182dc2b
--- /dev/null
+++ b/common/types/src/test/java/io/helidon/common/types/TypeInfoTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.common.types;
+
+import java.util.Optional;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+class TypeInfoTest {
+ @Test
+ void testFindInHierarchyInterfaces() {
+ TypeName ifaceA = TypeName.create("io.helidon.common.types.test.A");
+ TypeName ifaceB = TypeName.create("io.helidon.common.types.test.B");
+ TypeName ifaceC = TypeName.create("io.helidon.common.types.test.C");
+ TypeInfo aInfo = TypeInfo.builder()
+ .typeName(ifaceA)
+ .kind(ElementKind.INTERFACE)
+ .build();
+ TypeInfo bInfo = TypeInfo.builder()
+ .typeName(ifaceB)
+ .kind(ElementKind.INTERFACE)
+ .addInterfaceTypeInfo(aInfo)
+ .build();
+ TypeInfo cInfo = TypeInfo.builder()
+ .typeName(ifaceC)
+ .kind(ElementKind.INTERFACE)
+ .addInterfaceTypeInfo(bInfo)
+ .build();
+
+ Optional foundInfo = cInfo.findInHierarchy(ifaceA);
+ assertThat(foundInfo, not(Optional.empty()));
+ assertThat(foundInfo.get(), sameInstance(aInfo));
+
+ foundInfo = cInfo.findInHierarchy(ifaceB);
+ assertThat(foundInfo, not(Optional.empty()));
+ assertThat(foundInfo.get(), sameInstance(bInfo));
+
+ foundInfo = bInfo.findInHierarchy(ifaceA);
+ assertThat(foundInfo, not(Optional.empty()));
+ assertThat(foundInfo.get(), sameInstance(aInfo));
+
+ foundInfo = aInfo.findInHierarchy(ifaceB);
+ assertThat(foundInfo, is(Optional.empty()));
+ }
+
+ @Test
+ void testFindInHierarchyTypes() {
+ TypeName ifaceA = TypeName.create("io.helidon.common.types.test.A");
+ TypeName classB = TypeName.create("io.helidon.common.types.test.B");
+ TypeName classC = TypeName.create("io.helidon.common.types.test.C");
+ TypeInfo aInfo = TypeInfo.builder()
+ .typeName(ifaceA)
+ .kind(ElementKind.INTERFACE)
+ .build();
+ TypeInfo bInfo = TypeInfo.builder()
+ .typeName(classB)
+ .kind(ElementKind.CLASS)
+ .addInterfaceTypeInfo(aInfo)
+ .build();
+ TypeInfo cInfo = TypeInfo.builder()
+ .typeName(classC)
+ .kind(ElementKind.INTERFACE)
+ .superTypeInfo(bInfo)
+ .build();
+
+ Optional foundInfo = cInfo.findInHierarchy(ifaceA);
+ assertThat(foundInfo, not(Optional.empty()));
+ assertThat(foundInfo.get(), sameInstance(aInfo));
+
+ foundInfo = cInfo.findInHierarchy(classB);
+ assertThat(foundInfo, not(Optional.empty()));
+ assertThat(foundInfo.get(), sameInstance(bInfo));
+
+ foundInfo = bInfo.findInHierarchy(ifaceA);
+ assertThat(foundInfo, not(Optional.empty()));
+ assertThat(foundInfo.get(), sameInstance(aInfo));
+
+ foundInfo = aInfo.findInHierarchy(classB);
+ assertThat(foundInfo, is(Optional.empty()));
+ }
+
+}
diff --git a/config/config-mp/src/main/java/io/helidon/config/mp/MpConfigImpl.java b/config/config-mp/src/main/java/io/helidon/config/mp/MpConfigImpl.java
index 8299a1c1cc5..d35fdb91b16 100644
--- a/config/config-mp/src/main/java/io/helidon/config/mp/MpConfigImpl.java
+++ b/config/config-mp/src/main/java/io/helidon/config/mp/MpConfigImpl.java
@@ -30,6 +30,7 @@
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
@@ -452,7 +453,11 @@ private Optional> findImplicit(Class type) {
if (Enum.class.isAssignableFrom(type)) {
return Optional.of(value -> {
Class extends Enum> enumClass = (Class extends Enum>) type;
- return (T) Enum.valueOf(enumClass, value);
+ try {
+ return (T) Enum.valueOf(enumClass, value);
+ } catch (Exception e) {
+ return (T) Enum.valueOf(enumClass, value.toUpperCase(Locale.ROOT));
+ }
});
}
// any class that has a "public static T method()"
diff --git a/config/config-mp/src/main/java/io/helidon/config/mp/MpConfigProviderResolver.java b/config/config-mp/src/main/java/io/helidon/config/mp/MpConfigProviderResolver.java
index 9ecd670227d..4b6f57efdc1 100644
--- a/config/config-mp/src/main/java/io/helidon/config/mp/MpConfigProviderResolver.java
+++ b/config/config-mp/src/main/java/io/helidon/config/mp/MpConfigProviderResolver.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.
@@ -32,6 +32,7 @@
import java.util.stream.Stream;
import io.helidon.common.GenericType;
+import io.helidon.common.config.GlobalConfig;
import io.helidon.config.ConfigValue;
import io.helidon.config.MetaConfig;
import io.helidon.config.spi.ConfigMapper;
@@ -167,8 +168,8 @@ public static void buildTimeEnd() {
private ConfigDelegate doRegisterConfig(Config config, ClassLoader classLoader) {
ConfigDelegate currentConfig = CONFIGS.remove(classLoader);
- if (config instanceof ConfigDelegate) {
- config = ((ConfigDelegate) config).delegate();
+ if (config instanceof ConfigDelegate delegate) {
+ config = delegate.delegate();
}
if (null != currentConfig) {
@@ -178,6 +179,11 @@ private ConfigDelegate doRegisterConfig(Config config, ClassLoader classLoader)
ConfigDelegate newConfig = new ConfigDelegate(config);
CONFIGS.put(classLoader, newConfig);
+ if (classLoader == Thread.currentThread().getContextClassLoader()) {
+ // this should be the default class loader (we do not support classloader magic in Helidon)
+ GlobalConfig.config(() -> newConfig, true);
+ }
+
return newConfig;
}
diff --git a/config/config-mp/src/main/java/io/helidon/config/mp/SeConfig.java b/config/config-mp/src/main/java/io/helidon/config/mp/SeConfig.java
index 514830dab44..5ec1c8d3e43 100644
--- a/config/config-mp/src/main/java/io/helidon/config/mp/SeConfig.java
+++ b/config/config-mp/src/main/java/io/helidon/config/mp/SeConfig.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.
@@ -84,8 +84,8 @@ class SeConfig implements Config {
this.stringKey = prefix.child(key).toString();
this.stringPrefix = prefix.toString();
- if (delegate instanceof MpConfigImpl) {
- this.delegateImpl = (MpConfigImpl) delegate;
+ if (delegate instanceof MpConfigImpl mpConfig) {
+ this.delegateImpl = mpConfig;
} else {
this.delegateImpl = null;
}
diff --git a/config/config/src/main/java/io/helidon/config/BuilderImpl.java b/config/config/src/main/java/io/helidon/config/BuilderImpl.java
index 28e60577289..a1bd6f09681 100644
--- a/config/config/src/main/java/io/helidon/config/BuilderImpl.java
+++ b/config/config/src/main/java/io/helidon/config/BuilderImpl.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.
@@ -61,6 +61,7 @@ class BuilderImpl implements Config.Builder {
private MergingStrategy mergingStrategy = MergingStrategy.fallback();
private boolean hasSystemPropertiesSource;
private boolean hasEnvVarSource;
+ private boolean sourcesConfigured;
/*
* Config mapper providers
*/
@@ -123,6 +124,8 @@ public Config.Builder sources(List> sourceSuppl
sourceSuppliers.stream()
.map(Supplier::get)
.forEach(this::addSource);
+ // this was intentional, even if empty (such as from Config.just())
+ this.sourcesConfigured = true;
return this;
}
@@ -427,14 +430,14 @@ private ConfigSourcesRuntime buildConfigSources(ConfigContextImpl context) {
envVarAliasGeneratorEnabled = true;
}
- boolean nothingConfigured = sources.isEmpty();
+ boolean nothingConfigured = sources.isEmpty() && !sourcesConfigured;
if (nothingConfigured) {
// use meta configuration to load all sources
- MetaConfig.configSources(mediaType -> context.findParser(mediaType).isPresent(), context.supportedSuffixes())
- .stream()
+ MetaConfigFinder.findConfigSource(mediaType -> context.findParser(mediaType).isPresent(),
+ context.supportedSuffixes())
.map(context::sourceRuntimeBase)
- .forEach(targetSources::add);
+ .ifPresent(targetSources::add);
} else {
// add all configured or discovered sources
@@ -702,56 +705,6 @@ public String toString() {
}
}
- private static final class WeightedConfigSource implements Weighted {
- private final HelidonSourceWithPriority source;
- private final ConfigContext context;
-
- private WeightedConfigSource(HelidonSourceWithPriority source, ConfigContext context) {
- this.source = source;
- this.context = context;
- }
-
- @Override
- public double weight() {
- return source.weight(context);
- }
-
- private ConfigSourceRuntimeImpl runtime(ConfigContextImpl context) {
- return context.sourceRuntimeBase(source.unwrap());
- }
- }
-
- private static final class HelidonSourceWithPriority {
- private final ConfigSource configSource;
- private final Double explicitWeight;
-
- private HelidonSourceWithPriority(ConfigSource configSource, Double explicitWeight) {
- this.configSource = configSource;
- this.explicitWeight = explicitWeight;
- }
-
- ConfigSource unwrap() {
- return configSource;
- }
-
- double weight(ConfigContext context) {
- // first - explicit priority. If configured by user, return it
- if (null != explicitWeight) {
- return explicitWeight;
- }
-
- // ordinal from data
- return context.sourceRuntime(configSource)
- .node("config_priority")
- .flatMap(node -> node.value()
- .map(Double::parseDouble))
- .orElseGet(() -> {
- // the config source does not have an ordinal configured, I need to get it from other places
- return Weights.find(configSource, Weighted.DEFAULT_WEIGHT);
- });
- }
- }
-
private static class LoadedFilterProvider implements Function {
private final ConfigFilter filter;
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 f34c4a88d38..bf59b50f5c3 100644
--- a/config/config/src/main/java/io/helidon/config/Config.java
+++ b/config/config/src/main/java/io/helidon/config/Config.java
@@ -1635,8 +1635,14 @@ default Builder sources(Supplier extends ConfigSource> configSource,
* @see #config(Config)
*/
default Builder metaConfig() {
- MetaConfig.metaConfig()
- .ifPresent(this::config);
+ try {
+ MetaConfig.metaConfig()
+ .ifPresent(this::config);
+ } catch (Exception e) {
+ System.getLogger(getClass().getName())
+ .log(System.Logger.Level.WARNING, "Failed to load SE meta-configuration,"
+ + " please make sure it has correct format.", e);
+ }
return this;
}
diff --git a/config/config/src/main/java/io/helidon/config/ConfigDiff.java b/config/config/src/main/java/io/helidon/config/ConfigDiff.java
index 13126dbf629..156c5652121 100644
--- a/config/config/src/main/java/io/helidon/config/ConfigDiff.java
+++ b/config/config/src/main/java/io/helidon/config/ConfigDiff.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2020 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.
@@ -86,8 +86,8 @@ private static boolean notEqual(Config left, Config right) {
}
private static Optional value(Config node) {
- if (node instanceof AbstractConfigImpl) {
- return ((AbstractConfigImpl) node).value();
+ if (node instanceof AbstractConfigImpl abstractConfig) {
+ return abstractConfig.value();
}
return node.asString().asOptional();
}
diff --git a/config/config/src/main/java/io/helidon/config/ConfigKeyImpl.java b/config/config/src/main/java/io/helidon/config/ConfigKeyImpl.java
index f920e4f8883..30e36d84dd2 100644
--- a/config/config/src/main/java/io/helidon/config/ConfigKeyImpl.java
+++ b/config/config/src/main/java/io/helidon/config/ConfigKeyImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2022 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.
@@ -109,8 +109,8 @@ ConfigKeyImpl child(String key) {
@Override
public ConfigKeyImpl child(io.helidon.common.config.Config.Key key) {
final List path;
- if (key instanceof ConfigKeyImpl) {
- path = ((ConfigKeyImpl) key).path;
+ if (key instanceof ConfigKeyImpl configKey) {
+ path = configKey.path;
} else {
path = new LinkedList<>();
while (!key.isRoot()) {
diff --git a/config/config/src/main/java/io/helidon/config/ConfigSourceRuntimeImpl.java b/config/config/src/main/java/io/helidon/config/ConfigSourceRuntimeImpl.java
index 7c91359737f..f95c91bfbeb 100644
--- a/config/config/src/main/java/io/helidon/config/ConfigSourceRuntimeImpl.java
+++ b/config/config/src/main/java/io/helidon/config/ConfigSourceRuntimeImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2022 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.
@@ -83,16 +83,15 @@ class ConfigSourceRuntimeImpl implements ConfigSourceRuntime {
// content source
AtomicReference lastStamp = new AtomicReference<>();
- if (configSource instanceof ParsableSource) {
+ if (configSource instanceof ParsableSource parsableSource) {
// eager parsable config source
- reloader = new ParsableConfigSourceReloader(configContext, (ParsableSource) source, lastStamp);
+ reloader = new ParsableConfigSourceReloader(configContext, parsableSource, lastStamp);
singleNodeFunction = objectNodeToSingleNode();
- } else if (configSource instanceof NodeConfigSource) {
+ } else if (configSource instanceof NodeConfigSource nodeConfigSource) {
// eager node config source
- reloader = new NodeConfigSourceReloader((NodeConfigSource) source, lastStamp);
+ reloader = new NodeConfigSourceReloader(nodeConfigSource, lastStamp);
singleNodeFunction = objectNodeToSingleNode();
- } else if (configSource instanceof LazyConfigSource) {
- LazyConfigSource lazySource = (LazyConfigSource) source;
+ } else if (configSource instanceof LazyConfigSource lazySource) {
// lazy config source
reloader = Optional::empty;
singleNodeFunction = lazySource::node;
@@ -143,8 +142,7 @@ class ConfigSourceRuntimeImpl implements ConfigSourceRuntime {
}
}
- if (!changesSupported && (configSource instanceof EventConfigSource)) {
- EventConfigSource event = (EventConfigSource) source;
+ if (!changesSupported && (configSource instanceof EventConfigSource event)) {
changesSupported = true;
changesRunnable = () -> event.onChange((key, config) -> listeners.forEach(it -> it.accept(key, config)));
}
@@ -222,8 +220,8 @@ private synchronized void initialLoad() {
}
// we may have media type mapping per node configured as well
- if (configSource instanceof AbstractConfigSource) {
- loadedData = loadedData.map(it -> ((AbstractConfigSource) configSource)
+ if (configSource instanceof AbstractConfigSource abstractConfigSource) {
+ loadedData = loadedData.map(it -> abstractConfigSource
.processNodeMapping(configContext::findParser, ConfigKeyImpl.of(), it));
}
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 afbe1b9c502..f78a7df2edb 100644
--- a/config/config/src/main/java/io/helidon/config/MetaConfig.java
+++ b/config/config/src/main/java/io/helidon/config/MetaConfig.java
@@ -22,7 +22,6 @@
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
-import java.util.function.Function;
import io.helidon.common.HelidonServiceLoader;
import io.helidon.common.media.type.MediaType;
@@ -241,18 +240,6 @@ static List configSources(Config metaConfig) {
return configSources;
}
- // only interested in config source
- static List configSources(Function supportedMediaType, List supportedSuffixes) {
- Optional metaConfigOpt = metaConfig();
-
- return metaConfigOpt
- .map(MetaConfig::configSources)
- .orElseGet(() -> MetaConfigFinder.findConfigSource(supportedMediaType, supportedSuffixes)
- .map(List::of)
- .orElseGet(List::of));
-
- }
-
private static Config createDefault() {
// use defaults
Config.Builder builder = Config.builder();
diff --git a/config/config/src/main/java/io/helidon/config/UrlConfigSource.java b/config/config/src/main/java/io/helidon/config/UrlConfigSource.java
index 24ebeda0c94..c936442e014 100644
--- a/config/config/src/main/java/io/helidon/config/UrlConfigSource.java
+++ b/config/config/src/main/java/io/helidon/config/UrlConfigSource.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.
@@ -139,8 +139,8 @@ public Optional load() throws ConfigException {
try {
URLConnection urlConnection = url.openConnection();
- if (urlConnection instanceof HttpURLConnection) {
- return httpContent((HttpURLConnection) urlConnection);
+ if (urlConnection instanceof HttpURLConnection httpURLConnection) {
+ return httpContent(httpURLConnection);
} else {
return genericContent(urlConnection);
}
diff --git a/config/config/src/main/java/io/helidon/config/UrlHelper.java b/config/config/src/main/java/io/helidon/config/UrlHelper.java
index 1267390e555..673c4564a32 100644
--- a/config/config/src/main/java/io/helidon/config/UrlHelper.java
+++ b/config/config/src/main/java/io/helidon/config/UrlHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2022 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.
@@ -44,8 +44,7 @@ static Optional dataStamp(URL url) {
// the URL may not be an HTTP URL
try {
URLConnection urlConnection = url.openConnection();
- if (urlConnection instanceof HttpURLConnection) {
- HttpURLConnection connection = (HttpURLConnection) urlConnection;
+ if (urlConnection instanceof HttpURLConnection connection) {
try {
connection.setRequestMethod(HEAD_METHOD);
if (STATUS_NOT_FOUND == connection.getResponseCode()) {
diff --git a/config/config/src/main/resources/META-INF/helidon/service.loader b/config/config/src/main/resources/META-INF/helidon/service.loader
index d07172ed9b3..c951c168384 100644
--- a/config/config/src/main/resources/META-INF/helidon/service.loader
+++ b/config/config/src/main/resources/META-INF/helidon/service.loader
@@ -1,4 +1,6 @@
# 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
+# This cannot be done for now, as ObjectConfigMapper ends up before built-ins when
+# we disable mapper services
+# io.helidon.config.spi.ConfigMapperProvider
diff --git a/config/metadata/codegen/src/main/java/io/helidon/config/metadata/codegen/ConfigMetadataCodegenExtension.java b/config/metadata/codegen/src/main/java/io/helidon/config/metadata/codegen/ConfigMetadataCodegenExtension.java
index 482d7bbb3d0..47e4bf2f96b 100644
--- a/config/metadata/codegen/src/main/java/io/helidon/config/metadata/codegen/ConfigMetadataCodegenExtension.java
+++ b/config/metadata/codegen/src/main/java/io/helidon/config/metadata/codegen/ConfigMetadataCodegenExtension.java
@@ -109,10 +109,12 @@ private void storeMetadata() {
.build());
}
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try (PrintWriter w = new PrintWriter(baos, true, StandardCharsets.UTF_8)) {
- Hson.Array.create(root).write(w);
+ if (!root.isEmpty()) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (PrintWriter w = new PrintWriter(baos, true, StandardCharsets.UTF_8)) {
+ Hson.Array.create(root).write(w);
+ }
+ ctx.filer().writeResource(baos.toByteArray(), META_FILE);
}
- ctx.filer().writeResource(baos.toByteArray(), META_FILE);
}
}
diff --git a/config/metadata/codegen/src/main/java/io/helidon/config/metadata/codegen/TypeHandlerBuilderApi.java b/config/metadata/codegen/src/main/java/io/helidon/config/metadata/codegen/TypeHandlerBuilderApi.java
index 11b94b43380..afc3b65a1fa 100644
--- a/config/metadata/codegen/src/main/java/io/helidon/config/metadata/codegen/TypeHandlerBuilderApi.java
+++ b/config/metadata/codegen/src/main/java/io/helidon/config/metadata/codegen/TypeHandlerBuilderApi.java
@@ -157,14 +157,14 @@ private OptionType typeForBlueprintFromSignature(TypedElementInfo element,
if (!ElementInfoPredicates.hasNoArgs(element)) {
throw new CodegenException("Method " + element + " is annotated with @Configured, "
+ "yet it has a parameter. Interface methods must not have parameters.",
- element.originatingElement().orElse(element.elementName()));
+ element.originatingElementValue());
}
TypeName returnType = element.typeName();
if (ElementInfoPredicates.isVoid(element)) {
throw new CodegenException("Method " + element + " is annotated with @Configured, "
+ "yet it is void. Interface methods must return the property type.",
- element.originatingElement().orElse(element.elementName()));
+ element.originatingElementValue());
}
if (returnType.isOptional()) {
diff --git a/config/metadata/codegen/src/main/java/io/helidon/config/metadata/codegen/TypeHandlerMetaApi.java b/config/metadata/codegen/src/main/java/io/helidon/config/metadata/codegen/TypeHandlerMetaApi.java
index aaf0587ae7c..70b1ef5ce7c 100644
--- a/config/metadata/codegen/src/main/java/io/helidon/config/metadata/codegen/TypeHandlerMetaApi.java
+++ b/config/metadata/codegen/src/main/java/io/helidon/config/metadata/codegen/TypeHandlerMetaApi.java
@@ -217,7 +217,7 @@ private void processTargetType(TypeInfo typeInfo, ConfiguredType type, TypeName
throw new CodegenException("Type " + typeName.fqName() + " is marked with @Configured"
+ ", yet it has a static builder() method. Please mark the builder instead "
+ "of this class.",
- typeInfo.originatingElement().orElseGet(typeInfo::typeName));
+ typeInfo.originatingElementValue());
}
}
@@ -243,14 +243,14 @@ private void processTargetType(TypeInfo typeInfo, ConfiguredType type, TypeName
+ validMethod
+ " does not have value defined. It is mandatory on non-builder "
+ "methods",
- typeInfo.originatingElement().orElseGet(typeInfo::typeName));
+ typeInfo.originatingElementValue());
}
if (data.description() == null || data.description().isBlank()) {
throw new CodegenException("ConfiguredOption on " + typeName.fqName() + "." + validMethod
+ " does not have description defined. It is mandatory on non-builder "
+ "methods",
- typeInfo.originatingElement().orElseGet(typeInfo::typeName));
+ typeInfo.originatingElementValue());
}
if (data.type() == null) {
@@ -281,7 +281,7 @@ private void processTargetType(TypeInfo typeInfo, ConfiguredType type, TypeName
throw new CodegenException("Type " + typeName.fqName() + " is marked as standalone configuration unit, "
+ "yet it does have "
+ "neither a builder method, nor a create method",
- typeInfo.originatingElement().orElseGet(typeInfo::typeName));
+ typeInfo.originatingElementValue());
}
typeInfo.elementInfo()
@@ -342,7 +342,7 @@ private OptionType optionType(TypedElementInfo elementInfo, ConfiguredOptionData
throw new CodegenException("Method " + elementInfo.elementName()
+ " is annotated with @ConfiguredOption, "
+ "yet it does not have explicit type, or exactly one parameter",
- typeInfo.originatingElement().orElseGet(typeInfo::typeName));
+ typeInfo.originatingElementValue());
} else {
TypedElementInfo parameter = parameters.iterator().next();
TypeName paramType = parameter.typeName();
diff --git a/config/tests/pom.xml b/config/tests/pom.xml
index 28d007e3c1f..870b7571945 100644
--- a/config/tests/pom.xml
+++ b/config/tests/pom.xml
@@ -64,5 +64,8 @@
config-metadata-meta-api
config-metadata-builder-api
test-lazy-source
+ test-no-config-sources
+ test-default-config-source
+ test-mp-se-meta
diff --git a/config/tests/test-default-config-source/pom.xml b/config/tests/test-default-config-source/pom.xml
new file mode 100644
index 00000000000..f5357e2b230
--- /dev/null
+++ b/config/tests/test-default-config-source/pom.xml
@@ -0,0 +1,63 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.config.tests
+ helidon-config-tests-project
+ 4.1.0-SNAPSHOT
+ ../pom.xml
+
+ helidon-test-default-config-source
+ Helidon Config Tests Default Config Source
+
+
+ Test that when no config sources are configured, we do not fall to meta config, but we want to
+ use default config sources
+
+
+
+
+ io.helidon.config
+ helidon-config
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.common.testing
+ helidon-common-testing-junit5
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
diff --git a/config/tests/test-default-config-source/src/main/resources/application.yaml b/config/tests/test-default-config-source/src/main/resources/application.yaml
new file mode 100644
index 00000000000..cf3e2c3af54
--- /dev/null
+++ b/config/tests/test-default-config-source/src/main/resources/application.yaml
@@ -0,0 +1,17 @@
+#
+# 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.
+#
+
+value: "from-file"
diff --git a/config/tests/test-default-config-source/src/main/resources/meta-config.yaml b/config/tests/test-default-config-source/src/main/resources/meta-config.yaml
new file mode 100644
index 00000000000..01468e56507
--- /dev/null
+++ b/config/tests/test-default-config-source/src/main/resources/meta-config.yaml
@@ -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.
+#
+
+# MP style, which would normally fail in Helidon SE, but should be ignored, as we do not specify meta config
+add-default-sources: true
+sources:
+ - type: "properties"
+ classpath: "app.properties"
\ No newline at end of file
diff --git a/config/tests/test-default-config-source/src/test/java/io/helidon/config/tests/nosources/DefaultSourceTest.java b/config/tests/test-default-config-source/src/test/java/io/helidon/config/tests/nosources/DefaultSourceTest.java
new file mode 100644
index 00000000000..6775f4a3211
--- /dev/null
+++ b/config/tests/test-default-config-source/src/test/java/io/helidon/config/tests/nosources/DefaultSourceTest.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.config.tests.nosources;
+
+import java.util.Optional;
+
+import io.helidon.config.Config;
+
+import org.junit.jupiter.api.Test;
+
+import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class DefaultSourceTest {
+ @Test
+ public void testDefaultSource() {
+ Config config = Config.builder()
+ .disableSystemPropertiesSource()
+ .disableEnvironmentVariablesSource()
+ .build();
+
+ Optional value = config.get("value")
+ .asString()
+ .asOptional();
+
+ // meta config MUST be ignored
+ assertThat("We defined not sources, we should fall back to default application.yaml",
+ value,
+ optionalValue(is("from-file")));
+ }
+}
diff --git a/config/tests/test-mp-se-meta/pom.xml b/config/tests/test-mp-se-meta/pom.xml
new file mode 100644
index 00000000000..54c23faa0b1
--- /dev/null
+++ b/config/tests/test-mp-se-meta/pom.xml
@@ -0,0 +1,67 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.config.tests
+ helidon-config-tests-project
+ 4.1.0-SNAPSHOT
+ ../pom.xml
+
+ helidon-config-tests-mp-se-meta
+ Helidon Config Tests MP SE Meta
+
+
+ Test that when using MP config with meta-config file name that conflicts with Helidon SE, everything
+ works as expected.
+
+
+
+
+ io.helidon.config
+ helidon-config
+
+
+ io.helidon.config
+ helidon-config-mp
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.common.testing
+ helidon-common-testing-junit5
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
diff --git a/config/tests/test-mp-se-meta/src/main/resources/app.properties b/config/tests/test-mp-se-meta/src/main/resources/app.properties
new file mode 100644
index 00000000000..763fa6bd7c6
--- /dev/null
+++ b/config/tests/test-mp-se-meta/src/main/resources/app.properties
@@ -0,0 +1,17 @@
+#
+# 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.
+#
+
+helidon.test.value=value
diff --git a/config/tests/test-mp-se-meta/src/main/resources/application.yaml b/config/tests/test-mp-se-meta/src/main/resources/application.yaml
new file mode 100644
index 00000000000..496e0c1ed88
--- /dev/null
+++ b/config/tests/test-mp-se-meta/src/main/resources/application.yaml
@@ -0,0 +1,17 @@
+#
+# 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.
+#
+
+helidon.app.value: "app-value"
\ No newline at end of file
diff --git a/config/tests/test-mp-se-meta/src/main/resources/meta-config.yaml b/config/tests/test-mp-se-meta/src/main/resources/meta-config.yaml
new file mode 100644
index 00000000000..607b6f02e6d
--- /dev/null
+++ b/config/tests/test-mp-se-meta/src/main/resources/meta-config.yaml
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+
+# This is an MP meta-config. It should not be named like this, but if the user does it, we should
+# honor it
+add-default-sources: true
+sources:
+ - type: "properties"
+ classpath: "app.properties"
\ No newline at end of file
diff --git a/config/tests/test-mp-se-meta/src/test/java/io/helidon/config/tests/mpsemeta/MpSeMetaTest.java b/config/tests/test-mp-se-meta/src/test/java/io/helidon/config/tests/mpsemeta/MpSeMetaTest.java
new file mode 100644
index 00000000000..46ef7958ee4
--- /dev/null
+++ b/config/tests/test-mp-se-meta/src/test/java/io/helidon/config/tests/mpsemeta/MpSeMetaTest.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.config.tests.mpsemeta;
+
+import io.helidon.common.config.GlobalConfig;
+
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+
+import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class MpSeMetaTest {
+ @Order(0)
+ @Test
+ public void testSeMeta() {
+ // this should not fail
+ io.helidon.config.Config config = io.helidon.config.Config.create();
+ assertThat(config.get("helidon.app.value").asString().asOptional(),
+ optionalValue(is("app-value")));
+ }
+
+ @Order(1)
+ @Test
+ public void testMpMeta() {
+ System.setProperty("io.helidon.config.mp.meta-config", "meta-config.yaml");
+ Config config = ConfigProvider.getConfig();
+ assertThat(config.getValue("helidon.test.value", String.class), is("value"));
+
+ assertThat(GlobalConfig.config()
+ .get("helidon.test.value")
+ .asString()
+ .asOptional(), optionalValue(is("value")));
+ }
+}
diff --git a/config/tests/test-no-config-sources/pom.xml b/config/tests/test-no-config-sources/pom.xml
new file mode 100644
index 00000000000..e07222e3728
--- /dev/null
+++ b/config/tests/test-no-config-sources/pom.xml
@@ -0,0 +1,63 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.config.tests
+ helidon-config-tests-project
+ 4.1.0-SNAPSHOT
+ ../pom.xml
+
+ helidon-config-tests-no-config-sources
+ Helidon Config Tests No Config Sources
+
+
+ Test that when no config sources are defined (such as when using Config.just()), we do not
+ fallback to meta configuration.
+
+
+
+
+ io.helidon.config
+ helidon-config
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.common.testing
+ helidon-common-testing-junit5
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+
diff --git a/config/tests/test-no-config-sources/src/main/resources/application.yaml b/config/tests/test-no-config-sources/src/main/resources/application.yaml
new file mode 100644
index 00000000000..cf3e2c3af54
--- /dev/null
+++ b/config/tests/test-no-config-sources/src/main/resources/application.yaml
@@ -0,0 +1,17 @@
+#
+# 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.
+#
+
+value: "from-file"
diff --git a/config/tests/test-no-config-sources/src/main/resources/meta-config.yaml b/config/tests/test-no-config-sources/src/main/resources/meta-config.yaml
new file mode 100644
index 00000000000..feac5d69543
--- /dev/null
+++ b/config/tests/test-no-config-sources/src/main/resources/meta-config.yaml
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+
+sources:
+ - type: "inlined"
+ properties:
+ value: "from-meta-config"
\ No newline at end of file
diff --git a/config/tests/test-no-config-sources/src/test/java/io/helidon/config/tests/nosources/NoSourcesTest.java b/config/tests/test-no-config-sources/src/test/java/io/helidon/config/tests/nosources/NoSourcesTest.java
new file mode 100644
index 00000000000..f0bf1ff212e
--- /dev/null
+++ b/config/tests/test-no-config-sources/src/test/java/io/helidon/config/tests/nosources/NoSourcesTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.config.tests.nosources;
+
+import java.util.List;
+import java.util.Optional;
+
+import io.helidon.config.Config;
+
+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.MatcherAssert.assertThat;
+
+public class NoSourcesTest {
+ @Test
+ public void testJust() {
+ Config config = Config.just();
+
+ Optional value = config.get("value")
+ .asString()
+ .asOptional();
+
+ assertThat("We have used Config.just(), there should be NO config source", value, is(optionalEmpty()));
+ }
+
+ @Test
+ public void testBuilder() {
+ Config config = Config.builder()
+ .disableEnvironmentVariablesSource()
+ .disableSystemPropertiesSource()
+ .sources(List.of())
+ .build();
+
+ Optional value = config.get("value")
+ .asString()
+ .asOptional();
+
+ assertThat("We have used Config.builder() without specifying any source, there should be NO config source",
+ value,
+ is(optionalEmpty()));
+ }
+
+ @Test
+ public void testMetaConfigExplicit() {
+ // a sanity check that meta configuration works when requested
+
+ Config config = Config.builder()
+ .disableEnvironmentVariablesSource()
+ .disableSystemPropertiesSource()
+ .metaConfig()
+ .build();
+
+ Optional value = config.get("value")
+ .asString()
+ .asOptional();
+
+ assertThat("We have used metaConfig(), there should be the inlined config source configured",
+ value,
+ optionalValue(is("from-meta-config")));
+ }
+
+ @Test
+ public void testMetaConfigCreate() {
+ // a sanity check that meta configuration works when requested
+
+ Config config = Config.create();
+
+ Optional value = config.get("value")
+ .asString()
+ .asOptional();
+
+ assertThat("We have used Config.create(), there should be the inlined config source configured",
+ value,
+ optionalValue(is("from-meta-config")));
+ }
+}
diff --git a/docs/src/main/asciidoc/mp/config/advanced-configuration.adoc b/docs/src/main/asciidoc/mp/config/advanced-configuration.adoc
index 16d3b1e85cf..af3239ac18d 100644
--- a/docs/src/main/asciidoc/mp/config/advanced-configuration.adoc
+++ b/docs/src/main/asciidoc/mp/config/advanced-configuration.adoc
@@ -119,7 +119,9 @@ If a file named `mp-meta-config.yaml`, or `mp-meta-config.properties` is in the
on the classpath, and there is no explicit setup of configuration in the code, the configuration will
be loaded from the `meta-config` file.
The location of the file can be overridden using system property `io.helidon.config.mp.meta-config`,
-or environment variable `HELIDON_MP_META_CONFIG`
+or environment variable `HELIDON_MP_META_CONFIG`.
+
+*Important Note:* Do not use custom files named `meta-config.*`, as even when using Micro-Profile, we still use Helidon configuration in some of our components, and this file would be recognized as a Helidon SE Meta Configuration file, which may cause erroneous behavior.
[source,yaml]
.Example of a YAML meta configuration file:
diff --git a/etc/scripts/release.sh b/etc/scripts/release.sh
index 8b0ac750564..bf7f0632574 100755
--- a/etc/scripts/release.sh
+++ b/etc/scripts/release.sh
@@ -23,7 +23,7 @@ on_error(){
CODE="${?}" && \
set +x && \
printf "[ERROR] Error(code=%s) occurred at %s:%s command: %s\n" \
- "${CODE}" "${BASH_SOURCE[0]}" "${LINENO}" "${BASH_COMMAND}" >&2
+ "${CODE}" "${BASH_SOURCE[0]}" "${LINENO}" "${BASH_COMMAND}" >&2
}
trap on_error ERR
@@ -99,16 +99,16 @@ readonly COMMAND
exec 6>&1 1>&2
if [ -z "${COMMAND+x}" ] ; then
- echo "ERROR: no command provided"
- exit 1
+ echo "ERROR: no command provided"
+ exit 1
fi
case ${COMMAND} in
"update_version")
if [ -z "${VERSION}" ] ; then
- echo "ERROR: version required" >&2
- usage
- exit 1
+ echo "ERROR: version required" >&2
+ usage
+ exit 1
fi
;;
"create_tag"|"get_version")
diff --git a/etc/scripts/smoketest.sh b/etc/scripts/smoketest.sh
index a64cf6bba96..db49aba72b5 100755
--- a/etc/scripts/smoketest.sh
+++ b/etc/scripts/smoketest.sh
@@ -15,55 +15,26 @@
# limitations under the License.
#
-# Smoke test a Helidon release. This assumes:
-# 1. The release has a source tag in the Helidon GitHub repo
-# 2. The bits are in either the OSS Sonatype Staging Repo or Maven Central
-# 3. You have a profile defined as "ossrh-staging" that configures
-# https://oss.sonatype.org/content/groups/staging/ as a repository
-# See bottom of RELEASE.md for details
-
set -o pipefail || true # trace ERR through pipes
set -o errtrace || true # trace ERR through commands and functions
set -o errexit || true # exit the script if any statement returns a non-true return value
on_error(){
- CODE="${?}" && \
- set +x && \
- printf "[ERROR] Error(code=%s) occurred at %s:%s command: %s\n" \
- "${CODE}" "${BASH_SOURCE[0]}" "${LINENO}" "${BASH_COMMAND}"
+ CODE="${?}" && \
+ set +x && \
+ printf "[ERROR] Error(code=%s) occurred at %s:%s command: %s\n" \
+ "${CODE}" "${BASH_SOURCE[0]}" "${LINENO}" "${BASH_COMMAND}"
}
trap on_error ERR
-# Path to this script
-if [ -h "${0}" ] ; then
- SCRIPT_PATH="$(readlink "${0}")"
-else
- SCRIPT_PATH="${0}"
-fi
-readonly SCRIPT_PATH
-
-
-SCRIPT_DIR=$(dirname "${SCRIPT_PATH}")
-readonly SCRIPT_DIR
-
-# Local error handler
-smoketest_on_error(){
- on_error
- echo "===== Log file: ${OUTPUT_FILE} ====="
- # In case there is a process left running
-}
-
-# Setup error handling using local error handler (defined in includes/error_handlers.sh)
-error_trap_setup 'smoketest_on_error'
-
usage(){
cat <&2
+ usage
exit 1
fi
-set -u
-
-full(){
- echo "===== Full Test ====="
- cd "${SCRATCH}"
- quick
- cd "${SCRATCH}"
-
- if [[ "${VERSION}" =~ .*SNAPSHOT ]]; then
- echo "WARNING! SNAPSHOT version. Skipping tag checkout"
- else
- echo "===== Cloning Workspace ${GIT_URL} ====="
- git clone "${GIT_URL}"
- cd "${SCRATCH}/helidon"
- echo "===== Checking out tags/${VERSION} ====="
- git checkout "tags/${VERSION}"
- fi
-
- echo "===== Building examples ====="
- cd "${SCRATCH}/helidon/examples"
- # XXX we exclude todo-app frontend due to the issues with npm behind firewall
- mvn "${MAVEN_ARGS}" clean install -pl '!todo-app/frontend' ${STAGED_PROFILE}
- cd "${SCRATCH}"
-
- echo "===== Building test support ====="
- cd "${SCRATCH}/helidon/microprofile/tests/"
- mvn -N "${MAVEN_ARGS}" clean install ${STAGED_PROFILE}
- cd "${SCRATCH}/helidon/microprofile/tests/junit5"
- mvn "${MAVEN_ARGS}" clean install ${STAGED_PROFILE}
- cd "${SCRATCH}/helidon/microprofile/tests/junit5-tests"
- mvn "${MAVEN_ARGS}" clean install ${STAGED_PROFILE}
-
- echo "===== Running tests ====="
- cd "${SCRATCH}/helidon/tests"
- mvn "${MAVEN_ARGS}" clean install ${STAGED_PROFILE}
-
- # Primes dependencies for native-image builds
- cd "${SCRATCH}/helidon/tests/integration/native-image"
- mvn "${MAVEN_ARGS}" clean install ${STAGED_PROFILE}
-
- echo "===== Running native image tests ====="
- if [ -z "${GRAALVM_HOME}" ]; then
- echo "WARNING! GRAALVM_HOME is not set. Skipping native image tests"
- else
- echo "GRAALVM_HOME=${GRAALVM_HOME}"
- readonly native_image_tests="mp-1 mp-2 mp-3"
- for native_test in ${native_image_tests}; do
- cd "${SCRATCH}/helidon/tests/integration/native-image/${native_test}"
- mvn "${MAVEN_ARGS}" clean package -Pnative-image ${STAGED_PROFILE}
- done
-
- # Run this one because it has no pre-reqs and self-tests
- cd "${SCRATCH}/helidon/tests/integration/native-image/mp-1"
- target/helidon-tests-native-image-mp-1
- fi
-
+PID=""
+trap '[ -n "${PID}" ] && kill ${PID} 2> /dev/null || true' 0
+
+maven_proxies() {
+ [ -f "${HOME}/.m2/settings.xml" ] && \
+ awk -f- "${HOME}/.m2/settings.xml" </{
+ IN_PROXIES="true"
+ next
+ }
+ /<\/proxies>/{
+ IN_PROXIES="false"
+ }
+ {
+ if (IN_PROXIES=="true") {
+ print \$0
+ }
+ }
+EOF
}
-waituntilready() {
- # Give app a chance to start --retry will retry until it is up
- # --retry-connrefused requires curl 7.51.0 or newer
- sleep 6
- #curl -s --retry-connrefused --retry 3 -X GET http://localhost:8080/health/live
- curl -s --retry 3 -X GET http://localhost:8080/health/live
- echo
+maven_settings() {
+ cat <
+
+ $(maven_proxies)
+
+
+
+ ossrh-staging
+
+
+ ossrh-staging
+ OSS Sonatype Staging
+ https://oss.sonatype.org/content/groups/staging/
+
+ false
+
+
+ true
+
+
+
+
+
+ ossrh-staging
+ OSS Sonatype Staging
+ https://oss.sonatype.org/content/groups/staging/
+
+ false
+
+
+ true
+
+
+
+
+
+
+EOF
}
-testGET() {
- echo "GET $1"
- http_code=$(curl -s -o /dev/null -w "%{http_code}" -X GET "${1}")
- if [ "${http_code}" -ne "200" ]; then
- echo "ERROR: Bad HTTP code. Expected 200 got ${http_code}. GET ${1}"
- kill "${PID}"
- return 1
- fi
- return 0
+# arg1: uri
+wait_ready() {
+ sleep 6
+ if ! kill -0 "${PID}" 2> /dev/null ; then
+ echo "ERROR: process not alive" >&2
+ return 1
+ fi
+
+ case ${1} in
+ bare-*)
+ # no-op
+ ;;
+ *-mp)
+ curl -q -s -f \
+ --retry 3 \
+ -o /dev/null \
+ -w "%{http_code} %{url_effective}\n" \
+ http://localhost:8080/health/live
+ ;;
+ *)
+ curl -q -s -f \
+ --retry 3 \
+ -o /dev/null \
+ -w "%{http_code} %{url_effective}\n" \
+ http://localhost:8080/observe/health/live
+ ;;
+ esac
}
-#
-# $1 = archetype name: "quickstart-se"
-buildAndTestArchetype(){
- archetype_name=${1}
- archetype_pkg=$(echo "${archetype_name}" | tr "\-" "\.")
-
- echo "===== Testing Archetype ${archetype_name} ====="
-
- mvn "${MAVEN_ARGS}" -U archetype:generate -DinteractiveMode=false \
- -DarchetypeGroupId=io.helidon.archetypes \
- -DarchetypeArtifactId="helidon-${archetype_name}" \
- -DarchetypeVersion="${VERSION}" \
- -DgroupId=io.helidon.examples \
- -DartifactId=helidon-"${archetype_name}" \
- -Dpackage=io.helidon.examples."${archetype_pkg}" \
- ${STAGED_PROFILE}
-
-
- echo "===== ${archetype_name}: building jar ====="
- mvn "${MAVEN_ARGS}" -f helidon-"${archetype_name}"/pom.xml ${STAGED_PROFILE} clean package
-
- echo "===== Running and pinging ${archetype_name} app using jar ====="
- java -jar "helidon-${archetype_name}/target/helidon-${archetype_name}.jar" &
- PID=$!
- testApp "${archetype_name}"
- kill ${PID}
-
- echo "===== ${archetype_name}: building jlink image ====="
- mvn "${MAVEN_ARGS}" -f "helidon-${archetype_name}/pom.xml" ${STAGED_PROFILE} -Pjlink-image package -DskipTests
-
- echo "===== Running and pinging ${archetype_name} app using jlink image ====="
- "helidon-${archetype_name}/target/helidon-${archetype_name}-jri/bin/start" &
- PID=$!
- testApp "${archetype_name}"
- kill ${PID}
- sleep 1
+# arg1: url
+http_get() {
+ curl -q -s -f \
+ -w "\n%{http_code} %{url_effective}\n" \
+ "${1}"
}
-testApp(){
- # Wait for app to come up
- waituntilready
-
- # Hit some endpoints
- if [ "${archetype_name}" = "quickstart-se" ] || [ "${archetype_name}" = "quickstart-mp" ]; then
- testGET http://localhost:8080/greet
- testGET http://localhost:8080/greet/Joe
- fi
- testGET http://localhost:8080/health
- testGET http://localhost:8080/metrics
+# arg1: archetype
+test_app(){
+ # health & metrics
+ case ${1} in
+ bare-*)
+ # no-op
+ ;;
+ *-se)
+ http_get http://localhost:8080/observe/health
+ http_get http://localhost:8080/observe/metrics
+ ;;
+ *-mp)
+ http_get http://localhost:8080/health
+ http_get http://localhost:8080/metrics
+ ;;
+ esac
+
+ # app endpoint
+ case ${1} in
+ database-*)
+ # no-op
+ ;;
+ bare-se|quickstart-*)
+ http_get http://localhost:8080/greet
+ http_get http://localhost:8080/greet/Joe
+ ;;
+ bare-mp)
+ http_get http://localhost:8080/simple-greet
+ ;;
+ esac
}
-quick(){
- readonly archetypes="
- quickstart-se \
- quickstart-mp \
- bare-se \
- bare-mp \
- database-se \
- database-mp \
- "
+# arg1: archetype
+test_archetype(){
+ printf "\n*******************************************"
+ printf "\nINFO: %s - Generating project" "${ARCHETYPE}"
+ printf "\n*******************************************\n\n"
+
+ # shellcheck disable=SC2086
+ mvn ${MAVEN_ARGS} -U \
+ -DinteractiveMode=false \
+ -DarchetypeGroupId=io.helidon.archetypes \
+ -DarchetypeArtifactId="helidon-${ARCHETYPE}" \
+ -DarchetypeVersion="${VERSION}" \
+ -DgroupId=io.helidon.smoketest \
+ -DartifactId=helidon-"${ARCHETYPE}" \
+ -Dpackage=io.helidon.smoketest."${ARCHETYPE/-/.}" \
+ archetype:generate
+
+ printf "\n*******************************************"
+ printf "\nINFO: %s - Building jar" "${ARCHETYPE}"
+ printf "\n*******************************************\n\n"
+
+ # shellcheck disable=SC2086
+ mvn ${MAVEN_ARGS} \
+ -f "helidon-${ARCHETYPE}/pom.xml" \
+ clean package
+
+ printf "\n*******************************************"
+ printf "\nINFO: %s - Running and pinging app using jar image" "${ARCHETYPE}"
+ printf "\n*******************************************\n\n"
+
+ java -jar "helidon-${ARCHETYPE}/target/helidon-${ARCHETYPE}.jar" &
+ PID=${!}
+ wait_ready "${ARCHETYPE}"
+ test_app "${ARCHETYPE}"
+ kill ${PID}
+
+ printf "\n*******************************************"
+ printf "\nINFO: %s - Building jlink image" "${ARCHETYPE}"
+ printf "\n*******************************************\n\n"
+
+ # shellcheck disable=SC2086
+ mvn ${MAVEN_ARGS} \
+ -f "helidon-${ARCHETYPE}/pom.xml" \
+ -DskipTests \
+ -Pjlink-image \
+ package
+
+ printf "\n*******************************************"
+ printf "\nINFO: %s - Running and pinging app using jlink image" "${ARCHETYPE}"
+ printf "\n*******************************************\n\n"
+
+ "helidon-${ARCHETYPE}/target/helidon-${ARCHETYPE}-jri/bin/start" &
+ PID=${!}
+ wait_ready "${ARCHETYPE}"
+ test_app "${ARCHETYPE}"
+ kill ${PID}
+}
- echo "===== Quick Test ====="
- cd "${SCRATCH}"
+WORK_DIR="${TMPDIR:-$(mktemp -d)}/helidon-smoke/${VERSION}-$(date +%Y-%m-%d-%H-%M-%S)"
+readonly WORK_DIR
- echo "===== Testing Archetypes ====="
+LOG_FILE="${WORK_DIR}/test.log"
+readonly LOG_FILE
- for a in ${archetypes}; do
- buildAndTestArchetype "${a}"
- done
-}
+mkdir -p "${WORK_DIR}"
-cd "${SCRATCH}"
+maven_settings > "${WORK_DIR}/settings.xml"
+MAVEN_ARGS="${MAVEN_ARGS} -s ${WORK_DIR}/settings.xml"
-OUTPUT_FILE=${SCRATCH}/helidon-smoketest-log.txt
-LOCAL_MVN_REPO=$(mvn "${MAVEN_ARGS}" help:evaluate -Dexpression=settings.localRepository | grep -v '\[INFO\]')
-readonly OUTPUT_FILE LOCAL_MVN_REPO
+exec 1>> >(tee "${LOG_FILE}")
+exec 2>> >(tee "${LOG_FILE}")
-echo "===== Running in ${SCRATCH} ====="
-echo "===== Log file: ${OUTPUT_FILE} ====="
+cd "${WORK_DIR}"
-if [ -n "${CLEAN_MVN_REPO}" ] && [ -d "${LOCAL_MVN_REPO}" ]; then
- echo "===== Cleaning release from local maven repository ${LOCAL_MVN_REPO} ====="
- find "${LOCAL_MVN_REPO}/io/helidon" -depth -name "${VERSION}" -type d -exec rm -rf {} \;
-fi
+printf "\n*******************************************"
+printf "\nINFO: Directory - %s" "${WORK_DIR}"
+printf "\nINFO: Log - %s" "${LOG_FILE}"
+printf "\n*******************************************\n\n"
-# Invoke command
-${COMMAND} | tee "${OUTPUT_FILE}"
+test_archetype "${ARCHETYPE}"
-echo "===== Log file: ${OUTPUT_FILE} ====="
+printf "\n*******************************************"
+printf "\nINFO: Directory - %s" "${WORK_DIR}"
+printf "\nINFO: Log - %s" "${LOG_FILE}"
+printf "\n*******************************************\n\n"
diff --git a/inject/maven-plugin/src/main/java/io/helidon/inject/maven/plugin/QualifierConfig.java b/inject/maven-plugin/src/main/java/io/helidon/inject/maven/plugin/QualifierConfig.java
index 6753c140f44..454cdcad0e8 100644
--- a/inject/maven-plugin/src/main/java/io/helidon/inject/maven/plugin/QualifierConfig.java
+++ b/inject/maven-plugin/src/main/java/io/helidon/inject/maven/plugin/QualifierConfig.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.inject.maven.plugin;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -76,6 +77,11 @@ public Map values() {
return Map.of("value", value);
}
+ @Override
+ public List metaAnnotations() {
+ return List.of();
+ }
+
@Override
public int compareTo(Annotation o) {
return this.typeName().compareTo(o.typeName());
diff --git a/metadata/hson/src/test/java/io/helidon/metadata/hson/ExistingTypesTest.java b/metadata/hson/src/test/java/io/helidon/metadata/hson/ExistingTypesTest.java
index 9d5cf77d9fa..357fa7b092c 100644
--- a/metadata/hson/src/test/java/io/helidon/metadata/hson/ExistingTypesTest.java
+++ b/metadata/hson/src/test/java/io/helidon/metadata/hson/ExistingTypesTest.java
@@ -33,6 +33,16 @@
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
class ExistingTypesTest {
+ @Test
+ void testNewServiceRegistry() throws IOException {
+ Hson.Array modules;
+ try (InputStream inputStream = resource("/new-service-registry.json")) {
+ assertThat(inputStream, notNullValue());
+ modules = Hson.parse(inputStream)
+ .asArray();
+ }
+ assertThat(modules, notNullValue());
+ }
@Test
void testServiceRegistry() throws IOException {
Hson.Struct object;
diff --git a/metadata/hson/src/test/resources/new-service-registry.json b/metadata/hson/src/test/resources/new-service-registry.json
new file mode 100644
index 00000000000..de9d76b7038
--- /dev/null
+++ b/metadata/hson/src/test/resources/new-service-registry.json
@@ -0,0 +1,16 @@
+[
+ {
+ "module": "unnamed/io.helidon.metrics",
+ "services": [
+ {
+ "type": "inject",
+ "weight": 90.0,
+ "descriptor": "io.helidon.metrics.CountedInterceptor__ServiceDescriptor",
+ "contracts": [
+ "io.helidon.metrics.CountedInterceptor",
+ "io.helidon.service.inject.api.Interception.Interceptor"
+ ]
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/parent/pom.xml b/parent/pom.xml
index d41cbb96500..073609364b3 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -199,7 +199,7 @@
- staging
+ ossrh-staging
ossrh-staging
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
index b5f0349996f..f1b21454c94 100644
--- a/service/codegen/src/main/java/io/helidon/service/codegen/GenerateServiceDescriptor.java
+++ b/service/codegen/src/main/java/io/helidon/service/codegen/GenerateServiceDescriptor.java
@@ -194,6 +194,8 @@ private ClassModel.Builder generate() {
dependenciesMethod(classModel, params, superType);
isAbstractMethod(classModel, superType, isAbstractClass);
instantiateMethod(classModel, serviceType, params, isAbstractClass);
+ postConstructMethod(typeInfo, classModel, serviceType);
+ preDestroyMethod(typeInfo, classModel, serviceType);
weightMethod(typeInfo, classModel, superType);
// service type is an implicit contract
@@ -759,6 +761,62 @@ private void instantiateMethod(ClassModel.Builder classModel,
.update(it -> createInstantiateBody(serviceType, it, params)));
}
+ private void postConstructMethod(TypeInfo typeInfo, ClassModel.Builder classModel, TypeName serviceType) {
+ // postConstruct()
+ lifecycleMethod(typeInfo, ServiceCodegenTypes.SERVICE_ANNOTATION_POST_CONSTRUCT).ifPresent(method -> {
+ classModel.addMethod(postConstruct -> postConstruct.name("postConstruct")
+ .addAnnotation(Annotations.OVERRIDE)
+ .addParameter(instance -> instance.type(serviceType)
+ .name("instance"))
+ .addContentLine("instance." + method.elementName() + "();"));
+ });
+ }
+
+ private void preDestroyMethod(TypeInfo typeInfo, ClassModel.Builder classModel, TypeName serviceType) {
+ // preDestroy
+ lifecycleMethod(typeInfo, ServiceCodegenTypes.SERVICE_ANNOTATION_PRE_DESTROY).ifPresent(method -> {
+ classModel.addMethod(preDestroy -> preDestroy.name("preDestroy")
+ .addAnnotation(Annotations.OVERRIDE)
+ .addParameter(instance -> instance.type(serviceType)
+ .name("instance"))
+ .addContentLine("instance." + method.elementName() + "();"));
+ });
+ }
+
+ private Optional lifecycleMethod(TypeInfo typeInfo, TypeName annotationType) {
+ List list = typeInfo.elementInfo()
+ .stream()
+ .filter(ElementInfoPredicates.hasAnnotation(annotationType))
+ .toList();
+ if (list.isEmpty()) {
+ return Optional.empty();
+ }
+ if (list.size() > 1) {
+ throw new IllegalStateException("There is more than one method annotated with " + annotationType.fqName()
+ + ", which is not allowed on type " + typeInfo.typeName().fqName());
+ }
+ TypedElementInfo method = list.getFirst();
+ if (method.accessModifier() == AccessModifier.PRIVATE) {
+ throw new CodegenException("Method annotated with " + annotationType.fqName()
+ + ", is private, which is not supported: " + typeInfo.typeName().fqName()
+ + "#" + method.elementName(),
+ method.originatingElement().orElseGet(method::elementName));
+ }
+ if (!method.parameterArguments().isEmpty()) {
+ throw new CodegenException("Method annotated with " + annotationType.fqName()
+ + ", has parameters, which is not supported: " + typeInfo.typeName().fqName()
+ + "#" + method.elementName(),
+ method.originatingElement().orElseGet(method::elementName));
+ }
+ if (!method.typeName().equals(TypeNames.PRIMITIVE_VOID)) {
+ throw new CodegenException("Method annotated with " + annotationType.fqName()
+ + ", is not void, which is not supported: " + typeInfo.typeName().fqName()
+ + "#" + method.elementName(),
+ method.originatingElement().orElseGet(method::elementName));
+ }
+ return Optional.of(method);
+ }
+
private void createInstantiateBody(TypeName serviceType,
Method.Builder method,
List params) {
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
index 5414ed3cd00..c093f37add4 100644
--- a/service/codegen/src/main/java/io/helidon/service/codegen/RegistryCodegenExtension.java
+++ b/service/codegen/src/main/java/io/helidon/service/codegen/RegistryCodegenExtension.java
@@ -17,7 +17,7 @@
package io.helidon.service.codegen;
/**
- * Code generation extension for Helidon Service REgistry.
+ * Code generation extension for Helidon Service Registry.
*/
interface RegistryCodegenExtension {
/**
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
index 785d9567cf0..317ac1222b7 100644
--- a/service/codegen/src/main/java/io/helidon/service/codegen/ServiceCodegenTypes.java
+++ b/service/codegen/src/main/java/io/helidon/service/codegen/ServiceCodegenTypes.java
@@ -26,6 +26,16 @@ 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.PreDestroy}.
+ */
+ public static final TypeName SERVICE_ANNOTATION_PRE_DESTROY =
+ TypeName.create("io.helidon.service.registry.Service.PreDestroy");
+ /**
+ * {@link io.helidon.common.types.TypeName} for {@code io.helidon.service.registry.Service.PostConstruct}.
+ */
+ public static final TypeName SERVICE_ANNOTATION_POST_CONSTRUCT =
+ TypeName.create("io.helidon.service.registry.Service.PostConstruct");
/**
* {@link io.helidon.common.types.TypeName} for {@code io.helidon.service.registry.Service.Contract}.
*/
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 39ef78db293..b6b13b517aa 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
@@ -26,7 +26,6 @@
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;
@@ -46,11 +45,14 @@ class CoreServiceRegistry implements ServiceRegistry {
Comparator.comparing(ServiceProvider::weight).reversed()
.thenComparing(ServiceProvider::descriptorType);
- private final Map> providersByContract;
+ private final Map> providersByContract;
private final Map providersByService;
+ private final List allProviders;
+ @SuppressWarnings({"rawtypes", "unchecked"})
CoreServiceRegistry(ServiceRegistryConfig config, ServiceDiscovery serviceDiscovery) {
- Map> providers = new HashMap<>();
+ List allProviders = new ArrayList<>();
+ Map> providers = new HashMap<>();
Map providersByService = new IdentityHashMap<>();
// each just once
@@ -65,18 +67,22 @@ class CoreServiceRegistry implements ServiceRegistry {
config.serviceInstances().forEach((descriptor, instance) -> {
if (processedDescriptorTypes.add(descriptor.descriptorType())) {
BoundInstance bi = new BoundInstance(descriptor, Optional.of(instance));
+ allProviders.add(bi);
providersByService.put(descriptor, bi);
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)));
- providersByService.put(descriptor, bd);
- addContracts(providers, descriptor.contracts(), bd);
- }
+ for (Descriptor descriptor : config.serviceDescriptors()) {
+ BoundDescriptor bd = new BoundDescriptor(this, descriptor, LazyValue.create(() -> {
+ var instance = instance(descriptor);
+ instance.ifPresent(descriptor::postConstruct);
+ return instance;
+ }));
+ allProviders.add(bd);
+ providersByService.put(descriptor, bd);
+ addContracts(providers, descriptor.contracts(), bd);
}
boolean logUnsupported = LOGGER.isLoggable(Level.TRACE);
@@ -95,12 +101,20 @@ class CoreServiceRegistry implements ServiceRegistry {
DiscoveredDescriptor dd = new DiscoveredDescriptor(this,
descriptorMeta,
instanceSupplier(descriptorMeta));
+ allProviders.add(dd);
providersByService.put(descriptorMeta.descriptor(), dd);
addContracts(providers, descriptorMeta.contracts(), dd);
}
}
+ // sort all the providers
+ providers.values()
+ .forEach(it -> it.sort(PROVIDER_COMPARATOR));
+ allProviders.sort(PROVIDER_COMPARATOR);
+ allProviders.reversed();
+
this.providersByContract = Map.copyOf(providers);
this.providersByService = providersByService;
+ this.allProviders = List.copyOf(allProviders);
}
@Override
@@ -157,39 +171,57 @@ public Optional get(ServiceInfo serviceInfo) {
@Override
public List allServices(TypeName contract) {
return Optional.ofNullable(providersByContract.get(contract))
- .orElseGet(Set::of)
+ .orElseGet(List::of)
.stream()
.map(ServiceProvider::descriptor)
.collect(Collectors.toUnmodifiableList());
}
- private static void addContracts(Map> providers,
+ void shutdown() {
+ allProviders.forEach(ServiceProvider::close);
+ }
+
+ private static void addContracts(Map> providers,
Set contracts,
ServiceProvider provider) {
for (TypeName contract : contracts) {
- providers.computeIfAbsent(contract, it -> new TreeSet<>(PROVIDER_COMPARATOR))
+ providers.computeIfAbsent(contract, it -> new ArrayList<>())
.add(provider);
}
}
- private Supplier> instanceSupplier(DescriptorHandler descriptorMeta) {
- LazyValue> serviceInstance = LazyValue.create(() -> instance(descriptorMeta.descriptor()));
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private ServiceAndInstance instanceSupplier(DescriptorHandler descriptorMeta) {
+ LazyValue> serviceInstance = LazyValue.create(() -> {
+ Descriptor descriptor = descriptorMeta.descriptor();
+ var instance = instance(descriptor);
+ instance.ifPresent(descriptor::postConstruct);
+ return instance;
+ });
if (descriptorMeta.contracts().contains(TypeNames.SUPPLIER)) {
- return () -> instanceFromSupplier(descriptorMeta.descriptor(), serviceInstance);
+ return new ServiceAndInstance(serviceInstance,
+ () -> instanceFromSupplier(descriptorMeta.descriptor(), serviceInstance));
} else {
- return serviceInstance;
+ return new ServiceAndInstance(serviceInstance);
+ }
+ }
+
+ private record ServiceAndInstance(LazyValue> serviceSupplier,
+ Supplier> instanceSupplier) {
+ ServiceAndInstance(LazyValue> serviceSupplier) {
+ this(serviceSupplier, serviceSupplier);
}
}
private List allProviders(TypeName contract) {
- Set serviceProviders = providersByContract.get(contract);
+ List serviceProviders = providersByContract.get(contract);
if (serviceProviders == null) {
return List.of();
}
- return new ArrayList<>(serviceProviders);
+ return List.copyOf(serviceProviders);
}
private Optional instanceFromSupplier(Descriptor> descriptor, LazyValue> serviceInstanceSupplier) {
@@ -264,6 +296,8 @@ private interface ServiceProvider {
double weight();
TypeName descriptorType();
+
+ void close();
}
private record BoundInstance(Descriptor> descriptor, Optional instance) implements ServiceProvider {
@@ -276,6 +310,11 @@ public double weight() {
public TypeName descriptorType() {
return descriptor.descriptorType();
}
+
+ @Override
+ public void close() {
+ // as the instance was provided from outside, we do not call pre-destroy
+ }
}
private record BoundDescriptor(CoreServiceRegistry registry,
@@ -316,17 +355,25 @@ public double weight() {
public TypeName descriptorType() {
return descriptor.descriptorType();
}
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ @Override
+ public void close() {
+ if (lazyInstance.isLoaded()) {
+ lazyInstance.get().ifPresent(it -> ((Descriptor) descriptor).preDestroy(it));
+ }
+ }
}
private record DiscoveredDescriptor(CoreServiceRegistry registry,
DescriptorHandler metadata,
- Supplier> instanceSupplier,
+ ServiceAndInstance instances,
ReentrantLock lock) implements ServiceProvider {
private DiscoveredDescriptor(CoreServiceRegistry registry,
DescriptorHandler metadata,
- Supplier> instanceSupplier) {
- this(registry, metadata, instanceSupplier, new ReentrantLock());
+ ServiceAndInstance instances) {
+ this(registry, metadata, instances, new ReentrantLock());
}
@Override
@@ -336,6 +383,7 @@ public Descriptor> descriptor() {
@Override
public Optional instance() {
+ var instanceSupplier = instances.instanceSupplier();
if ((instanceSupplier instanceof LazyValue> lv) && lv.isLoaded()) {
return instanceSupplier.get();
}
@@ -361,5 +409,14 @@ public double weight() {
public TypeName descriptorType() {
return metadata.descriptorType();
}
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ @Override
+ public void close() {
+ var serviceSupplier = instances.serviceSupplier();
+ if (serviceSupplier.isLoaded()) {
+ serviceSupplier.get().ifPresent(it -> ((Descriptor) metadata.descriptor()).preDestroy(it));
+ }
+ }
}
}
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
index 1c8f1a8aa28..a6d61242da5 100644
--- a/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistryManager.java
+++ b/service/registry/src/main/java/io/helidon/service/registry/CoreServiceRegistryManager.java
@@ -63,6 +63,7 @@ public void shutdown() {
Lock lock = lifecycleLock.writeLock();
try {
lock.lock();
+ registry.shutdown();
registry = null;
} finally {
lock.unlock();
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 624b9becb40..7061c0dd63a 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
@@ -344,6 +344,22 @@ default Object instantiate(DependencyContext ctx) {
throw new IllegalStateException("Cannot instantiate type " + serviceType().fqName() + ", as it is either abstract,"
+ " or an interface.");
}
+
+ /**
+ * Invoke {@link io.helidon.service.registry.Service.PostConstruct} annotated method(s).
+ *
+ * @param instance instance to use
+ */
+ default void postConstruct(T instance) {
+ }
+
+ /**
+ * Invoke {@link io.helidon.service.registry.Service.PreDestroy} annotated method(s).
+ *
+ * @param instance instance to use
+ */
+ default void preDestroy(T instance) {
+ }
}
private record TypeAndName(String type, String name) {
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 572ef7f9bcf..8f3d29eeafd 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
@@ -72,6 +72,36 @@ private Service() {
TypeName TYPE = TypeName.create(Provider.class);
}
+ /**
+ * A method annotated with this annotation will be invoked after the constructor is finished
+ * and all dependencies are satisfied.
+ *
+ * The method must not have any parameters and must be accessible (not {@code private}).
+ */
+ @Documented
+ @Retention(RetentionPolicy.CLASS)
+ @Target(ElementType.METHOD)
+ public @interface PostConstruct {
+ }
+
+ /**
+ * A method annotated with this annotation will be invoked when the service registry shuts down.
+ *
+ * Behavior of this annotation may differ based on the service registry implementation used. For example
+ * when using Helidon Service Inject (to be introduced), a pre-destroy method would be used when the scope
+ * a service is created in is finished. The core service registry behaves similar like a singleton scope - instance
+ * is created once, and pre-destroy is called when the registry is shut down.
+ * This also implies that instances that are NOT created within a scope cannot have their pre-destroy methods
+ * invoked, as we do not control their lifecycle.
+ *
+ * The method must not have any parameters and must be accessible (not {@code private}).
+ */
+ @Documented
+ @Retention(RetentionPolicy.CLASS)
+ @Target(ElementType.METHOD)
+ public @interface PreDestroy {
+ }
+
/**
* 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
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 c0ccc588d80..3e6bf64c623 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
@@ -62,6 +62,8 @@ void testTypes() {
}
checkField(toCheck, checked, fields, "SERVICE_ANNOTATION_PROVIDER", Service.Provider.class);
+ checkField(toCheck, checked, fields, "SERVICE_ANNOTATION_PRE_DESTROY", Service.PreDestroy.class);
+ checkField(toCheck, checked, fields, "SERVICE_ANNOTATION_POST_CONSTRUCT", Service.PostConstruct.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);
diff --git a/service/tests/registry/pom.xml b/service/tests/registry/pom.xml
index 50b807e0f8e..0a66188f428 100644
--- a/service/tests/registry/pom.xml
+++ b/service/tests/registry/pom.xml
@@ -39,7 +39,6 @@
io.helidon.service
helidon-service-registry
-
io.helidon.config
helidon-config
diff --git a/service/tests/registry/src/main/java/io/helidon/service/test/registry/TestLifecycle.java b/service/tests/registry/src/main/java/io/helidon/service/test/registry/TestLifecycle.java
new file mode 100644
index 00000000000..b9d05877736
--- /dev/null
+++ b/service/tests/registry/src/main/java/io/helidon/service/test/registry/TestLifecycle.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
+interface TestLifecycle {
+ int postConstructCalled();
+}
diff --git a/service/tests/registry/src/main/java/io/helidon/service/test/registry/TestLifecycleService.java b/service/tests/registry/src/main/java/io/helidon/service/test/registry/TestLifecycleService.java
new file mode 100644
index 00000000000..a03509b86f7
--- /dev/null
+++ b/service/tests/registry/src/main/java/io/helidon/service/test/registry/TestLifecycleService.java
@@ -0,0 +1,43 @@
+/*
+ * 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.concurrent.atomic.AtomicInteger;
+
+import io.helidon.service.registry.Service;
+
+@Service.Provider
+class TestLifecycleService implements TestLifecycle {
+ static final AtomicInteger PRE_DESTROY = new AtomicInteger();
+
+ private final AtomicInteger postConstruct = new AtomicInteger();
+
+ @Override
+ public int postConstructCalled() {
+ return postConstruct.get();
+ }
+
+ @Service.PostConstruct
+ void postConstruct() {
+ this.postConstruct.incrementAndGet();
+ }
+
+ @Service.PreDestroy
+ void preDestroy() {
+ PRE_DESTROY.incrementAndGet();
+ }
+}
diff --git a/service/tests/registry/src/test/java/io/helidon/service/test/registry/ServiceLifecycleTest.java b/service/tests/registry/src/test/java/io/helidon/service/test/registry/ServiceLifecycleTest.java
new file mode 100644
index 00000000000..219d5ea1f95
--- /dev/null
+++ b/service/tests/registry/src/test/java/io/helidon/service/test/registry/ServiceLifecycleTest.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.test.registry;
+
+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.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ServiceLifecycleTest {
+ 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();
+ assertThat("Pre destroy should be called on registry shutdown",
+ TestLifecycleService.PRE_DESTROY.get(), is(1));
+ }
+ registryManager = null;
+ registry = null;
+ }
+
+ @Test
+ void testServiceLifecycle() {
+ assertThat("Pre destroy should not be called", TestLifecycleService.PRE_DESTROY.get(), is(0));
+ TestLifecycle testLifecycle = registry.get(TestLifecycle.class);
+ assertThat("There should be 1 post-construct call on the instance", testLifecycle.postConstructCalled(), is(1));
+ assertThat("Pre destroy should not be called", TestLifecycleService.PRE_DESTROY.get(), is(0));
+ }
+}
diff --git a/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java b/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java
index 3592a504a35..eed2ed38863 100644
--- a/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java
+++ b/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.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.
@@ -22,6 +22,7 @@
import java.lang.System.Logger.Level;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
+import java.net.URI;
import java.net.URL;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -87,11 +88,11 @@ void stop() throws Exception {
}
URL getHealthUrl() throws MalformedURLException {
- return new URL("http://localhost:" + this.port + "/health");
+ return URI.create("http://localhost:" + this.port + "/health").toURL();
}
URL getBaseUrl() throws MalformedURLException {
- return new URL("http://localhost:" + this.port);
+ return URI.create("http://localhost:" + this.port).toURL();
}
void waitForApplicationDown() throws Exception {
@@ -210,7 +211,7 @@ private void runExitOnStartedTest(String edition) throws Exception {
Thread.sleep(500);
} while (System.currentTimeMillis() < maxTime);
- String eol = System.getProperty("line.separator");
+ String eol = System.lineSeparator();
Assertions.fail("quickstart " + edition + " did not exit as expected." + eol
+ eol
+ "stdOut: " + stdOut + eol
diff --git a/tests/integration/dbclient/pgsql/etc/docker/Dockerfile b/tests/integration/dbclient/pgsql/etc/docker/Dockerfile
index f0778bb6852..587335551ec 100644
--- a/tests/integration/dbclient/pgsql/etc/docker/Dockerfile
+++ b/tests/integration/dbclient/pgsql/etc/docker/Dockerfile
@@ -17,8 +17,9 @@
FROM oraclelinux:9-slim
RUN microdnf install postgresql-server && microdnf clean all
+RUN mkdir -p /var/run/postgresql && \
+ chown postgres:postgres /var/run/postgresql
ADD entrypoint.sh /usr/local/bin/
-
ENV PGDATA /var/lib/pgsql/data
USER postgres
ENTRYPOINT ["entrypoint.sh"]
diff --git a/webserver/observe/metrics/src/test/resources/application.yaml b/webserver/observe/metrics/src/test/resources/application.yaml
index 204c874771f..4e4e2cc8413 100644
--- a/webserver/observe/metrics/src/test/resources/application.yaml
+++ b/webserver/observe/metrics/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.
@@ -22,6 +22,6 @@ server:
scopes:
- name: vendor
filter:
- include: requests.l.*
+ include: requests\.l.*
key-performance-indicators:
extended: true
diff --git a/webserver/security/src/test/resources/security-application.yaml b/webserver/security/src/test/resources/security-application.yaml
index 504929aa6fe..f2ae0014db3 100644
--- a/webserver/security/src/test/resources/security-application.yaml
+++ b/webserver/security/src/test/resources/security-application.yaml
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2016, 2023 Oracle and/or its affiliates.
+# Copyright (c) 2016, 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.
@@ -47,6 +47,7 @@ server:
- path: "/auditOnly"
# method - any
# audit all methods (by default GET and HEAD are not audited)
+ methods: ["get"]
audit: true
audit-event-type: "unit_test"
audit-message-format: "Unit test message format"
diff --git a/webserver/websocket/src/main/java/io/helidon/webserver/websocket/WsRouting.java b/webserver/websocket/src/main/java/io/helidon/webserver/websocket/WsRouting.java
index f6ee7f1454e..52d94519c3f 100644
--- a/webserver/websocket/src/main/java/io/helidon/webserver/websocket/WsRouting.java
+++ b/webserver/websocket/src/main/java/io/helidon/webserver/websocket/WsRouting.java
@@ -52,7 +52,7 @@ public static Builder builder() {
}
/**
- * Emtpy WebSocket routing.
+ * Empty WebSocket routing.
*
* @return empty routing
*/