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/.mvn/extensions.xml b/.mvn/extensions.xml
index ba49f1b425a..ad9cb3a7b00 100644
--- a/.mvn/extensions.xml
+++ b/.mvn/extensions.xml
@@ -22,6 +22,6 @@
io.helidon.build-toolshelidon-build-cache-maven-extension
- 4.0.11
+ 4.0.12
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96ed779c24a..6487d5857df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,68 @@ For Helidon 2.x releases please see [Helidon 2.x CHANGELOG.md](https://github.co
For Helidon 1.x releases please see [Helidon 1.x CHANGELOG.md](https://github.com/oracle/helidon/blob/helidon-1.x/CHANGELOG.md)
+## [4.1.2]
+
+This release contains important bugfixes and enhancements and is recommended for all users of Helidon 4.
+
+A minimum of Java 21 is required to use Helidon 4.
+
+### CHANGES
+
+- gRPC: Adds support to iterate over URIs when connecting to a gRPC service [9300](https://github.com/helidon-io/helidon/pull/9300)
+- LRA: LRA testing feature [9320](https://github.com/helidon-io/helidon/pull/9320)
+- Logging: JSON Formatter for JUL [9301](https://github.com/helidon-io/helidon/pull/9301)
+- Security: Policy validator configurable per endpoint in config (#9248) [9308](https://github.com/helidon-io/helidon/pull/9308)
+- WebServer: Allows the webserver's write buffer size to be set to 0 [9314](https://github.com/helidon-io/helidon/pull/9314)
+- WebServer: Fix DataReader.findNewLine with lone EOL character [9327](https://github.com/helidon-io/helidon/pull/9327)
+- WebServer: Grouping Executors related methods into a single class [9298](https://github.com/helidon-io/helidon/pull/9298)
+- WebServer: New implementation for SSE in webserver [9297](https://github.com/helidon-io/helidon/pull/9297)
+- WebServer: Smart async writer in webserver [9292](https://github.com/helidon-io/helidon/pull/9292)
+- Dependencies: Upgrade Jersey to 3.1.8 [9303](https://github.com/helidon-io/helidon/pull/9303)
+- Dependencies: Upgrades protobuf to 3.25.5 [9299](https://github.com/helidon-io/helidon/pull/9299)
+- Dependencies: Uptake build-tools 4.0.12 (fixes [9305](https://github.com/helidon-io/helidon/issues/9305)) [9323](https://github.com/helidon-io/helidon/pull/9323)
+- Docs: Add emphasis on including an OTel exporter and configuring [9312](https://github.com/helidon-io/helidon/pull/9312)
+- Docs: Document work-around for maven archetype issue (#9316) [9324](https://github.com/helidon-io/helidon/pull/9324)
+- Tests: Fix DbClient PostgreSQL tests [9293](https://github.com/helidon-io/helidon/pull/9293)
+
+## [4.1.1]
+
+This release contains important bugfixes and enhancements and is recommended for all users of Helidon 4. It is compatible with Helidon 4.0.X.
+
+A minimum of Java 21 is required to use Helidon 4.
+
+### Notable Changes
+
+- Implement gRPC MP Client [9026](https://github.com/helidon-io/helidon/pull/9026)
+
+### CHANGES
+
+- CORS: Remove headers that do not affect CORS decision-making from request adapter logging output [9178](https://github.com/helidon-io/helidon/pull/9178)
+- Codegen: Add support for additional modifiers [9201](https://github.com/helidon-io/helidon/pull/9201)
+- Codegen: Fix generation of annotations, including lists, nested annotations etc. [9182](https://github.com/helidon-io/helidon/pull/9182)
+- Codegen: Handling enum and type values in a consistent way in code generation. [9167](https://github.com/helidon-io/helidon/pull/9167)
+- Codegen: Support for validation of Duration and URI default values. [9166](https://github.com/helidon-io/helidon/pull/9166)
+- Codegen: Udpates to types and annotation processing [9168](https://github.com/helidon-io/helidon/pull/9168)
+- Config: Replace manual casts on pattern with instanceof in HoconConfigParser [9209](https://github.com/helidon-io/helidon/pull/9209)
+- LRA: Replace deprecated method Scheduling.fixedRateBuilder() [9098](https://github.com/helidon-io/helidon/pull/9098)
+- Security: Required authorization propagated from the class level now [9137](https://github.com/helidon-io/helidon/pull/9137)
+- Tracing: Allow users to direct Helidon to use an existing global `OpenTelemetry` instance rather than create its own [9205](https://github.com/helidon-io/helidon/pull/9205)
+- WebServer: Allows the creation of empty SSE events [9207](https://github.com/helidon-io/helidon/pull/9207)
+- WebServer: Increases default value of write-buffer-size to 4K [9190](https://github.com/helidon-io/helidon/pull/9190)
+- WebServer: UncheckedIOException no longer a special case [9206](https://github.com/helidon-io/helidon/pull/9206)
+- gRPC: Downgrades version of protobuf for backwards compatibility [9162](https://github.com/helidon-io/helidon/pull/9162)
+- gRPC: Implements support for client gRPC channel injections [9155](https://github.com/helidon-io/helidon/pull/9155)
+- gRPC: Implements the gRPC MP Client API [9026](https://github.com/helidon-io/helidon/pull/9026)
+- gRPC: Renames package-private Grpc type to GrpcRouteHandler [9173](https://github.com/helidon-io/helidon/pull/9173)
+- Build: Fix nightly script [9221](https://github.com/helidon-io/helidon/pull/9221)
+- Build: Update release workflows [9210](https://github.com/helidon-io/helidon/pull/9210)
+- Dependencies: Upgrade microprofile-cdi-tck to 4.0.13 [9141](https://github.com/helidon-io/helidon/pull/9141)
+- Dependencies: Upgrade oci sdk to 3.46.1 [9179](https://github.com/helidon-io/helidon/pull/9179)
+- Dependencies: Upgrade slf4j to 2.0.16 [9143](https://github.com/helidon-io/helidon/pull/9143)
+- Deprecation: deprecate old injection integration for oci [9184](https://github.com/helidon-io/helidon/pull/9184)
+- Docs: Clarify description of config profiles [9188](https://github.com/helidon-io/helidon/pull/9188)
+- Docs: Documents gRPC MP Client API [9150](https://github.com/helidon-io/helidon/pull/9150)
+- Tests: Builder tests that confidential options are not printed in toString() [9154](https://github.com/helidon-io/helidon/pull/9154)
## [4.1.0]
@@ -1397,6 +1459,8 @@ Helidon 4.0.0 is a major release that includes significant new features and fixe
- MicroProfile: MP path based static content should use index.html (4.x) [4737](https://github.com/oracle/helidon/pull/4737)
- Build: 4.0 version and poms [4655](https://github.com/oracle/helidon/pull/4655)
+[4.1.2]: https://github.com/oracle/helidon/compare/4.1.1...4.1.2
+[4.1.1]: https://github.com/oracle/helidon/compare/4.1.0...4.1.1
[4.1.0]: https://github.com/oracle/helidon/compare/4.0.11...4.1.0
[4.0.11]: https://github.com/oracle/helidon/compare/4.0.10...4.0.11
[4.0.10]: https://github.com/oracle/helidon/compare/4.0.9...4.0.10
diff --git a/all/pom.xml b/all/pom.xml
index 0abd9ea96f9..009c6cc6f34 100644
--- a/all/pom.xml
+++ b/all/pom.xml
@@ -22,7 +22,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomhelidon-all
@@ -463,6 +463,10 @@
io.helidon.common.featureshelidon-common-features
+
+ io.helidon.common.concurrency
+ helidon-common-concurrency-limits
+ io.helidon.dbclienthelidon-dbclient
@@ -996,6 +1000,10 @@
io.helidon.webserverhelidon-webserver-service-common
+
+ io.helidon.webserver
+ helidon-webserver-concurrency-limits
+ io.helidon.webserver.testing.junit5helidon-webserver-testing-junit5
diff --git a/applications/mp/pom.xml b/applications/mp/pom.xml
index 388b5eda632..6be5ed09bce 100644
--- a/applications/mp/pom.xml
+++ b/applications/mp/pom.xml
@@ -23,7 +23,7 @@
io.helidon.applicationshelidon-applications
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../parent/pom.xmlhelidon-mp
diff --git a/applications/parent/pom.xml b/applications/parent/pom.xml
index 4d7b6975146..a6d26ad323b 100644
--- a/applications/parent/pom.xml
+++ b/applications/parent/pom.xml
@@ -23,7 +23,7 @@
io.helidon.applicationshelidon-applications-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.applicationshelidon-applications
@@ -41,8 +41,8 @@
3.6.03.1.03.1.2
- 4.0.11
- 4.0.11
+ 4.0.14
+ 4.0.143.3.00.10.21.5.0.Final
diff --git a/applications/pom.xml b/applications/pom.xml
index e8a58ec9eb1..14ab1cc7a14 100644
--- a/applications/pom.xml
+++ b/applications/pom.xml
@@ -23,7 +23,7 @@
io.helidonhelidon-dependencies
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../dependencies/pom.xmlio.helidon.applications
diff --git a/applications/se/pom.xml b/applications/se/pom.xml
index e3fc3792428..4275245bb9f 100644
--- a/applications/se/pom.xml
+++ b/applications/se/pom.xml
@@ -23,7 +23,7 @@
io.helidon.applicationshelidon-applications
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../parent/pom.xmlhelidon-se
diff --git a/archetypes/archetypes/pom.xml b/archetypes/archetypes/pom.xml
index 99d392d90e2..c0e7f49f3f3 100644
--- a/archetypes/archetypes/pom.xml
+++ b/archetypes/archetypes/pom.xml
@@ -23,7 +23,7 @@
io.helidon.archetypeshelidon-archetypes-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-archetypehelidon-archetypes
diff --git a/archetypes/legacy/bare-mp/pom.xml b/archetypes/legacy/bare-mp/pom.xml
index 76e5feb9ab0..6d18160010f 100644
--- a/archetypes/legacy/bare-mp/pom.xml
+++ b/archetypes/legacy/bare-mp/pom.xml
@@ -23,7 +23,7 @@
io.helidon.archetypeshelidon-archetypes-legacy-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-bare-mphelidon-archetype
diff --git a/archetypes/legacy/bare-se/pom.xml b/archetypes/legacy/bare-se/pom.xml
index 4163e47a536..002297b631c 100644
--- a/archetypes/legacy/bare-se/pom.xml
+++ b/archetypes/legacy/bare-se/pom.xml
@@ -23,7 +23,7 @@
io.helidon.archetypeshelidon-archetypes-legacy-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-bare-sehelidon-archetype
diff --git a/archetypes/legacy/database-mp/pom.xml b/archetypes/legacy/database-mp/pom.xml
index f85c680b66c..db52c7cfe81 100644
--- a/archetypes/legacy/database-mp/pom.xml
+++ b/archetypes/legacy/database-mp/pom.xml
@@ -23,7 +23,7 @@
io.helidon.archetypeshelidon-archetypes-legacy-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-database-mphelidon-archetype
diff --git a/archetypes/legacy/database-se/pom.xml b/archetypes/legacy/database-se/pom.xml
index 633636704a5..0de30d63774 100644
--- a/archetypes/legacy/database-se/pom.xml
+++ b/archetypes/legacy/database-se/pom.xml
@@ -23,7 +23,7 @@
io.helidon.archetypeshelidon-archetypes-legacy-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-database-sehelidon-archetype
diff --git a/archetypes/legacy/pom.xml b/archetypes/legacy/pom.xml
index 0248323c497..09e637e1702 100644
--- a/archetypes/legacy/pom.xml
+++ b/archetypes/legacy/pom.xml
@@ -23,7 +23,7 @@
io.helidon.archetypeshelidon-archetypes-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-archetypes-legacy-projectHelidon Legacy Archetypes
diff --git a/archetypes/legacy/quickstart-mp/pom.xml b/archetypes/legacy/quickstart-mp/pom.xml
index f0b936918a4..e4168bd6517 100644
--- a/archetypes/legacy/quickstart-mp/pom.xml
+++ b/archetypes/legacy/quickstart-mp/pom.xml
@@ -23,7 +23,7 @@
io.helidon.archetypeshelidon-archetypes-legacy-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-quickstart-mphelidon-archetype
diff --git a/archetypes/legacy/quickstart-se/pom.xml b/archetypes/legacy/quickstart-se/pom.xml
index 8c7fe0d7398..8dcbf902502 100644
--- a/archetypes/legacy/quickstart-se/pom.xml
+++ b/archetypes/legacy/quickstart-se/pom.xml
@@ -23,7 +23,7 @@
io.helidon.archetypeshelidon-archetypes-legacy-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-quickstart-sehelidon-archetype
diff --git a/archetypes/pom.xml b/archetypes/pom.xml
index 1006c562d8a..b840db697bc 100644
--- a/archetypes/pom.xml
+++ b/archetypes/pom.xml
@@ -23,7 +23,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.archetypeshelidon-archetypes-project
diff --git a/bom/pom.xml b/bom/pom.xml
index d6b6d76825a..8b6af76b14d 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -23,7 +23,7 @@
io.helidonhelidon-parent
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../parent/pom.xmlhelidon-bom
@@ -31,7 +31,7 @@
Helidon BOM POM
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT
@@ -610,6 +610,11 @@
helidon-common-features${helidon.version}
+
+ io.helidon.common.concurrency
+ helidon-common-concurrency-limits
+ ${helidon.version}
+
@@ -781,6 +786,16 @@
helidon-lra-coordinator-narayana-client${helidon.version}
+
+ io.helidon.lra
+ helidon-lra-coordinator-server
+ ${helidon.version}
+
+
+ io.helidon.microprofile.lra
+ helidon-microprofile-lra-testing
+ ${helidon.version}
+
@@ -1303,6 +1318,11 @@
helidon-webserver-service-common${helidon.version}
+
+ io.helidon.webserver
+ helidon-webserver-concurrency-limits
+ ${helidon.version}
+ io.helidon.webserver.testing.junit5helidon-webserver-testing-junit5
diff --git a/builder/api/pom.xml b/builder/api/pom.xml
index b10f24bb656..7e6a6158597 100644
--- a/builder/api/pom.xml
+++ b/builder/api/pom.xml
@@ -24,7 +24,7 @@
io.helidon.builderhelidon-builder-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/builder/codegen/pom.xml b/builder/codegen/pom.xml
index a196148d27d..164695c1b40 100644
--- a/builder/codegen/pom.xml
+++ b/builder/codegen/pom.xml
@@ -23,7 +23,7 @@
io.helidon.builderhelidon-builder-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
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/pom.xml b/builder/pom.xml
index 799ea2c7eea..6362a060faa 100644
--- a/builder/pom.xml
+++ b/builder/pom.xml
@@ -25,7 +25,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/builder/processor/pom.xml b/builder/processor/pom.xml
index 1e7b5629d1a..9bfc0949cc1 100644
--- a/builder/processor/pom.xml
+++ b/builder/processor/pom.xml
@@ -23,7 +23,7 @@
io.helidon.builderhelidon-builder-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/builder/tests/builder/pom.xml b/builder/tests/builder/pom.xml
index 1f8179ba8cd..e60957cd550 100644
--- a/builder/tests/builder/pom.xml
+++ b/builder/tests/builder/pom.xml
@@ -23,7 +23,7 @@
io.helidon.builder.testshelidon-builder-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/builder/tests/codegen/pom.xml b/builder/tests/codegen/pom.xml
index 6ffb4e0b0f8..56aae2c3df5 100644
--- a/builder/tests/codegen/pom.xml
+++ b/builder/tests/codegen/pom.xml
@@ -23,7 +23,7 @@
io.helidon.builder.testshelidon-builder-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/builder/tests/common-types/pom.xml b/builder/tests/common-types/pom.xml
index 5e4303ce1ea..f5613f14b5b 100644
--- a/builder/tests/common-types/pom.xml
+++ b/builder/tests/common-types/pom.xml
@@ -23,7 +23,7 @@
io.helidon.builder.testshelidon-builder-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
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
+
+ io.micrometer
+ micrometer-registry-prometheus-simpleclient
+ ${version.lib.micrometer-prometheus}
+
diff --git a/docs/pom.xml b/docs/pom.xml
index 1c753de4850..2c20ea103ef 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -23,7 +23,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-docssite
@@ -86,6 +86,21 @@
postgresqltrue
+
+ io.helidon.microprofile.lra
+ helidon-microprofile-lra-testing
+ true
+
+
+ org.hamcrest
+ hamcrest-all
+ true
+
+
+ io.helidon.microprofile.testing
+ helidon-microprofile-testing-junit5
+ true
+
diff --git a/docs/src/main/asciidoc/includes/attributes.adoc b/docs/src/main/asciidoc/includes/attributes.adoc
index e1c80e22389..9cf1261c1b7 100644
--- a/docs/src/main/asciidoc/includes/attributes.adoc
+++ b/docs/src/main/asciidoc/includes/attributes.adoc
@@ -23,7 +23,7 @@ ifndef::attributes-included[]
// functional attributes
:imagesdir: {rootdir}/images
-:helidon-version: 4.1.0-SNAPSHOT
+:helidon-version: 4.2.0-SNAPSHOT
:helidon-version-is-release: false
:sourcedir: {rootdir}/../java/io/helidon/docs
diff --git a/docs/src/main/asciidoc/includes/security/providers/abac.adoc b/docs/src/main/asciidoc/includes/security/providers/abac.adoc
index 5a1f0820096..1909d7dc145 100644
--- a/docs/src/main/asciidoc/includes/security/providers/abac.adoc
+++ b/docs/src/main/asciidoc/includes/security/providers/abac.adoc
@@ -196,3 +196,15 @@ security:
----
include::{sourcedir}/includes/security/providers/AbacSnippets.java[tag=snippet_4, indent=0]
----
+
+[source,yaml]
+.Configuration example for `JAX-RS` over the configuration
+----
+server:
+ features:
+ security:
+ endpoints:
+ - path: "/somePath"
+ config:
+ abac.policy-validator.statement: "${env.time.year >= 2017}"
+----
\ No newline at end of file
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/docs/src/main/asciidoc/mp/guides/quickstart.adoc b/docs/src/main/asciidoc/mp/guides/quickstart.adoc
index 51a93d0a212..8c13e2714b1 100644
--- a/docs/src/main/asciidoc/mp/guides/quickstart.adoc
+++ b/docs/src/main/asciidoc/mp/guides/quickstart.adoc
@@ -49,6 +49,11 @@ mvn -U archetype:generate -DinteractiveMode=false \
-Dpackage=io.helidon.examples.quickstart.mp
----
+TIP: If you are using an earlier version of Helidon and the above command
+fails with `java.lang.NoSuchMethodError` then you can work-around the error
+by replacing `archetype:generate` with `org.apache.maven.plugins:maven-archetype-plugin:3.2.1:generate`.
+Or use the helidon CLI.
+
[source,bash]
.Or alternatively run the helidon CLI
----
diff --git a/docs/src/main/asciidoc/mp/jaxrs/helidon-connector.adoc b/docs/src/main/asciidoc/mp/jaxrs/helidon-connector.adoc
index cab34bcd99f..ab5b695cfcb 100644
--- a/docs/src/main/asciidoc/mp/jaxrs/helidon-connector.adoc
+++ b/docs/src/main/asciidoc/mp/jaxrs/helidon-connector.adoc
@@ -112,6 +112,11 @@ properties supported by the connector, their types, scopes and default values.
|client, invocation
|true
+|jersey.config.client.request.expect.100.continue.processing
+|`Boolean`
+|client
+|true
+
|jersey.connector.helidon.config
|`io.helidon.config.Config`
|client
diff --git a/docs/src/main/asciidoc/mp/lra.adoc b/docs/src/main/asciidoc/mp/lra.adoc
index 96a186976bd..1fcdc199d3d 100644
--- a/docs/src/main/asciidoc/mp/lra.adoc
+++ b/docs/src/main/asciidoc/mp/lra.adoc
@@ -24,6 +24,7 @@
:spec-version: 1.0-RC3
:spec-name: MicroProfile {feature-name} specification
:javadoc-link: https://download.eclipse.org/microprofile/microprofile-lra-{spec-version}/apidocs/org/eclipse/microprofile/lra/annotation/
+:microtx-link: https://docs.oracle.com/en/database/oracle/transaction-manager-for-microservices/index.html
:rootdir: {docdir}/..
include::{rootdir}/includes/mp.adoc[]
@@ -36,8 +37,10 @@ include::{rootdir}/includes/mp.adoc[]
* <>
* <>
* <>
+* <>
* <>
** <>
+** <>
** <>
** <>
* <>
@@ -351,6 +354,73 @@ include::{sourcedir}/mp/LraSnippets.java[tag=snippet_13, indent=0]
<5> Method which will be called by coordinator when LRA is completed
<6> Method which will be called by coordinator when LRA is canceled
+== Testing
+Testing of JAX-RS resources with LRA can be challenging as LRA participant running in parallel with the test is needed.
+
+Helidon provides test coordinator which can be started automatically with additional socket on a random port within your
+own Helidon application. You only need one extra test dependency to enable test coordinator in your xref:testing/testing.adoc[@HelidonTest].
+
+[source, xml]
+.Dependency
+----
+
+ io.helidon.microprofile.lra
+ helidon-microprofile-lra-testing
+ test
+
+----
+
+Considering that you have LRA enabled JAX-RS resource you want to test.
+
+[source, java]
+.Example JAX-RS resource with LRA.
+----
+include::{sourcedir}/mp/LraSnippets.java[tag=snippet_14, indent=0]
+----
+
+Helidon test with enabled CDI discovery can look like this.
+
+[source, java]
+.HelidonTest with LRA test support.
+----
+include::{sourcedir}/mp/LraSnippets.java[tag=snippet_15, indent=0]
+----
+<1> Resource is discovered automatically
+<2> Test coordinator needs to be added manually
+<3> Injecting test coordinator to access state of LRA managed by coordinator mid-test
+<4> Retrieving LRA managed by coordinator by LraId
+<5> Asserting LRA state in coordinator
+
+LRA testing feature has the following default configuration:
+
+* port: `0` - coordinator is started on random port(Helidon LRA participant is capable to discover test coordinator automatically)
+* bind-address: `localhost` - bind address of the coordinator
+* helidon.lra.coordinator.persistence: `false` - LRAs managed by test coordinator are not persisted
+* helidon.lra.participant.use-build-time-index: `false` - Participant annotation inspection ignores Jandex index files created in build time, it helps to avoid issues with additional test resources
+
+Testing LRA coordinator is started on additional named socket `test-lra-coordinator` configured with default index `500`.
+Default index can be changed with system property `helidon.lra.coordinator.test-socket.index`.
+
+Example: `-Dhelidon.lra.coordinator.test-socket.index=20`.
+
+[source, java]
+.HelidonTest override LRA test feature default settings.
+----
+include::{sourcedir}/mp/LraSnippets.java[tag=snippet_16, indent=0]
+----
+<1> Start test LRA coordinator always on the same port 8070(default is random port)
+<2> Test LRA coordinator socket bind address (default is localhost)
+<3> Persist LRA managed by coordinator(default is false)
+<4> Use build time Jandex index(default is false)
+
+When CDI bean auto-discovery is not desired, LRA and Config CDI extensions needs to be added manually.
+
+[source, java]
+.HelidonTest setup with disabled discovery.
+----
+include::{sourcedir}/mp/LraSnippets.java[tag=snippet_17, indent=0]
+----
+
== Additional Information
=== Coordinator
@@ -359,12 +429,65 @@ the participants when the LRA transaction gets cancelled or completes (in case i
In addition, participant also keeps track of timeouts, retries participant calls, and assigns LRA ids.
.Helidon LRA supports following coordinators
+* {microtx-link}[MicroTx LRA coordinator]
* Helidon LRA coordinator
* https://narayana.io/lra[Narayana coordinator].
+=== MicroTx LRA Coordinator
+Oracle Transaction Manager for Microservices - {microtx-link}[MicroTx] is an enterprise grade transaction manager for microservices,
+among other it manages LRA transactions and is compatible with Narayana LRA clients.
+
+MicroTx LRA coordinator is compatible with Narayana clients when `narayanaLraCompatibilityMode` is on,
+you need to add another dependency to enable Narayana client:
+[source,xml]
+.Dependency needed for using Helidon LRA with Narayana compatible coordinator
+----
+
+ io.helidon.lra
+ helidon-lra-coordinator-narayana-client
+
+----
+
+[source, bash]
+.Run MicroTx in Docker
+----
+docker container run --name otmm -v "$(pwd)":/app/config \
+-w /app/config -p 8080:8080/tcp --env CONFIG_FILE=tcs.yaml \
+--add-host host.docker.internal:host-gateway -d tmm:
+----
+
+To use MicroTx with Helidon LRA participant, `narayanaLraCompatibilityMode` needs to be enabled.
+
+[source, yaml]
+.Configure MicroTx for development
+----
+tmmAppName: tcs
+tmmConfiguration:
+ listenAddr: 0.0.0.0:8080
+ internalAddr: http://host.docker.internal:8080
+ externalUrl: http://lra-coordinator.acme.com:8080
+ xaCoordinator:
+ enabled: false
+ lraCoordinator:
+ enabled: true
+ tccCoordinator:
+ enabled: false
+ storage:
+ type: memory
+ authentication:
+ enabled: false
+ authorization:
+ enabled: false
+ serveTLS:
+ enabled: false
+ narayanaLraCompatibilityMode:
+ enabled: true #<1>
+----
+<1> Enable Narayana compatibility mode
+
=== Helidon LRA Coordinator
-CAUTION: Experimental tool, usage in production is not advised.
+CAUTION: Test tool, usage in production is not advised.
[source,bash]
.Build and run Helidon LRA coordinator
@@ -417,3 +540,4 @@ with port `8070` defined in the snippet above you need to configure your Helidon
* {microprofile-lra-spec-url}[{spec-name}]
* https://download.eclipse.org/microprofile/microprofile-lra-{spec-version}/apidocs/org/eclipse/microprofile/lra/[Microprofile LRA JavaDoc]
* https://helidon.io/docs/v4/apidocs/io.helidon.lra.coordinator.client/module-summary.html[Helidon LRA Client JavaDoc]
+* {microtx-link}[MicroTx - Oracle Transaction Manager for Microservices]
diff --git a/docs/src/main/asciidoc/mp/persistence.adoc b/docs/src/main/asciidoc/mp/persistence.adoc
index 86e9df1c7fa..1459974ac73 100644
--- a/docs/src/main/asciidoc/mp/persistence.adoc
+++ b/docs/src/main/asciidoc/mp/persistence.adoc
@@ -327,7 +327,7 @@ the `test` data source's associated connection pool or vendor-specific
[source,properties]
----
-javax.sql.DataSource.test.foo.bar=baz # <1> <2> <3>
+javax.sql.DataSource.test.foo.bar=baz# <1><2><3>
----
<1> The *_objecttype_* portion of the configuration property name is
`javax.sql.DataSource`.
@@ -434,8 +434,8 @@ a service name of `XE`, a `user` of `scott`, and a `password` of
[source,properties]
----
-javax.sql.DataSource.main.connectionFactoryClassName = oracle.jdbc.pool.OracleDataSource # <1>
-javax.sql.DataSource.main.url = jdbc:oracle:thin://@localhost:1521/XE # <2>
+javax.sql.DataSource.main.connectionFactoryClassName = oracle.jdbc.pool.OracleDataSource# <1>
+javax.sql.DataSource.main.URL = jdbc:oracle:thin:@//localhost:1521/XE# <2>
javax.sql.DataSource.main.user = scott
javax.sql.DataSource.main.password = tiger
----
@@ -488,8 +488,8 @@ password:
[source,properties]
----
-javax.sql.DataSource.test.dataSourceClassName = org.h2.jdbcx.JdbcDataSource # <1>
-javax.sql.DataSource.test.dataSource.url = jdbc:h2:mem:unit-testing;DB_CLOSE_DELAY=-1 # <2> <3>
+javax.sql.DataSource.test.dataSourceClassName = org.h2.jdbcx.JdbcDataSource# <1>
+javax.sql.DataSource.test.dataSource.url = jdbc:h2:mem:unit-testing;DB_CLOSE_DELAY=-1# <2><3>
javax.sql.DataSource.test.dataSource.user = sa
javax.sql.DataSource.test.dataSource.password =
----
diff --git a/docs/src/main/asciidoc/mp/telemetry.adoc b/docs/src/main/asciidoc/mp/telemetry.adoc
index 7fecd1c7349..7251e88c06e 100644
--- a/docs/src/main/asciidoc/mp/telemetry.adoc
+++ b/docs/src/main/asciidoc/mp/telemetry.adoc
@@ -48,6 +48,28 @@ include::{rootdir}/includes/dependencies.adoc[]
----
+[[otel-exporter-dependencies]]
+Also, add a dependency on an OpenTelemetry exporter.
+[source,xml]
+.Example dependency for the OpenTelemetry OTLP exporter
+----
+
+ io.opentelemetry
+ opentelemetry-exporter-otlp
+
+----
+[source,xml]
+.Example dependency for the OpenTelemetry Jaeger exporter
+----
+
+ io.opentelemetry
+ opentelemetry-exporter-jaeger
+
+----
+
+Typical applications use a single exporter but you can add dependencies on multiple exporters and then use configuration to choose which to use in any given execution.
+See the <> section for more details.
+
== Usage
link:https://opentelemetry.io/[OpenTelemetry] comprises a collection of APIs, SDKs, integration tools, and other software components intended to facilitate the generation and control of telemetry data, including traces, metrics, and logs. In an environment where distributed tracing is enabled via OpenTelemetry (which combines OpenTracing and OpenCensus), this specification establishes the necessary behaviors for MicroProfile applications to participate seamlessly.
@@ -61,7 +83,7 @@ MicroProfile Telemetry {mp-telemetry-version} allows for the exportation of the
If possible, assign the following config setting in your application's `META-INF/microprofile-config.properties` file:
[source,properties]
----
-mp.telemetry.span.name-includes-method = true
+telemetry.span.name-includes-method = true
----
Earlier releases of Helidon 4 implemented MicroProfile Telemetry 1.0 which was based on OpenTelemetry semantic conventions 1.22.0-alpha.
@@ -193,8 +215,10 @@ To configure OpenTelemetry, MicroProfile Config must be used, and the configurat
Please consult with the links above for all configurations' properties usage.
-The property should be declared in `microprofile-config.properties` file to be processed correctly.
-
+For your application to report trace information be sure you add a dependency on an OpenTelemetry exporter as <> and, as needed, configure its use.
+By default OpenTelemetry attempts to use the OTLP exporter so you do not need to add configuration to specify that choice.
+To use a different exporter set `otel.traces.exporter` in your configuration to the appropriate value: `jaeger`, `zipkin`, `prometheus`, etc.
+See the <> section below.
=== OpenTelemetry Java Agent
@@ -204,6 +228,7 @@ The OpenTelemetry Java Agent may influence the work of MicroProfile Telemetry, o
This way, Helidon will explicitly get all the configuration and objects from the Agent, thus allowing correct span hierarchy settings.
+[[examples]]
== Examples
This guide demonstrates how to incorporate MicroProfile Telemetry into Helidon and provides illustrations of how to view traces. Jaeger is employed in all the examples, and the Jaeger UI is used to view the traces.
diff --git a/docs/src/main/asciidoc/se/guides/quickstart.adoc b/docs/src/main/asciidoc/se/guides/quickstart.adoc
index 9bf0607ac2f..1a0ec936b8e 100644
--- a/docs/src/main/asciidoc/se/guides/quickstart.adoc
+++ b/docs/src/main/asciidoc/se/guides/quickstart.adoc
@@ -49,6 +49,11 @@ mvn -U archetype:generate -DinteractiveMode=false \
-Dpackage=io.helidon.examples.quickstart.se
----
+TIP: If you are using an earlier version of Helidon and the above command
+fails with `java.lang.NoSuchMethodError` then you can work-around the error
+by replacing `archetype:generate` with `org.apache.maven.plugins:maven-archetype-plugin:3.2.1:generate`.
+Or use the helidon CLI.
+
[source,bash]
.Or alternatively run the helidon CLI
----
diff --git a/docs/src/main/java/io/helidon/docs/mp/LraSnippets.java b/docs/src/main/java/io/helidon/docs/mp/LraSnippets.java
index e859b9c4c55..e643d0d9aae 100644
--- a/docs/src/main/java/io/helidon/docs/mp/LraSnippets.java
+++ b/docs/src/main/java/io/helidon/docs/mp/LraSnippets.java
@@ -17,14 +17,33 @@
import java.net.URI;
import java.time.temporal.ChronoUnit;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
+import io.helidon.lra.coordinator.Lra;
+import io.helidon.microprofile.config.ConfigCdiExtension;
+import io.helidon.microprofile.lra.LraCdiExtension;
+import io.helidon.microprofile.testing.junit5.AddBean;
+import io.helidon.microprofile.testing.junit5.AddConfig;
+import io.helidon.microprofile.testing.junit5.AddExtension;
+import io.helidon.microprofile.testing.junit5.AddJaxRs;
+import io.helidon.microprofile.testing.junit5.DisableDiscovery;
+import io.helidon.microprofile.testing.junit5.HelidonTest;
+import io.helidon.microprofile.testing.lra.TestLraCoordinator;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.lra.LRAResponse;
import org.eclipse.microprofile.lra.annotation.AfterLRA;
@@ -36,10 +55,15 @@
import org.eclipse.microprofile.lra.annotation.Status;
import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
import org.eclipse.microprofile.lra.annotation.ws.rs.Leave;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER;
import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_ENDED_CONTEXT_HEADER;
import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_PARENT_CONTEXT_HEADER;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.is;
@SuppressWarnings("ALL")
class LraSnippets {
@@ -218,4 +242,91 @@ public Response compensateExample(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraI
// end::snippet_13[]
}
+ // tag::snippet_14[]
+ @ApplicationScoped
+ @Path("/test")
+ public class WithdrawResource {
+
+ private final List completedLras = new CopyOnWriteArrayList<>();
+ private final List cancelledLras = new CopyOnWriteArrayList<>();
+
+ @PUT
+ @Path("/withdraw")
+ @LRA(LRA.Type.REQUIRES_NEW)
+ public Response withdraw(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) Optional lraId, String content) {
+ if ("BOOM".equals(content)) {
+ throw new IllegalArgumentException("BOOM");
+ }
+ return Response.ok().build();
+ }
+
+ @Complete
+ public void complete(URI lraId) {
+ completedLras.add(lraId.toString());
+ }
+
+ @Compensate
+ public void rollback(URI lraId) {
+ cancelledLras.add(lraId.toString());
+ }
+
+ public List getCompletedLras() {
+ return completedLras;
+ }
+ }
+ // end::snippet_14[]
+
+ // tag::snippet_15[]
+ @HelidonTest
+ //@AddBean(WithdrawResource.class) //<1>
+ @AddBean(TestLraCoordinator.class) //<2>
+ public class LraTest {
+
+ @Inject
+ private WithdrawResource withdrawTestResource;
+
+ @Inject
+ private TestLraCoordinator coordinator; //<3>
+
+ @Inject
+ private WebTarget target;
+
+ @Test
+ public void testLraComplete() {
+ try (Response res = target
+ .path("/test/withdraw")
+ .request()
+ .put(Entity.entity("test", MediaType.TEXT_PLAIN_TYPE))) {
+ assertThat(res.getStatus(), is(200));
+ String lraId = res.getHeaderString(LRA.LRA_HTTP_CONTEXT_HEADER);
+ Lra lra = coordinator.lra(lraId); //<4>
+ assertThat(lra.status(), is(LRAStatus.Closed)); //<5>
+ assertThat(withdrawTestResource.getCompletedLras(), contains(lraId));
+ }
+ }
+ }
+ // end::snippet_15[]
+
+ // tag::snippet_16[]
+ @HelidonTest
+ @AddBean(TestLraCoordinator.class)
+ @AddConfig(key = "server.sockets.500.port", value = "8070") //<1>
+ @AddConfig(key = "server.sockets.500.bind-address", value = "custom.bind.name") //<2>
+ @AddConfig(key = "helidon.lra.coordinator.persistence", value = "true") //<3>
+ @AddConfig(key = "helidon.lra.participant.use-build-time-index", value = "true") //<4>
+ public class LraCustomConfigTest {
+ }
+ // end::snippet_16[]
+
+ // tag::snippet_17[]
+ @HelidonTest
+ @DisableDiscovery
+ @AddJaxRs
+ @AddBean(TestLraCoordinator.class)
+ @AddExtension(LraCdiExtension.class)
+ @AddExtension(ConfigCdiExtension.class)
+ @AddBean(WithdrawResource.class)
+ public class LraNoDiscoveryTest {
+ }
+ // end::snippet_17[]
}
diff --git a/etc/dependency-check-suppression.xml b/etc/dependency-check-suppression.xml
index 87c129b8a5e..2b0ff882133 100644
--- a/etc/dependency-check-suppression.xml
+++ b/etc/dependency-check-suppression.xml
@@ -2,6 +2,22 @@
+
+
+
+
+ ^pkg:maven/org\.glassfish.*/(jakarta\.el|jaxb-core|jaxb-runtime|osgi-resource-locator|txw2)@.*$
+ CVE-2024-9329
+
+
@@ -174,4 +190,22 @@
cpe:/a:mysql:mysql
+
+
+
+ ^pkg:maven/io\.micrometer/micrometer-registry-prometheus-simpleclient@.*$
+ CVE-2019-3826
+
+
+
+ ^pkg:maven/io\.prometheus/prometheus-metrics-(.*)@.*$
+ CVE-2019-3826
+
+
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/fault-tolerance/fault-tolerance/pom.xml b/fault-tolerance/fault-tolerance/pom.xml
index 39e8f42b20e..2a88f703817 100644
--- a/fault-tolerance/fault-tolerance/pom.xml
+++ b/fault-tolerance/fault-tolerance/pom.xml
@@ -22,7 +22,7 @@
io.helidon.fault-tolerancehelidon-fault-tolerance-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-fault-tolerance
diff --git a/fault-tolerance/pom.xml b/fault-tolerance/pom.xml
index a3b5e4cbcf3..710b1866fd0 100644
--- a/fault-tolerance/pom.xml
+++ b/fault-tolerance/pom.xml
@@ -22,7 +22,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.fault-tolerancehelidon-fault-tolerance-project
diff --git a/graphql/pom.xml b/graphql/pom.xml
index a39dec3a30d..8e793d44d0a 100644
--- a/graphql/pom.xml
+++ b/graphql/pom.xml
@@ -26,7 +26,7 @@
helidon-projectio.helidon
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.graphql
diff --git a/graphql/server/pom.xml b/graphql/server/pom.xml
index d2eb6d9dda4..c757f204af7 100644
--- a/graphql/server/pom.xml
+++ b/graphql/server/pom.xml
@@ -25,7 +25,7 @@
io.helidon.graphqlhelidon-graphql-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-graphql-server
diff --git a/grpc/api/pom.xml b/grpc/api/pom.xml
index 25e8f98f9fc..825aa07dbe9 100644
--- a/grpc/api/pom.xml
+++ b/grpc/api/pom.xml
@@ -21,7 +21,7 @@
helidon-grpc-projectio.helidon.grpc
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/grpc/core/pom.xml b/grpc/core/pom.xml
index 072d008c7db..81b22cd6aae 100644
--- a/grpc/core/pom.xml
+++ b/grpc/core/pom.xml
@@ -21,7 +21,7 @@
helidon-grpc-projectio.helidon.grpc
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/grpc/pom.xml b/grpc/pom.xml
index 2729c49e310..074362c8969 100644
--- a/grpc/pom.xml
+++ b/grpc/pom.xml
@@ -22,7 +22,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpom
diff --git a/health/health-checks/pom.xml b/health/health-checks/pom.xml
index 6d03d787962..d88af8adf13 100644
--- a/health/health-checks/pom.xml
+++ b/health/health-checks/pom.xml
@@ -22,7 +22,7 @@
io.helidon.healthhelidon-health-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-health-checks
diff --git a/health/health/pom.xml b/health/health/pom.xml
index 8dd1497bcd1..c5a2c9b6219 100644
--- a/health/health/pom.xml
+++ b/health/health/pom.xml
@@ -21,7 +21,7 @@
helidon-health-projectio.helidon.health
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/health/pom.xml b/health/pom.xml
index abcddd58648..fd06ce069f6 100644
--- a/health/pom.xml
+++ b/health/pom.xml
@@ -22,7 +22,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpom
diff --git a/helidon/pom.xml b/helidon/pom.xml
index 3cd58d704e2..23401db4d21 100644
--- a/helidon/pom.xml
+++ b/helidon/pom.xml
@@ -22,7 +22,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon
diff --git a/http/encoding/deflate/pom.xml b/http/encoding/deflate/pom.xml
index baaea1372ef..d9b04d87361 100644
--- a/http/encoding/deflate/pom.xml
+++ b/http/encoding/deflate/pom.xml
@@ -21,7 +21,7 @@
io.helidon.http.encodinghelidon-http-encoding-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-http-encoding-deflate
diff --git a/http/encoding/encoding/pom.xml b/http/encoding/encoding/pom.xml
index 92208be8691..a9e6f8b68b0 100644
--- a/http/encoding/encoding/pom.xml
+++ b/http/encoding/encoding/pom.xml
@@ -21,7 +21,7 @@
io.helidon.http.encodinghelidon-http-encoding-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-http-encoding
diff --git a/http/encoding/gzip/pom.xml b/http/encoding/gzip/pom.xml
index f8c4a55c09a..a440048d243 100644
--- a/http/encoding/gzip/pom.xml
+++ b/http/encoding/gzip/pom.xml
@@ -21,7 +21,7 @@
io.helidon.http.encodinghelidon-http-encoding-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-http-encoding-gzip
diff --git a/http/encoding/pom.xml b/http/encoding/pom.xml
index e767147197f..a9adb7e7226 100644
--- a/http/encoding/pom.xml
+++ b/http/encoding/pom.xml
@@ -21,7 +21,7 @@
io.helidon.httphelidon-http-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.http.encoding
diff --git a/http/http/pom.xml b/http/http/pom.xml
index 24157668137..1e332fe2808 100644
--- a/http/http/pom.xml
+++ b/http/http/pom.xml
@@ -23,7 +23,7 @@
io.helidon.httphelidon-http-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-http
diff --git a/http/http/src/main/java/io/helidon/http/HostValidator.java b/http/http/src/main/java/io/helidon/http/HostValidator.java
new file mode 100644
index 00000000000..834a27b0b5a
--- /dev/null
+++ b/http/http/src/main/java/io/helidon/http/HostValidator.java
@@ -0,0 +1,347 @@
+/*
+ * 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.http;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Validate the host string (maybe from the {@code Host} header).
+ *
+ * The configuration should be done through property {@value #JSON_FORMAT_PROP_KEY}, that provides comma separated list of name to
+ * fields to log (references are the same as you would use in a {@link java.util.logging.SimpleFormatter}).
+ *
+ * The configuration falls back to property {@value io.helidon.logging.jul.HelidonFormatter#JUL_FORMAT_PROP_KEY},
+ * and analyzes it to provide a "guessed" JSON structure.
+ *
+ * Example (and also the default format):
+ * {@value #DEFAULT_FORMAT}
+ */
+public class HelidonJsonFormatter extends Formatter {
+ static final String DEFAULT_FORMAT = "ts:%1$tQ,date:%1$tY.%1$tm.%1$td,time:%1$tH:%1$tM:%1$tS.%1$tL,level:%4$s,message:%5$s,"
+ + "exception:%6$s,thread:!thread!,logger:%3$s";
+
+ private static final String JSON_FORMAT_PROP_KEY = "io.helidon.logging.jul.HelidonJsonFormatter.fields";
+
+ // formats we understand
+ private static final String EPOCH_MILLIS_FORMAT = "%1$tQ";
+ private static final String YEAR_FORMAT = "%1$tY";
+ private static final String MONTH_FORMAT = "%1$tm";
+ private static final String DAY_FORMAT = "%1$td";
+ private static final String HOUR_FORMAT = "%1$tH";
+ private static final String MINUTE_FORMAT = "%1$tM";
+ private static final String SECOND_FORMAT = "%1$tS";
+ private static final String SOURCE_FORMAT = "%2$s";
+ private static final String LOGGER_FORMAT = "%3$s";
+ private static final String LEVEL_FORMAT = "%4$s";
+ private static final String MESSAGE_FORMAT = "%5$s";
+ private static final String EXCEPTION_FORMAT = "%6$s";
+
+ private final List formatters;
+
+ /**
+ * Create new instance of the {@link io.helidon.logging.jul.HelidonJsonFormatter}.
+ */
+ public HelidonJsonFormatter() {
+ String jsonFormat = LogManager.getLogManager().getProperty(JSON_FORMAT_PROP_KEY);
+ String julFormat = LogManager.getLogManager().getProperty(JUL_FORMAT_PROP_KEY);
+ if (jsonFormat == null && julFormat == null) {
+ jsonFormat = DEFAULT_FORMAT;
+ }
+ if (jsonFormat == null) {
+ this.formatters = guessFromSimpleFormat(julFormat);
+ } else {
+ this.formatters = fromJsonFormat(jsonFormat);
+ }
+ }
+
+ HelidonJsonFormatter(String format, boolean jsonFormat) {
+ this.formatters = jsonFormat ? fromJsonFormat(format) : guessFromSimpleFormat(format);
+ }
+
+ @Override
+ public String format(LogRecord record) {
+ var builder = Hson.Struct.builder();
+ var params = HelidonFormatter.parameters(record, super.formatMessage(record));
+
+ formatters.forEach(formatter -> formatter.update(builder, params));
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ builder.build()
+ .write(pw);
+ pw.println();
+ pw.close();
+ return sw.toString();
+ }
+
+ private List fromJsonFormat(String jsonFormat) {
+ return Stream.of(jsonFormat.split(","))
+ .map(it -> new ValueFormatter(Field.create(jsonFormat, it)))
+ .collect(Collectors.toUnmodifiableList());
+ }
+
+ private List guessFromSimpleFormat(String julFormat) {
+ List result = new ArrayList<>();
+ Map counters = new HashMap<>();
+ String usedFormat = julFormat.replaceAll("%n", " ");
+ // spaces expected to separate "blocks"
+ for (String block : usedFormat.split(" ")) {
+ if (block.isBlank()) {
+ continue;
+ }
+ // now lets do some "magic"
+ if (block.contains(YEAR_FORMAT) && block.contains(MONTH_FORMAT) && block.contains(DAY_FORMAT)) {
+ if (block.contains(HOUR_FORMAT)) {
+ // full timestamp
+ result.add(new ValueFormatter(new Field(name(counters, "timestamp"), block)));
+ } else {
+ // date only
+ result.add(new ValueFormatter(new Field(name(counters, "date"), block)));
+ }
+ continue;
+ }
+ if (block.contains(HOUR_FORMAT) && block.contains(MINUTE_FORMAT)) {
+ result.add(new ValueFormatter(new Field(name(counters, "time"), block)));
+ continue;
+ }
+
+ // now only create sections if it only contains one parameter
+ if (block.contains(SOURCE_FORMAT) && block.length() < 7) {
+ result.add(new ValueFormatter(new Field(name(counters, "source"), block)));
+ continue;
+ }
+ if (block.contains(LOGGER_FORMAT) && block.length() < 7) {
+ result.add(new ValueFormatter(new Field(name(counters, "logger"), block)));
+ continue;
+ }
+ if (block.contains(LEVEL_FORMAT) && block.length() < 7) {
+ result.add(new ValueFormatter(new Field(name(counters, "level"), block)));
+ continue;
+ }
+ if (block.contains(MESSAGE_FORMAT) && block.length() < 7) {
+ result.add(new ValueFormatter(new Field(name(counters, "message"), block)));
+ continue;
+ }
+ if (block.contains(EXCEPTION_FORMAT) && block.length() < 7) {
+ result.add(new ValueFormatter(new Field(name(counters, "exception"), block)));
+ continue;
+ }
+ if (block.contains(THREAD_TOKEN) && block.length() < THREAD_TOKEN.length() + 3) {
+ result.add(new ValueFormatter(new Field(name(counters, "thread"), block)));
+ continue;
+ }
+
+ // now let's extract the parts
+ if (block.contains(EPOCH_MILLIS_FORMAT)) {
+ result.add(new ValueFormatter(new Field(name(counters, "ts"), EPOCH_MILLIS_FORMAT)));
+ }
+ if (block.contains(YEAR_FORMAT)) {
+ result.add(new ValueFormatter(new Field(name(counters, "year"), YEAR_FORMAT)));
+ }
+ if (block.contains(MONTH_FORMAT)) {
+ result.add(new ValueFormatter(new Field(name(counters, "month"), MONTH_FORMAT)));
+ }
+ if (block.contains(DAY_FORMAT)) {
+ result.add(new ValueFormatter(new Field(name(counters, "day"), DAY_FORMAT)));
+ }
+ if (block.contains(HOUR_FORMAT)) {
+ result.add(new ValueFormatter(new Field(name(counters, "hour"), HOUR_FORMAT)));
+ }
+ if (block.contains(MINUTE_FORMAT)) {
+ result.add(new ValueFormatter(new Field(name(counters, "minute"), MINUTE_FORMAT)));
+ }
+ if (block.contains(SECOND_FORMAT)) {
+ result.add(new ValueFormatter(new Field(name(counters, "second"), SECOND_FORMAT)));
+ }
+ if (block.contains(SOURCE_FORMAT)) {
+ result.add(new ValueFormatter(new Field(name(counters, "source"), SOURCE_FORMAT)));
+ }
+ if (block.contains(LOGGER_FORMAT)) {
+ result.add(new ValueFormatter(new Field(name(counters, "logger"), LOGGER_FORMAT)));
+ }
+ if (block.contains(LEVEL_FORMAT)) {
+ result.add(new ValueFormatter(new Field(name(counters, "level"), LEVEL_FORMAT)));
+ }
+ if (block.contains(MESSAGE_FORMAT)) {
+ result.add(new ValueFormatter(new Field(name(counters, "message"), MESSAGE_FORMAT)));
+ }
+ if (block.contains(EXCEPTION_FORMAT)) {
+ result.add(new ValueFormatter(new Field(name(counters, "exception"), EXCEPTION_FORMAT)));
+ }
+ if (block.contains(THREAD_TOKEN) || block.contains("%X{" + HelidonFormatter.THREAD + "}")) {
+ result.add(new ValueFormatter(new Field(name(counters, "thread"), THREAD_TOKEN)));
+ }
+
+ // MDC support
+ Matcher matcher = HelidonFormatter.X_VALUE.matcher(usedFormat);
+ while (matcher.find()) {
+ String name = matcher.group(2);
+ if (!name.equals(HelidonFormatter.THREAD)) {
+ result.add(new ValueFormatter(new Field(name(counters, "X." + name), "%X{" + name + "}")));
+ }
+ }
+ }
+ return result;
+ }
+
+ private String name(Map counters, String name) {
+ if (counters.containsKey(name)) {
+ return name + "_" + counters.get(name).incrementAndGet();
+ } else {
+ counters.put(name, new AtomicInteger());
+ return name;
+ }
+ }
+
+ private static class ValueFormatter {
+ private final Set parsedProps = new HashSet<>();
+ private final String jsonName;
+ private final String format;
+ private final boolean thread;
+
+ private ValueFormatter(Field field) {
+ this.jsonName = field.name();
+ this.format = field.format();
+
+ this.thread = this.format.contains(THREAD_TOKEN) || this.format.contains("%X{" + HelidonFormatter.THREAD + "}");
+ Matcher matcher = HelidonFormatter.X_VALUE.matcher(this.format);
+ while (matcher.find()) {
+ parsedProps.add(matcher.group(2));
+ }
+ }
+
+ private void update(Hson.Struct.Builder jsonBuilder, Object... parameters) {
+
+ String message = thread ? HelidonFormatter.thread(format) : format;
+ for (String parsedKey : parsedProps) {
+ String value = HelidonMdc.get(parsedKey).orElse("");
+ message = HelidonFormatter.PATTERN_CACHE
+ .computeIfAbsent(parsedKey, key -> Pattern.compile("%X\\{" + key + "}"))
+ .matcher(message)
+ .replaceAll(value);
+ }
+ String formattedValue = String.format(message, parameters);
+ if (!formattedValue.isBlank()) {
+ jsonBuilder.set(jsonName, formattedValue);
+ }
+ }
+ }
+
+ private record Field(String name, String format) {
+ private static Field create(String format, String field) {
+ int index = field.indexOf(':');
+ if (index == -1) {
+ throw new IllegalArgumentException("Invalid format definition for " + HelidonJsonFormatter.class.getSimpleName()
+ + ", each field must have field name followed by a colon with field "
+ + "value,"
+ + " such as 'message:%5$s', but got: '" + field + "'. "
+ + "Full format: " + format);
+ }
+ return new Field(field.substring(0, index), field.substring(index + 1));
+ }
+ }
+}
diff --git a/logging/jul/src/main/java/module-info.java b/logging/jul/src/main/java/module-info.java
index e17f91fd07c..36facfd2116 100644
--- a/logging/jul/src/main/java/module-info.java
+++ b/logging/jul/src/main/java/module-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,12 +20,13 @@
module io.helidon.logging.jul {
requires io.helidon.common;
+ requires io.helidon.metadata.hson;
requires java.logging;
requires transitive io.helidon.common.context;
requires transitive io.helidon.logging.common;
- exports io.helidon.logging.jul;
+ exports io.helidon.logging.jul;
provides io.helidon.common.context.spi.DataPropagationProvider with io.helidon.logging.jul.JulMdcPropagator;
provides io.helidon.logging.common.spi.MdcProvider with io.helidon.logging.jul.JulMdcProvider;
diff --git a/logging/jul/src/test/java/io/helidon/logging/jul/JulJsonTest.java b/logging/jul/src/test/java/io/helidon/logging/jul/JulJsonTest.java
new file mode 100644
index 00000000000..9183bcb66b1
--- /dev/null
+++ b/logging/jul/src/test/java/io/helidon/logging/jul/JulJsonTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.logging.jul;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+import io.helidon.logging.common.HelidonMdc;
+import io.helidon.metadata.hson.Hson;
+
+import org.junit.jupiter.api.Test;
+
+import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty;
+import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class JulJsonTest {
+ private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy.MM.dd");
+ private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
+ private static final Instant INSTANT = Instant.parse("2007-12-03T10:15:30.00Z");
+ private static final ZonedDateTime DATE_TIME = INSTANT.atZone(ZoneId.systemDefault());
+
+ private static final LogRecord LOG_RECORD;
+ private static final LogRecord LOG_RECORD_WITH_EXCEPTION;
+
+ static {
+ LogRecord record = new LogRecord(Level.WARNING, "Message content");
+ record.setInstant(INSTANT);
+ record.setLoggerName("LoggerName");
+ record.setThrown(new IllegalStateException("Thrown"));
+ record.setSourceClassName("io.helidon.logging.jul.JulJsonTest");
+ record.setSourceMethodName("testJsonDefaultFormat");
+ LOG_RECORD_WITH_EXCEPTION = record;
+
+ record = new LogRecord(Level.WARNING, "Message content");
+ record.setInstant(INSTANT);
+ record.setLoggerName("LoggerName");
+ record.setSourceClassName("io.helidon.logging.jul.JulJsonTest");
+ record.setSourceMethodName("testJsonDefaultFormat");
+ LOG_RECORD = record;
+ }
+
+ @Test
+ public void testJsonDefaultFormat() throws InterruptedException {
+ HelidonJsonFormatter formatter = new HelidonJsonFormatter(HelidonJsonFormatter.DEFAULT_FORMAT, true);
+
+ String threadName = "logging-jul-test-thread";
+
+ AtomicReference resultReference = new AtomicReference<>();
+ Thread.ofVirtual()
+ .name(threadName)
+ .start(() -> {
+ resultReference.set(formatter.format(LOG_RECORD));
+ })
+ .join(Duration.ofSeconds(5));
+
+ String result = resultReference.get();
+ var json = Hson.parse(new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8)))
+ .asStruct();
+
+ assertThat(json.stringValue("ts"), optionalValue(is(String.valueOf(INSTANT.toEpochMilli()))));
+ assertThat(json.stringValue("date"), optionalValue(is(DATE_FORMAT.format(DATE_TIME))));
+ assertThat(json.stringValue("time"), optionalValue(is(TIME_FORMAT.format(DATE_TIME))));
+ assertThat(json.stringValue("level"), optionalValue(is("WARNING")));
+ assertThat(json.stringValue("message"), optionalValue(is("Message content")));
+ assertThat(json.stringValue("exception"), optionalEmpty());
+ assertThat(json.stringValue("logger"), optionalValue(is("LoggerName")));
+ assertThat(json.stringValue("thread"), optionalValue(containsString(threadName)));
+ }
+
+ @Test
+ public void testJsonWithExceptionDefaultFormat() throws InterruptedException {
+ HelidonJsonFormatter formatter = new HelidonJsonFormatter(HelidonJsonFormatter.DEFAULT_FORMAT, true);
+
+ String threadName = "logging-jul-test-thread";
+
+ AtomicReference resultReference = new AtomicReference<>();
+ Thread.ofVirtual()
+ .name(threadName)
+ .start(() -> {
+ resultReference.set(formatter.format(LOG_RECORD_WITH_EXCEPTION));
+ })
+ .join(Duration.ofSeconds(1000));
+
+ String result = resultReference.get();
+ var json = Hson.parse(new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8)))
+ .asStruct();
+
+ assertThat(json.stringValue("date"), optionalValue(is(DATE_FORMAT.format(DATE_TIME))));
+ assertThat(json.stringValue("time"), optionalValue(is(TIME_FORMAT.format(DATE_TIME))));
+ assertThat(json.stringValue("level"), optionalValue(is("WARNING")));
+ assertThat(json.stringValue("message"), optionalValue(is("Message content")));
+ assertThat(json.stringValue("exception"), optionalValue(containsString("Thrown")));
+ assertThat(json.stringValue("logger"), optionalValue(is("LoggerName")));
+ assertThat(json.stringValue("thread"), optionalValue(containsString(threadName)));
+ }
+
+ @Test
+ public void testJsonWithExceptionSimpleFormat() throws InterruptedException {
+ String simpleFormat = "%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$s %3$s !thread!: %5$s%6$s %X{test}%n";
+ HelidonJsonFormatter formatter = new HelidonJsonFormatter(simpleFormat, false);
+
+ String threadName = "logging-jul-test-thread";
+
+ AtomicReference resultReference = new AtomicReference<>();
+ Thread.ofVirtual()
+ .name(threadName)
+ .start(() -> {
+ HelidonMdc.set("test", "testValue");
+ resultReference.set(formatter.format(LOG_RECORD_WITH_EXCEPTION));
+ })
+ .join(Duration.ofSeconds(1000));
+
+ String result = resultReference.get();
+ var json = Hson.parse(new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8)))
+ .asStruct();
+
+ assertThat(json.stringValue("date"), optionalValue(is(DATE_FORMAT.format(DATE_TIME))));
+ assertThat(json.stringValue("time"), optionalValue(is(TIME_FORMAT.format(DATE_TIME))));
+ assertThat(json.stringValue("logger"), optionalValue(is("LoggerName")));
+ assertThat(json.stringValue("level"), optionalValue(is("WARNING")));
+ assertThat(json.stringValue("thread"), optionalValue(containsString(threadName)));
+ assertThat(json.stringValue("message"), optionalValue(is("Message content")));
+ assertThat(json.stringValue("exception"), optionalValue(containsString("Thrown")));
+ assertThat(json.stringValue("X.test"), optionalValue(containsString("testValue")));
+ }
+
+ @Test
+ public void testJsonWithExceptionCustomFormat() throws InterruptedException {
+ String format = "timestamp:%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS.%1$tL,loglevel:%4$s,test:%X{test},msg:%5$s,source:%2$s";
+ HelidonJsonFormatter formatter = new HelidonJsonFormatter(format, true);
+
+ String threadName = "logging-jul-test-thread";
+
+ AtomicReference resultReference = new AtomicReference<>();
+ Thread.ofVirtual()
+ .name(threadName)
+ .start(() -> {
+ HelidonMdc.set("test", "testValue");
+ resultReference.set(formatter.format(LOG_RECORD_WITH_EXCEPTION));
+ })
+ .join(Duration.ofSeconds(1000));
+
+ String result = resultReference.get();
+ var json = Hson.parse(new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8)))
+ .asStruct();
+
+ assertThat(json.stringValue("timestamp"), optionalValue(is(DATE_FORMAT.format(DATE_TIME) + " "
+ + TIME_FORMAT.format(DATE_TIME))));
+ assertThat(json.stringValue("loglevel"), optionalValue(is("WARNING")));
+ assertThat(json.stringValue("msg"), optionalValue(is("Message content")));
+ assertThat(json.stringValue("test"), optionalValue(is("testValue")));
+ assertThat(json.stringValue("source"), optionalValue(is("io.helidon.logging.jul.JulJsonTest"
+ + " testJsonDefaultFormat")));
+ }
+}
+
diff --git a/logging/log4j/pom.xml b/logging/log4j/pom.xml
index dd2f7ea6b7d..9a878517f33 100644
--- a/logging/log4j/pom.xml
+++ b/logging/log4j/pom.xml
@@ -20,7 +20,7 @@
helidon-logging-projectio.helidon.logging
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/logging/pom.xml b/logging/pom.xml
index 0a3cacfcb38..056c6597f7e 100644
--- a/logging/pom.xml
+++ b/logging/pom.xml
@@ -21,7 +21,7 @@
helidon-projectio.helidon
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.logging
diff --git a/logging/slf4j/pom.xml b/logging/slf4j/pom.xml
index 4f99fc4ea33..f763bbc6eed 100644
--- a/logging/slf4j/pom.xml
+++ b/logging/slf4j/pom.xml
@@ -21,7 +21,7 @@
helidon-logging-projectio.helidon.logging
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-logging-slf4j
diff --git a/logging/tests/log4j/pom.xml b/logging/tests/log4j/pom.xml
index 1b666b7430a..01be9ffe90d 100644
--- a/logging/tests/log4j/pom.xml
+++ b/logging/tests/log4j/pom.xml
@@ -21,7 +21,7 @@
io.helidon.logging.testshelidon-logging-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-logging-tests-log4j
diff --git a/logging/tests/pom.xml b/logging/tests/pom.xml
index 197b433b9fb..d3662f04646 100644
--- a/logging/tests/pom.xml
+++ b/logging/tests/pom.xml
@@ -24,7 +24,7 @@
io.helidon.logginghelidon-logging-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.logging.tests
diff --git a/lra/coordinator/client/narayana-client/pom.xml b/lra/coordinator/client/narayana-client/pom.xml
index a1e8964b0c0..644ec9763c2 100644
--- a/lra/coordinator/client/narayana-client/pom.xml
+++ b/lra/coordinator/client/narayana-client/pom.xml
@@ -24,7 +24,7 @@
io.helidon.lrahelidon-lra-coordinator-client-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-lra-coordinator-narayana-client
diff --git a/lra/coordinator/client/narayana-client/src/main/java/io/helidon/lra/coordinator/client/narayana/NarayanaClient.java b/lra/coordinator/client/narayana-client/src/main/java/io/helidon/lra/coordinator/client/narayana/NarayanaClient.java
index ae2f0a20fc5..df03a34535f 100644
--- a/lra/coordinator/client/narayana-client/src/main/java/io/helidon/lra/coordinator/client/narayana/NarayanaClient.java
+++ b/lra/coordinator/client/narayana-client/src/main/java/io/helidon/lra/coordinator/client/narayana/NarayanaClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,6 +46,8 @@
import org.eclipse.microprofile.lra.annotation.LRAStatus;
import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
+import static java.lang.System.Logger.Level.DEBUG;
+
/**
* Narayana LRA coordinator client.
*/
@@ -103,6 +105,7 @@ private URI startInternal(URI parentLRA, String clientID, PropagatedHeaders head
.map(p -> parseBaseUri(p.toASCIIString()))
.orElse(coordinatorUriSupplier.get());
+ logF("Starting LRA, coordinator: {0}/start, clientId: {1}, timeout: {2}", baseUri, clientID, timeout);
return retry.invoke(() -> {
HttpClientRequest req = prepareWebClient(baseUri)
.post()
@@ -139,6 +142,7 @@ private URI startInternal(URI parentLRA, String clientID, PropagatedHeaders head
@Override
public void cancel(URI lraId, PropagatedHeaders headers) {
+ logF("Cancelling LRA {0}", lraId);
retry.invoke(() -> {
var req = prepareWebClient(lraId)
.put()
@@ -164,6 +168,7 @@ public void cancel(URI lraId, PropagatedHeaders headers) {
@Override
public void close(URI lraId, PropagatedHeaders headers) {
+ logF("Closing LRA {0}", lraId);
retry.invoke(() -> {
var req = prepareWebClient(lraId)
.put()
@@ -197,6 +202,7 @@ public Optional join(URI lraId,
Participant p) {
String links = compensatorLinks(p);
+ logF("Joining LRA {0} with links: {1}", lraId, links);
return retry.invoke(() -> {
var req = prepareWebClient(lraId)
.put()
@@ -227,13 +233,14 @@ public Optional join(URI lraId,
throw connectionError("Unexpected coordinator response ", res.status().code());
}
} catch (Exception e) {
- throw connectionError("Unable to join LRA", e);
+ throw connectionError("Unable to join LRA " + lraId, e);
}
});
}
@Override
public void leave(URI lraId, PropagatedHeaders headers, Participant p) {
+ logF("Leaving LRA {0} participant: {1}", lraId, p);
retry.invoke(() -> {
var req = prepareWebClient(lraId)
.put()
@@ -260,6 +267,7 @@ public void leave(URI lraId, PropagatedHeaders headers, Participant p) {
@Override
public LRAStatus status(URI lraId, PropagatedHeaders headers) {
+ logF("Checking status of LRA {0}", lraId);
return retry.invoke(() -> {
var req = prepareWebClient(lraId)
.get()
@@ -354,7 +362,9 @@ private CoordinatorConnectionException connectionError(String message, Throwable
}
private void logF(String msg, Object... params) {
- LOGGER.log(Level.DEBUG, msg, params);
+ if (LOGGER.isLoggable(DEBUG)) {
+ LOGGER.log(DEBUG, msg, params);
+ }
}
}
diff --git a/lra/coordinator/client/pom.xml b/lra/coordinator/client/pom.xml
index 0ff6735e772..bb7c82025c0 100644
--- a/lra/coordinator/client/pom.xml
+++ b/lra/coordinator/client/pom.xml
@@ -24,7 +24,7 @@
io.helidon.lrahelidon-lra-coordinator-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.lrahelidon-lra-coordinator-client-project
diff --git a/lra/coordinator/client/spi/pom.xml b/lra/coordinator/client/spi/pom.xml
index 8470fc2ba28..36abc06aa24 100644
--- a/lra/coordinator/client/spi/pom.xml
+++ b/lra/coordinator/client/spi/pom.xml
@@ -24,7 +24,7 @@
io.helidon.lrahelidon-lra-coordinator-client-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-lra-coordinator-client-spi
diff --git a/lra/coordinator/pom.xml b/lra/coordinator/pom.xml
index a4bea92f804..56b3ed18670 100644
--- a/lra/coordinator/pom.xml
+++ b/lra/coordinator/pom.xml
@@ -24,7 +24,7 @@
io.helidon.lrahelidon-lra-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.lrahelidon-lra-coordinator-project
diff --git a/lra/coordinator/server/pom.xml b/lra/coordinator/server/pom.xml
index 0c801defe74..015c333267b 100644
--- a/lra/coordinator/server/pom.xml
+++ b/lra/coordinator/server/pom.xml
@@ -23,25 +23,32 @@
https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- io.helidon.applications
- helidon-se
- 4.1.0-SNAPSHOT
- ../../../applications/se/pom.xml
+ io.helidon.lra
+ helidon-lra-coordinator-project
+ 4.2.0-SNAPSHOT
- io.helidon.lrahelidon-lra-coordinator-serverHelidon LRA CoordinatorNarayana compatible LRA coordinator
- true
- true
- trueio.helidon.lra.coordinator.Main
+
+
+
+ io.helidon.applications
+ helidon-se
+ ${project.parent.version}
+ pom
+ import
+
+
+
+
org.eclipse.microprofile.lra
@@ -131,6 +138,120 @@
+ ${project.artifactId}
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+ ${version.plugin.os}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${version.plugin.compiler}
+
+ full
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${version.plugin.surefire}
+
+ false
+
+ ${project.build.outputDirectory}/logging.properties
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ ${version.plugin.failsafe}
+
+ false
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ ${version.plugin.dependency}
+
+
+ copy-libs
+ prepare-package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/libs
+ false
+ false
+ true
+ true
+ runtime
+
+ okio
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ ${version.plugin.resources}
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${version.plugin.jar}
+
+
+
+ true
+ libs
+
+ ${mainClass}
+ false
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ ${version.plugin.exec}
+
+ java
+ true
+
+ -classpath
+
+ ${mainClass}
+
+
+
+
+ io.helidon.build-tools
+ helidon-maven-plugin
+ ${version.plugin.helidon}
+
+
+ io.helidon.licensing
+ helidon-licensing
+ ${helidon.version}
+
+
+
+
+
+
org.apache.maven.plugins
diff --git a/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/CoordinatorService.java b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/CoordinatorService.java
index bebdf1d0b89..5636870ecf8 100644
--- a/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/CoordinatorService.java
+++ b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/CoordinatorService.java
@@ -95,23 +95,23 @@ public class CoordinatorService implements HttpService {
init();
}
- private void init() {
- lraPersistentRegistry.load(this);
- recoveryTask = Scheduling.fixedRate()
- .delay(config.get("recovery-interval").asLong().orElse(200L))
- .initialDelay(200)
- .timeUnit(TimeUnit.MILLISECONDS)
- .task(this::tick)
+ /**
+ * Create a new Lra coordinator.
+ *
+ * @return coordinator
+ */
+ public static CoordinatorService create() {
+ return builder()
.build();
+ }
- if (config.get("periodical-persist").asBoolean().orElse(false)) {
- persistTask = Scheduling.fixedRate()
- .delay(config.get("persist-interval").asLong().orElse(5000L))
- .initialDelay(200)
- .timeUnit(TimeUnit.MILLISECONDS)
- .task(inv -> lraPersistentRegistry.save())
- .build();
- }
+ /**
+ * Create a new fluent API builder.
+ *
+ * @return a new builder
+ */
+ public static Builder builder() {
+ return new Builder();
}
/**
@@ -149,6 +149,39 @@ public void routing(HttpRules rules) {
.put("/{LraId}/remove", this::leave);
}
+ /**
+ * Get LRA by lraId.
+ *
+ * @param lraId without coordinator uri prefix
+ * @return LRA when managed by this coordinator or null
+ */
+ public Lra lra(String lraId) {
+ return this.lraPersistentRegistry.get(lraId);
+ }
+
+ LazyValue coordinatorURL() {
+ return coordinatorURL;
+ }
+
+ private void init() {
+ lraPersistentRegistry.load(this);
+ recoveryTask = Scheduling.fixedRateBuilder()
+ .delay(config.get("recovery-interval").asLong().orElse(200L))
+ .initialDelay(200)
+ .timeUnit(TimeUnit.MILLISECONDS)
+ .task(this::tick)
+ .build();
+
+ if (config.get("periodical-persist").asBoolean().orElse(false)) {
+ persistTask = Scheduling.fixedRateBuilder()
+ .delay(config.get("persist-interval").asLong().orElse(5000L))
+ .initialDelay(200)
+ .timeUnit(TimeUnit.MILLISECONDS)
+ .task(inv -> lraPersistentRegistry.save())
+ .build();
+ }
+ }
+
/**
* Ask coordinator to start new LRA and return its id.
*
@@ -163,15 +196,15 @@ private void start(ServerRequest req, ServerResponse res) {
String lraUUID = UUID.randomUUID().toString();
URI lraId = coordinatorUriWithPath(lraUUID);
if (!parentLRA.isEmpty()) {
- Lra parent = lraPersistentRegistry.get(parentLRA.replace(coordinatorURL.get().toASCIIString() + "/", ""));
+ LraImpl parent = lraPersistentRegistry.get(parentLRA.replace(coordinatorURL.get().toASCIIString() + "/", ""));
if (parent != null) {
- Lra childLra = new Lra(this, lraUUID, URI.create(parentLRA), this.config);
+ LraImpl childLra = new LraImpl(this, lraUUID, URI.create(parentLRA), this.config);
childLra.setupTimeout(timeLimit);
lraPersistentRegistry.put(lraUUID, childLra);
parent.addChild(childLra);
}
} else {
- Lra newLra = new Lra(this, lraUUID, config);
+ LraImpl newLra = new LraImpl(this, lraUUID, config);
newLra.setupTimeout(timeLimit);
lraPersistentRegistry.put(lraUUID, newLra);
}
@@ -189,12 +222,12 @@ private void start(ServerRequest req, ServerResponse res) {
*/
private void close(ServerRequest req, ServerResponse res) {
String lraId = req.path().pathParameters().get("LraId");
- Lra lra = lraPersistentRegistry.get(lraId);
+ LraImpl lra = lraPersistentRegistry.get(lraId);
if (lra == null) {
res.status(NOT_FOUND_404).send();
return;
}
- if (lra.status().get() != LRAStatus.Active) {
+ if (lra.lraStatus().get() != LRAStatus.Active) {
// Already time-outed
res.status(GONE_410).send();
return;
@@ -211,7 +244,7 @@ private void close(ServerRequest req, ServerResponse res) {
*/
private void cancel(ServerRequest req, ServerResponse res) {
String lraId = req.path().pathParameters().get("LraId");
- Lra lra = lraPersistentRegistry.get(lraId);
+ LraImpl lra = lraPersistentRegistry.get(lraId);
if (lra == null) {
res.status(NOT_FOUND_404).send();
return;
@@ -231,7 +264,7 @@ private void join(ServerRequest req, ServerResponse res) {
String lraId = req.path().pathParameters().get("LraId");
String compensatorLink = req.headers().first(HeaderNames.LINK).orElse("");
- Lra lra = lraPersistentRegistry.get(lraId);
+ LraImpl lra = lraPersistentRegistry.get(lraId);
if (lra == null) {
res.status(NOT_FOUND_404).send();
return;
@@ -257,14 +290,14 @@ private void join(ServerRequest req, ServerResponse res) {
*/
private void status(ServerRequest req, ServerResponse res) {
String lraId = req.path().pathParameters().get("LraId");
- Lra lra = lraPersistentRegistry.get(lraId);
+ LraImpl lra = lraPersistentRegistry.get(lraId);
if (lra == null) {
res.status(NOT_FOUND_404).send();
return;
}
res.status(OK_200)
- .send(lra.status().get().name());
+ .send(lra.lraStatus().get().name());
}
/**
@@ -278,7 +311,7 @@ private void leave(ServerRequest req, ServerResponse res) {
String lraId = req.path().pathParameters().get("LraId");
String compensatorLinks = req.content().as(String.class);
- Lra lra = lraPersistentRegistry.get(lraId);
+ LraImpl lra = lraPersistentRegistry.get(lraId);
if (lra == null) {
res.status(NOT_FOUND_404).send();
} else {
@@ -307,13 +340,13 @@ private void recovery(ServerRequest req, ServerResponse res) {
});
if (lraUUID.isPresent()) {
- Lra lra = lraPersistentRegistry.get(lraUUID.get());
+ LraImpl lra = lraPersistentRegistry.get(lraUUID.get());
if (lra != null) {
- if (RECOVERABLE_STATUSES.contains(lra.status().get())) {
+ if (RECOVERABLE_STATUSES.contains(lra.lraStatus().get())) {
JsonObject json = JSON.createObjectBuilder()
.add("lraId", lra.lraId())
- .add("status", lra.status().get().name())
- .add("recovering", Set.of(LRAStatus.Closed, LRAStatus.Cancelled).contains(lra.status().get()))
+ .add("status", lra.lraStatus().get().name())
+ .add("recovering", Set.of(LRAStatus.Closed, LRAStatus.Cancelled).contains(lra.lraStatus().get()))
.build();
res.status(OK_200).send(json);
} else {
@@ -325,10 +358,10 @@ private void recovery(ServerRequest req, ServerResponse res) {
} else {
JsonArray jsonValues = lraPersistentRegistry
.stream()
- .filter(lra -> RECOVERABLE_STATUSES.contains(lra.status().get()))
+ .filter(lra -> RECOVERABLE_STATUSES.contains(lra.lraStatus().get()))
.map(l -> JSON.createObjectBuilder()
.add("lraId", l.lraId())
- .add("status", l.status().get().name())
+ .add("status", l.lraStatus().get().name())
.build()
)
.collect(JSON::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::addAll)
@@ -348,7 +381,7 @@ private void get(ServerRequest req, ServerResponse res) {
.filter(lra -> lraId.map(id -> lra.lraId().equals(id)).orElse(true))
.map(l -> JSON.createObjectBuilder()
.add("lraId", l.lraId())
- .add("status", l.status().get().name())
+ .add("status", l.lraStatus().get().name())
.build()
)
.collect(JSON::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::addAll)
@@ -369,23 +402,23 @@ private void tick(FixedRateInvocation inv) {
if (lra.isReadyToDelete()) {
lraPersistentRegistry.remove(lra.lraId());
} else {
- if (LRAStatus.Cancelling == lra.status().get()) {
+ if (LRAStatus.Cancelling == lra.lraStatus().get()) {
LOGGER.log(Level.DEBUG, "Recovering {0}", lra.lraId());
lra.cancel();
}
- if (LRAStatus.Closing == lra.status().get()) {
+ if (LRAStatus.Closing == lra.lraStatus().get()) {
LOGGER.log(Level.DEBUG, "Recovering {0}", lra.lraId());
lra.close();
}
- if (lra.checkTimeout() && lra.status().get().equals(LRAStatus.Active)) {
+ if (lra.checkTimeout() && lra.lraStatus().get().equals(LRAStatus.Active)) {
LOGGER.log(Level.DEBUG, "Timeouting {0} ", lra.lraId());
- lra.timeout();
+ lra.triggerTimeout();
}
- if (Set.of(LRAStatus.Closed, LRAStatus.Cancelled).contains(lra.status().get())) {
+ if (Set.of(LRAStatus.Closed, LRAStatus.Cancelled).contains(lra.lraStatus().get())) {
// If a participant is unable to complete or compensate immediately or because of a failure
// then it must remember the fact (by reporting its' status via the @Status method)
// until explicitly told that it can clean up using this @Forget annotation.
- LOGGER.log(Level.DEBUG, "Forgetting {0} {1}", new Object[] {lra.status().get(), lra.lraId()});
+ LOGGER.log(Level.DEBUG, "Forgetting {0} {1}", new Object[] {lra.lraStatus().get(), lra.lraId()});
lra.tryForget();
lra.tryAfter();
}
@@ -394,10 +427,6 @@ private void tick(FixedRateInvocation inv) {
completedRecovery.getAndSet(new CompletableFuture<>()).complete(null);
}
- LazyValue getCoordinatorURL() {
- return coordinatorURL;
- }
-
private void nextRecoveryCycle() {
try {
completedRecovery.get().get(1, TimeUnit.SECONDS);
@@ -412,25 +441,6 @@ private URI coordinatorUriWithPath(String additionalPath) {
return URI.create(coordinatorURL.get().toASCIIString() + "/" + additionalPath);
}
- /**
- * Create a new Lra coordinator.
- *
- * @return coordinator
- */
- public static CoordinatorService create() {
- return builder()
- .build();
- }
-
- /**
- * Create a new fluent API builder.
- *
- * @return a new builder
- */
- public static Builder builder() {
- return new Builder();
- }
-
/**
* Coordinator builder.
*/
@@ -439,8 +449,8 @@ public static final class Builder implements io.helidon.common.Builder uriSupplier = () -> URI.create(config.get(COORDINATOR_URL_KEY)
- .asString()
- .orElse(DEFAULT_COORDINATOR_URL));
+ .asString()
+ .orElse(DEFAULT_COORDINATOR_URL));
/**
* Configuration needed for configuring coordinator.
@@ -453,18 +463,6 @@ public Builder config(Config config) {
return this;
}
- /**
- * Custom persistent registry for saving and loading the state of the coordinator.
- * Coordinator is not persistent by default.
- *
- * @param lraPersistentRegistry custom persistent registry
- * @return this builder
- */
- public Builder persistentRegistry(LraPersistentRegistry lraPersistentRegistry) {
- this.lraPersistentRegistry = lraPersistentRegistry;
- return this;
- }
-
/**
* Supplier for coordinator url.
* For supplying url after we know the port of the started server.
@@ -487,5 +485,17 @@ public CoordinatorService build() {
}
return new CoordinatorService(lraPersistentRegistry, uriSupplier, config);
}
+
+ /**
+ * Custom persistent registry for saving and loading the state of the coordinator.
+ * Coordinator is not persistent by default.
+ *
+ * @param lraPersistentRegistry custom persistent registry
+ * @return this builder
+ */
+ Builder persistentRegistry(LraPersistentRegistry lraPersistentRegistry) {
+ this.lraPersistentRegistry = lraPersistentRegistry;
+ return this;
+ }
}
}
diff --git a/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/Lra.java b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/Lra.java
index cd37fc9feb5..5da5101632e 100644
--- a/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/Lra.java
+++ b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/Lra.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,30 +15,10 @@
*/
package io.helidon.lra.coordinator;
-import java.lang.System.Logger.Level;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-import io.helidon.common.LazyValue;
-import io.helidon.config.Config;
-import io.helidon.http.ClientRequestHeaders;
import io.helidon.http.HeaderName;
import io.helidon.http.HeaderNames;
-import io.helidon.metrics.api.Counter;
-import io.helidon.metrics.api.MeterRegistry;
-import io.helidon.metrics.api.Metrics;
-import io.helidon.metrics.api.Timer;
import org.eclipse.microprofile.lra.annotation.LRAStatus;
@@ -47,298 +27,66 @@
import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_PARENT_CONTEXT_HEADER;
import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_RECOVERY_HEADER;
-class Lra {
- static final HeaderName LRA_HTTP_CONTEXT_HEADER_NAME = HeaderNames.create(LRA_HTTP_CONTEXT_HEADER);
- static final HeaderName LRA_HTTP_ENDED_CONTEXT_HEADER_NAME = HeaderNames.create(LRA_HTTP_ENDED_CONTEXT_HEADER);
- static final HeaderName LRA_HTTP_PARENT_CONTEXT_HEADER_NAME = HeaderNames.create(LRA_HTTP_PARENT_CONTEXT_HEADER);
- static final HeaderName LRA_HTTP_RECOVERY_HEADER_NAME = HeaderNames.create(LRA_HTTP_RECOVERY_HEADER);
-
- private static final System.Logger LOGGER = System.getLogger(Lra.class.getName());
-
- private final LazyValue coordinatorURL;
- private long timeout;
- private URI parentId;
- private final Set compensatorLinks = Collections.synchronizedSet(new HashSet<>());
-
- private final String lraId;
- private final Config config;
-
- private final List children = Collections.synchronizedList(new ArrayList<>());
-
- private final List participants = new CopyOnWriteArrayList<>();
-
- private final AtomicReference status = new AtomicReference<>(LRAStatus.Active);
-
- private final Lock lock = new ReentrantLock();
-
- private boolean isChild;
- private long whenReadyToDelete = 0;
-
- private final MeterRegistry registry = Metrics.globalRegistry();
- private final Counter lraCtr = registry.getOrCreate(Counter.builder("lractr"));
- private final Timer lrfLifeSpanTmr = registry.getOrCreate(Timer.builder("lralifespantmr"));
- private final Timer.Sample lraLifeSpanTmrSample = Timer.start(registry);
-
- Lra(CoordinatorService coordinatorService, String lraUUID, Config config) {
- lraId = lraUUID;
- this.config = config;
- lraCtr.increment();
- coordinatorURL = LazyValue.create(coordinatorService.getCoordinatorURL());
- }
-
- Lra(CoordinatorService coordinatorService, String lraUUID, URI parentId, Config config) {
- lraId = lraUUID;
- this.parentId = parentId;
- this.config = config;
- lraCtr.increment();
- coordinatorURL = LazyValue.create(coordinatorService.getCoordinatorURL());
- }
-
- String lraId() {
- return lraId;
- }
-
- String parentId() {
- return Optional.ofNullable(parentId).map(URI::toASCIIString).orElse(null);
- }
-
- boolean isChild() {
- return isChild;
- }
-
- void setChild(boolean child) {
- isChild = child;
- }
-
- long getTimeout() {
- return timeout;
- }
-
- void setStatus(LRAStatus status) {
- this.status.set(status);
- }
-
- long getWhenReadyToDelete() {
- return this.whenReadyToDelete;
- }
-
- void setWhenReadyToDelete(long whenReadyToDelete) {
- this.whenReadyToDelete = whenReadyToDelete;
- }
-
- void setTimeout(long timeout) {
- this.timeout = timeout;
- }
-
- List getParticipants() {
- return this.participants;
- }
-
- void setupTimeout(long timeLimit) {
- if (timeLimit != 0) {
- this.timeout = System.currentTimeMillis() + timeLimit;
- } else {
- this.timeout = 0;
- }
- }
-
- boolean checkTimeout() {
- return timeout > 0 && timeout < System.currentTimeMillis();
- }
-
- void addParticipant(String compensatorLink) {
- if (compensatorLinks.add(compensatorLink)) {
- Participant participant = new Participant(config);
- participant.parseCompensatorLinks(compensatorLink);
- participants.add(participant);
- }
- }
-
- void removeParticipant(String compensatorUrl) {
- Set forRemove = participants.stream()
- .filter(p -> p.equalCompensatorUris(compensatorUrl))
- .collect(Collectors.toSet());
- forRemove.forEach(participants::remove);
- }
-
- void addChild(Lra lra) {
- children.add(lra);
- lra.isChild = true;
- }
-
- Consumer headers() {
- return headers -> {
- headers.add(LRA_HTTP_CONTEXT_HEADER_NAME, lraContextId());
- headers.add(LRA_HTTP_ENDED_CONTEXT_HEADER_NAME, lraContextId());
- Optional.ofNullable(parentId)
- .map(URI::toASCIIString)
- .ifPresent(s -> headers.add(LRA_HTTP_PARENT_CONTEXT_HEADER_NAME, s));
- headers.add(LRA_HTTP_RECOVERY_HEADER_NAME, lraContextId() + "/recovery");
- };
- }
-
- void close() {
- Set allowedStatuses = Set.of(LRAStatus.Active, LRAStatus.Closing);
- if (LRAStatus.Closing != status.updateAndGet(old -> allowedStatuses.contains(old) ? LRAStatus.Closing : old)) {
- LOGGER.log(Level.WARNING, "Can't close LRA, it's already " + status.get().name() + " " + this.lraId);
- return;
- }
- lraLifeSpanTmrSample.stop(lrfLifeSpanTmr);
- if (lock.tryLock()) {
- try {
- sendComplete();
- // needs to go before nested close, so we know if nested was already closed
- // or not(non closed nested can't get @Forget call)
- forgetNested();
- for (Lra nestedLra : children) {
- nestedLra.close();
- }
- trySendAfterLRA();
- markForDeletion();
- } finally {
- lock.unlock();
- }
- }
- }
-
- void cancel() {
- Set allowedStatuses = Set.of(LRAStatus.Active, LRAStatus.Cancelling);
- if (LRAStatus.Cancelling != status.updateAndGet(old -> allowedStatuses.contains(old) ? LRAStatus.Cancelling : old)
- && !isChild) { // nested can be compensated even if closed
- LOGGER.log(Level.WARNING, "Can't cancel LRA, it's already " + status.get().name() + " " + this.lraId);
- return;
- }
- lraLifeSpanTmrSample.stop(lrfLifeSpanTmr);
- for (Lra nestedLra : children) {
- nestedLra.cancel();
- }
- if (lock.tryLock()) {
- try {
- sendCancel();
- trySendAfterLRA();
- trySendForgetLRA();
- markForDeletion();
- } finally {
- lock.unlock();
- }
- }
- }
-
- void timeout() {
- for (Lra nestedLra : children) {
- if (nestedLra.participants.stream().anyMatch(p -> p.state().isFinal() || p.isListenerOnly())) {
- nestedLra.timeout();
- }
- }
- cancel();
- if (lock.tryLock()) {
- try {
- trySendAfterLRA();
- } finally {
- lock.unlock();
- }
- }
- }
-
- private boolean forgetNested() {
- for (Lra nestedLra : children) {
- //dont do forget not yet closed nested lra
- if (nestedLra.status.get() != LRAStatus.Closed) continue;
- boolean allDone = true;
- for (Participant participant : nestedLra.participants) {
- if (participant.getForgetURI().isEmpty() || participant.isForgotten()) continue;
- allDone = participant.sendForget(nestedLra) && allDone;
- }
- if (!allDone) return false;
- }
- return true;
- }
-
-
- boolean tryAfter() {
- if (lock.tryLock()) {
- try {
- return trySendAfterLRA();
- } finally {
- lock.unlock();
- }
- }
- return false;
- }
-
- boolean tryForget() {
- if (lock.tryLock()) {
- try {
- return trySendForgetLRA();
- } finally {
- lock.unlock();
- }
- }
- return false;
- }
-
- private boolean trySendForgetLRA() {
- boolean allFinished = true;
- for (Participant participant : participants) {
- if (participant.getForgetURI().isEmpty() || participant.isForgotten()) continue;
- if (Set.of(
- Participant.Status.FAILED_TO_COMPLETE,
- Participant.Status.FAILED_TO_COMPENSATE
- ).contains(participant.state())) {
- allFinished = participant.sendForget(this) && allFinished;
- }
- }
- return allFinished;
- }
-
- AtomicReference status() {
- return status;
- }
-
- String lraContextId() {
- return coordinatorURL.get().toASCIIString() + "/" + lraId;
- }
-
- private void sendComplete() {
- boolean allClosed = true;
- for (Participant participant : participants) {
- if (participant.isInEndStateOrListenerOnly() && !isChild) {
- continue;
- }
- allClosed = participant.sendComplete(this) && allClosed;
- }
- if (allClosed) {
- this.status().compareAndSet(LRAStatus.Closing, LRAStatus.Closed);
- }
- }
-
- private void sendCancel() {
- boolean allDone = true;
- for (Participant participant : participants) {
- if (participant.isInEndStateOrListenerOnly() && !isChild) {
- continue;
- }
- allDone = participant.sendCancel(this) && allDone;
- }
- if (allDone) {
- this.status().compareAndSet(LRAStatus.Cancelling, LRAStatus.Cancelled);
- }
- }
-
- private boolean trySendAfterLRA() {
- boolean allSent = true;
- for (Participant participant : participants) {
- allSent = participant.trySendAfterLRA(this) && allSent;
- }
- return allSent;
- }
-
- boolean isReadyToDelete() {
- return whenReadyToDelete != 0 && whenReadyToDelete < System.currentTimeMillis();
- }
-
- void markForDeletion() {
- // delete after 10 minutes
- whenReadyToDelete = (10 * 60 * 1000) + System.currentTimeMillis();
- }
+/**
+ * Long Running Action managed by coordinator.
+ */
+public interface Lra {
+ /**
+ * LRA header name.
+ */
+ HeaderName LRA_HTTP_CONTEXT_HEADER_NAME = HeaderNames.create(LRA_HTTP_CONTEXT_HEADER);
+ /**
+ * LRA ended header name.
+ */
+ HeaderName LRA_HTTP_ENDED_CONTEXT_HEADER_NAME = HeaderNames.create(LRA_HTTP_ENDED_CONTEXT_HEADER);
+ /**
+ * LRA parent header name.
+ */
+ HeaderName LRA_HTTP_PARENT_CONTEXT_HEADER_NAME = HeaderNames.create(LRA_HTTP_PARENT_CONTEXT_HEADER);
+ /**
+ * LRA recovery header name.
+ */
+ HeaderName LRA_HTTP_RECOVERY_HEADER_NAME = HeaderNames.create(LRA_HTTP_RECOVERY_HEADER);
+
+ /**
+ * ID of the LRA used by this coordinator.
+ *
+ * @return lraId without coordinator URI prefix
+ */
+ String lraId();
+
+ /**
+ * LRA ID of the parent LRA if this LRA has any.
+ *
+ * @return id of parent LRA or null
+ */
+ String parentId();
+
+ /**
+ * Returns true if this LRA has parent LRA.
+ *
+ * @return true if LRA has parent
+ */
+ boolean isChild();
+
+ /**
+ * Returns exact time when will LRA timeout in millis.
+ *
+ * @return time of timeout in millis
+ */
+ long timeout();
+
+ /**
+ * All participants enrolled in this LRA.
+ *
+ * @return list of participants enrolled in this LRA
+ */
+ List participants();
+
+ /**
+ * Status of this LRA.
+ *
+ * @return status
+ */
+ LRAStatus status();
}
diff --git a/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/LraDatabasePersistentRegistry.java b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/LraDatabasePersistentRegistry.java
index c38ef83539d..b3b270a54cb 100644
--- a/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/LraDatabasePersistentRegistry.java
+++ b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/LraDatabasePersistentRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,9 +19,9 @@
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
@@ -35,46 +35,46 @@
class LraDatabasePersistentRegistry implements LraPersistentRegistry {
- private final Map lraMap = Collections.synchronizedMap(new HashMap<>());
- private final Config config;
- private final DbClient dbClient;
private static final Pattern LRA_ID_PATTERN = Pattern.compile(".*/([^/?]+).*");
+ private final Map lraMap = Collections.synchronizedMap(new HashMap<>());
+ private final Config config;
+ private final Supplier dbClient;
+ private final Boolean persistent;
LraDatabasePersistentRegistry(Config config) {
this.config = config;
- this.dbClient = DbClient.builder()
- .config(config.get("db"))
- .build();
-
- DbTransaction tx = dbClient.transaction();
- tx.namedDml("create-lra-table");
- tx.namedDml("create-participant-table");
- tx.commit();
- }
-
- @Override
- public Lra get(String lraId) {
- return lraMap.get(lraId);
- }
-
- @Override
- public void put(String key, Lra lra) {
- lraMap.put(key, lra);
- }
-
- @Override
- public void remove(String key) {
- lraMap.remove(key);
+ this.persistent = config.get("persistence").asBoolean().orElse(true);
+ if (persistent) {
+ this.dbClient = () -> DbClient.builder()
+ .config(config.get("db"))
+ .build();
+ DbTransaction tx = dbClient.get().transaction();
+ tx.namedDml("create-lra-table");
+ tx.namedDml("create-participant-table");
+ tx.commit();
+ } else {
+ this.dbClient = () -> {
+ throw new IllegalStateException("Persistence is not enabled");
+ };
+ }
}
- @Override
- public Stream stream() {
- return lraMap.values().stream();
+ static String parseLRAId(String lraUri) {
+ Matcher m = LRA_ID_PATTERN.matcher(lraUri);
+ if (!m.matches()) {
+ //LRA uri format
+ throw new RuntimeException("Error when parsing lraUri: " + lraUri);
+ }
+ return m.group(1);
}
@Override
public void load(CoordinatorService coordinatorService) {
- DbExecute tx = dbClient.execute();
+ if (!persistent) {
+ return;
+ }
+
+ DbExecute tx = dbClient.get().execute();
tx.namedQuery("load").forEach(row -> {
String lraId = row.column("ID").get(String.class);
String parentId = row.column("PARENT_ID").get(String.class);
@@ -96,11 +96,11 @@ public void load(CoordinatorService coordinatorService) {
Integer remainingCloseAttempts = row.column("REMAINING_CLOSE_ATTEMPTS").get(Integer.class);
Integer remainingAfterAttempts = row.column("REMAINING_AFTER_ATTEMPTS").get(Integer.class);
- Lra lra = lraMap.get(lraId);
+ LraImpl lra = lraMap.get(lraId);
if (lra == null) {
- lra = new Lra(coordinatorService, lraId,
- Optional.ofNullable(parentId).map(URI::create).orElse(null),
- config);
+ lra = new LraImpl(coordinatorService, lraId,
+ Optional.ofNullable(parentId).map(URI::create).orElse(null),
+ config);
lra.setTimeout(timeout);
lra.setStatus(LRAStatus.valueOf(lraStatus));
lra.setChild(isChild);
@@ -108,22 +108,21 @@ public void load(CoordinatorService coordinatorService) {
}
if (participantStatus != null) {
- Participant participant = new Participant(config);
- participant.setCompleteURI(Optional.ofNullable(completeLink).map(URI::create).orElse(null));
- participant.setCompensateURI(Optional.ofNullable(compensateLink).map(URI::create).orElse(null));
- participant.setAfterURI(Optional.ofNullable(afterLink).map(URI::create).orElse(null));
- participant.setForgetURI(Optional.ofNullable(forgetLink).map(URI::create).orElse(null));
- participant.setStatusURI(Optional.ofNullable(statusLink).map(URI::create).orElse(null));
- participant.setStatus(Participant.Status.valueOf(participantStatus));
- participant.setCompensateStatus(Participant.CompensateStatus.valueOf(compensateStatus));
- participant.setForgetStatus(Participant.ForgetStatus.valueOf(forgetStatus));
- participant.setAfterLraStatus(Participant.AfterLraStatus.valueOf(afterStatus));
- participant.setSendingStatus(Participant.SendingStatus.valueOf(sendingStatus));
- participant.setRemainingCloseAttempts(remainingCloseAttempts);
- participant.setRemainingAfterAttempts(remainingAfterAttempts);
-
- List participants = lra.getParticipants();
- participants.add(participant);
+ ParticipantImpl participant = new ParticipantImpl(config);
+ participant.completeURI(Optional.ofNullable(completeLink).map(URI::create).orElse(null));
+ participant.compensateURI(Optional.ofNullable(compensateLink).map(URI::create).orElse(null));
+ participant.afterURI(Optional.ofNullable(afterLink).map(URI::create).orElse(null));
+ participant.forgetURI(Optional.ofNullable(forgetLink).map(URI::create).orElse(null));
+ participant.statusURI(Optional.ofNullable(statusLink).map(URI::create).orElse(null));
+ participant.status(ParticipantImpl.Status.valueOf(participantStatus));
+ participant.compensateStatus(ParticipantImpl.CompensateStatus.valueOf(compensateStatus));
+ participant.forgetStatus(ParticipantImpl.ForgetStatus.valueOf(forgetStatus));
+ participant.afterLraStatus(ParticipantImpl.AfterLraStatus.valueOf(afterStatus));
+ participant.sendingStatus(ParticipantImpl.SendingStatus.valueOf(sendingStatus));
+ participant.remainingCloseAttempts(remainingCloseAttempts);
+ participant.remainingAfterAttempts(remainingAfterAttempts);
+
+ lra.addParticipant(participant);
}
lraMap.put(lraId, lra);
});
@@ -139,44 +138,68 @@ public void load(CoordinatorService coordinatorService) {
@Override
public void save() {
- DbTransaction tx = dbClient.transaction();
+ if (!persistent) {
+ return;
+ }
+
+ DbTransaction tx = dbClient.get().transaction();
cleanUp(tx);
saveAll(tx);
tx.commit();
}
+ @Override
+ public LraImpl get(String lraId) {
+ return lraMap.get(lraId);
+ }
+
+ @Override
+ public void put(String key, LraImpl lra) {
+ lraMap.put(key, lra);
+ }
+
+ @Override
+ public void remove(String key) {
+ lraMap.remove(key);
+ }
+
+ @Override
+ public Stream stream() {
+ return lraMap.values().stream();
+ }
+
private void saveAll(DbTransaction tx) {
lraMap.values().forEach(lra -> insertLra(tx, lra));
}
- private void insertLra(DbTransaction tx, Lra lra) {
+ private void insertLra(DbTransaction tx, LraImpl lra) {
tx.namedInsert("insert-lra",
- lra.lraId(),
- lra.parentId(),
- lra.getTimeout(),
- lra.status().get().name(),
- lra.isChild(),
- lra.getWhenReadyToDelete());
+ lra.lraId(),
+ lra.parentId(),
+ lra.timeout(),
+ lra.lraStatus().get().name(),
+ lra.isChild(),
+ lra.getWhenReadyToDelete());
// save all participants of the lra
- lra.getParticipants().forEach(participant -> insertParticipant(tx, lra, participant));
+ lra.participants().forEach(participant -> insertParticipant(tx, lra, (ParticipantImpl) participant));
}
- private void insertParticipant(DbTransaction tx, Lra lra, Participant p) {
+ private void insertParticipant(DbTransaction tx, LraImpl lra, ParticipantImpl p) {
tx.namedInsert("insert-participant",
- lra.lraId(),
- p.state().name(),
- p.getCompensateStatus().name(),
- p.getForgetStatus().name(),
- p.getAfterLraStatus().name(),
- p.getSendingStatus().name(),
- p.getRemainingCloseAttempts(),
- p.getRemainingAfterAttempts(),
- p.getCompleteURI().map(URI::toASCIIString).orElse(null),
- p.getCompensateURI().map(URI::toASCIIString).orElse(null),
- p.getAfterURI().map(URI::toASCIIString).orElse(null),
- p.getForgetURI().map(URI::toASCIIString).orElse(null),
- p.getStatusURI().map(URI::toASCIIString).orElse(null));
+ lra.lraId(),
+ p.state().name(),
+ p.compensateStatus().name(),
+ p.forgetStatus().name(),
+ p.afterLraStatus().name(),
+ p.sendingStatus().name(),
+ p.remainingCloseAttempts(),
+ p.remainingAfterAttempts(),
+ p.completeURI().map(URI::toASCIIString).orElse(null),
+ p.compensateURI().map(URI::toASCIIString).orElse(null),
+ p.afterURI().map(URI::toASCIIString).orElse(null),
+ p.forgetURI().map(URI::toASCIIString).orElse(null),
+ p.statusURI().map(URI::toASCIIString).orElse(null));
}
private void cleanUp(DbTransaction tx) {
@@ -184,13 +207,4 @@ private void cleanUp(DbTransaction tx) {
tx.namedDelete("delete-all-participants");
}
- static String parseLRAId(String lraUri) {
- Matcher m = LRA_ID_PATTERN.matcher(lraUri);
- if (!m.matches()) {
- //LRA uri format
- throw new RuntimeException("Error when parsing lraUri: " + lraUri);
- }
- return m.group(1);
- }
-
}
diff --git a/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/LraImpl.java b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/LraImpl.java
new file mode 100644
index 00000000000..abc47abf143
--- /dev/null
+++ b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/LraImpl.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.lra.coordinator;
+
+import java.lang.System.Logger.Level;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import io.helidon.common.LazyValue;
+import io.helidon.config.Config;
+import io.helidon.http.ClientRequestHeaders;
+import io.helidon.metrics.api.Counter;
+import io.helidon.metrics.api.MeterRegistry;
+import io.helidon.metrics.api.Metrics;
+import io.helidon.metrics.api.Timer;
+
+import org.eclipse.microprofile.lra.annotation.LRAStatus;
+
+class LraImpl implements Lra {
+
+ private static final System.Logger LOGGER = System.getLogger(LraImpl.class.getName());
+
+ private final LazyValue coordinatorURL;
+ private final Set compensatorLinks = Collections.synchronizedSet(new HashSet<>());
+ private final String lraId;
+ private final Config config;
+ private final List children = Collections.synchronizedList(new ArrayList<>());
+ private final List participants = new CopyOnWriteArrayList<>();
+ private final AtomicReference status = new AtomicReference<>(LRAStatus.Active);
+ private final Lock lock = new ReentrantLock();
+ private final MeterRegistry registry = Metrics.globalRegistry();
+ private final Counter lraCtr = registry.getOrCreate(Counter.builder("lractr"));
+ private final Timer lrfLifeSpanTmr = registry.getOrCreate(Timer.builder("lralifespantmr"));
+ private final Timer.Sample lraLifeSpanTmrSample = Timer.start(registry);
+ private long timeout;
+ private URI parentId;
+ private boolean isChild;
+ private long whenReadyToDelete = 0;
+
+ LraImpl(CoordinatorService coordinatorService, String lraUUID, Config config) {
+ lraId = lraUUID;
+ this.config = config;
+ lraCtr.increment();
+ coordinatorURL = LazyValue.create(coordinatorService.coordinatorURL());
+ }
+
+ LraImpl(CoordinatorService coordinatorService, String lraUUID, URI parentId, Config config) {
+ lraId = lraUUID;
+ this.parentId = parentId;
+ this.config = config;
+ lraCtr.increment();
+ coordinatorURL = LazyValue.create(coordinatorService.coordinatorURL());
+ }
+
+ @Override
+ public String lraId() {
+ return lraId;
+ }
+
+ @Override
+ public String parentId() {
+ return Optional.ofNullable(parentId).map(URI::toASCIIString).orElse(null);
+ }
+
+ @Override
+ public boolean isChild() {
+ return isChild;
+ }
+
+ void setChild(boolean child) {
+ isChild = child;
+ }
+
+ @Override
+ public long timeout() {
+ return timeout;
+ }
+
+ void setTimeout(long timeout) {
+ this.timeout = timeout;
+ }
+
+ @Override
+ public List participants() {
+ return this.participants.stream().map(Participant.class::cast).toList();
+ }
+
+ @Override
+ public LRAStatus status() {
+ return lraStatus().get();
+ }
+
+ void setStatus(LRAStatus status) {
+ this.status.set(status);
+ }
+
+ long getWhenReadyToDelete() {
+ return this.whenReadyToDelete;
+ }
+
+ void setWhenReadyToDelete(long whenReadyToDelete) {
+ this.whenReadyToDelete = whenReadyToDelete;
+ }
+
+ void addParticipant(ParticipantImpl participant) {
+ this.participants.add(participant);
+ }
+
+ void setupTimeout(long timeLimit) {
+ if (timeLimit != 0) {
+ this.timeout = System.currentTimeMillis() + timeLimit;
+ } else {
+ this.timeout = 0;
+ }
+ }
+
+ boolean checkTimeout() {
+ return timeout > 0 && timeout < System.currentTimeMillis();
+ }
+
+ void addParticipant(String compensatorLink) {
+ if (compensatorLinks.add(compensatorLink)) {
+ ParticipantImpl participant = new ParticipantImpl(config);
+ participant.parseCompensatorLinks(compensatorLink);
+ participants.add(participant);
+ }
+ }
+
+ void removeParticipant(String compensatorUrl) {
+ Set forRemove = participants.stream()
+ .filter(p -> p.equalCompensatorUris(compensatorUrl))
+ .collect(Collectors.toSet());
+ forRemove.forEach(participants::remove);
+ }
+
+ void addChild(LraImpl lra) {
+ children.add(lra);
+ lra.isChild = true;
+ }
+
+ Consumer headers() {
+ return headers -> {
+ headers.add(LRA_HTTP_CONTEXT_HEADER_NAME, lraContextId());
+ headers.add(LRA_HTTP_ENDED_CONTEXT_HEADER_NAME, lraContextId());
+ Optional.ofNullable(parentId)
+ .map(URI::toASCIIString)
+ .ifPresent(s -> headers.add(LRA_HTTP_PARENT_CONTEXT_HEADER_NAME, s));
+ headers.add(LRA_HTTP_RECOVERY_HEADER_NAME, lraContextId() + "/recovery");
+ };
+ }
+
+ void close() {
+ Set allowedStatuses = Set.of(LRAStatus.Active, LRAStatus.Closing);
+ if (LRAStatus.Closing != status.updateAndGet(old -> allowedStatuses.contains(old) ? LRAStatus.Closing : old)) {
+ LOGGER.log(Level.WARNING, "Can't close LRA, it's already " + status.get().name() + " " + this.lraId);
+ return;
+ }
+ lraLifeSpanTmrSample.stop(lrfLifeSpanTmr);
+ if (lock.tryLock()) {
+ try {
+ sendComplete();
+ // needs to go before nested close, so we know if nested was already closed
+ // or not(non closed nested can't get @Forget call)
+ forgetNested();
+ for (LraImpl nestedLra : children) {
+ nestedLra.close();
+ }
+ trySendAfterLRA();
+ markForDeletion();
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ void cancel() {
+ Set allowedStatuses = Set.of(LRAStatus.Active, LRAStatus.Cancelling);
+ if (LRAStatus.Cancelling != status.updateAndGet(old -> allowedStatuses.contains(old) ? LRAStatus.Cancelling : old)
+ && !isChild) { // nested can be compensated even if closed
+ LOGGER.log(Level.WARNING, "Can't cancel LRA, it's already " + status.get().name() + " " + this.lraId);
+ return;
+ }
+ lraLifeSpanTmrSample.stop(lrfLifeSpanTmr);
+ for (LraImpl nestedLra : children) {
+ nestedLra.cancel();
+ }
+ if (lock.tryLock()) {
+ try {
+ sendCancel();
+ trySendAfterLRA();
+ trySendForgetLRA();
+ markForDeletion();
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ void triggerTimeout() {
+ for (LraImpl nestedLra : children) {
+ if (nestedLra.participants.stream().anyMatch(p -> p.state().isFinal() || p.isListenerOnly())) {
+ nestedLra.triggerTimeout();
+ }
+ }
+ cancel();
+ if (lock.tryLock()) {
+ try {
+ trySendAfterLRA();
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ boolean tryAfter() {
+ if (lock.tryLock()) {
+ try {
+ return trySendAfterLRA();
+ } finally {
+ lock.unlock();
+ }
+ }
+ return false;
+ }
+
+ boolean tryForget() {
+ if (lock.tryLock()) {
+ try {
+ return trySendForgetLRA();
+ } finally {
+ lock.unlock();
+ }
+ }
+ return false;
+ }
+
+ AtomicReference lraStatus() {
+ return status;
+ }
+
+ String lraContextId() {
+ return coordinatorURL.get().toASCIIString() + "/" + lraId;
+ }
+
+ boolean isReadyToDelete() {
+ return whenReadyToDelete != 0 && whenReadyToDelete < System.currentTimeMillis();
+ }
+
+ void markForDeletion() {
+ // delete after 10 minutes
+ whenReadyToDelete = (10 * 60 * 1000) + System.currentTimeMillis();
+ }
+
+ private boolean forgetNested() {
+ for (LraImpl nestedLra : children) {
+ //don't do forget not yet closed nested lra
+ if (nestedLra.status.get() != LRAStatus.Closed) {
+ continue;
+ }
+ boolean allDone = true;
+ for (ParticipantImpl participant : nestedLra.participants) {
+ if (participant.forgetURI().isEmpty() || participant.isForgotten()) {
+ continue;
+ }
+ allDone = participant.sendForget(nestedLra) && allDone;
+ }
+ if (!allDone) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean trySendForgetLRA() {
+ boolean allFinished = true;
+ for (ParticipantImpl participant : participants) {
+ if (participant.forgetURI().isEmpty() || participant.isForgotten()) {
+ continue;
+ }
+ if (Set.of(
+ ParticipantImpl.Status.FAILED_TO_COMPLETE,
+ ParticipantImpl.Status.FAILED_TO_COMPENSATE
+ ).contains(participant.state())) {
+ allFinished = participant.sendForget(this) && allFinished;
+ }
+ }
+ return allFinished;
+ }
+
+ private void sendComplete() {
+ boolean allClosed = true;
+ for (ParticipantImpl participant : participants) {
+ if (participant.isInEndStateOrListenerOnly() && !isChild) {
+ continue;
+ }
+ allClosed = participant.sendComplete(this) && allClosed;
+ }
+ if (allClosed) {
+ this.lraStatus().compareAndSet(LRAStatus.Closing, LRAStatus.Closed);
+ }
+ }
+
+ private void sendCancel() {
+ boolean allDone = true;
+ for (ParticipantImpl participant : participants) {
+ if (participant.isInEndStateOrListenerOnly() && !isChild) {
+ continue;
+ }
+ allDone = participant.sendCancel(this) && allDone;
+ }
+ if (allDone) {
+ this.lraStatus().compareAndSet(LRAStatus.Cancelling, LRAStatus.Cancelled);
+ }
+ }
+
+ private boolean trySendAfterLRA() {
+ boolean allSent = true;
+ for (ParticipantImpl participant : participants) {
+ allSent = participant.trySendAfterLRA(this) && allSent;
+ }
+ return allSent;
+ }
+}
diff --git a/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/LraPersistentRegistry.java b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/LraPersistentRegistry.java
index d0aae2c8a2e..1d765a5f561 100644
--- a/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/LraPersistentRegistry.java
+++ b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/LraPersistentRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +39,7 @@ interface LraPersistentRegistry {
* @param lraId to look for
* @return lra if exist
*/
- Lra get(String lraId);
+ LraImpl get(String lraId);
/**
* Add new Lra.
@@ -47,7 +47,7 @@ interface LraPersistentRegistry {
* @param lraId id of new lra
* @param lra Lra
*/
- void put(String lraId, Lra lra);
+ void put(String lraId, LraImpl lra);
/**
* Remove lra by id.
@@ -61,6 +61,6 @@ interface LraPersistentRegistry {
*
* @return stream of all the Lras
*/
- Stream stream();
+ Stream stream();
}
diff --git a/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/Participant.java b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/Participant.java
index 8ce4f5a3309..2514923fc17 100644
--- a/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/Participant.java
+++ b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/Participant.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2023 Oracle and/or its affiliates.
+ * 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.
@@ -13,577 +13,49 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package io.helidon.lra.coordinator;
-import java.lang.System.Logger.Level;
import java.net.URI;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import io.helidon.config.Config;
-import io.helidon.webclient.api.HttpClientResponse;
-import io.helidon.webclient.api.WebClient;
-
-import org.eclipse.microprofile.lra.annotation.LRAStatus;
-import org.eclipse.microprofile.lra.annotation.ParticipantStatus;
-
-import static io.helidon.lra.coordinator.Lra.LRA_HTTP_CONTEXT_HEADER_NAME;
-import static io.helidon.lra.coordinator.Lra.LRA_HTTP_ENDED_CONTEXT_HEADER_NAME;
-import static io.helidon.lra.coordinator.Lra.LRA_HTTP_RECOVERY_HEADER_NAME;
-import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.Active;
-import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.Compensated;
-import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.Compensating;
-import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.Completed;
-import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.Completing;
-import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.FailedToCompensate;
-import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.FailedToComplete;
-import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.valueOf;
-
-class Participant {
-
- private static final int RETRY_CNT = 60;
- private static final int SYNCHRONOUS_RETRY_CNT = 5;
-
- private static final System.Logger LOGGER = System.getLogger(Participant.class.getName());
- private final AtomicReference compensateCalled = new AtomicReference<>(CompensateStatus.NOT_SENT);
- private final AtomicReference forgetCalled = new AtomicReference<>(ForgetStatus.NOT_SENT);
- private final AtomicReference afterLRACalled = new AtomicReference<>(AfterLraStatus.NOT_SENT);
- private final AtomicReference sendingStatus = new AtomicReference<>(SendingStatus.NOT_SENDING);
- private final AtomicInteger remainingCloseAttempts = new AtomicInteger(RETRY_CNT);
- private final AtomicInteger remainingAfterLraAttempts = new AtomicInteger(RETRY_CNT);
-
- private final AtomicReference status = new AtomicReference<>(Status.ACTIVE);
- private final Map compensatorLinks = new HashMap<>();
- private final long timeout;
- private final WebClient webClient = WebClient.builder().build();
-
- enum Status {
- ACTIVE(Active, null, null, false, Set.of(Completing, Compensating)),
-
- COMPENSATED(Compensated, null, null, true, Set.of(Compensated)),
- COMPLETED(Completed, null, null, true, Set.of(Completed)),
- FAILED_TO_COMPENSATE(FailedToCompensate, null, null, true, Set.of()),
- FAILED_TO_COMPLETE(FailedToComplete, null, null, true, Set.of()),
-
- CLIENT_COMPENSATING(Compensating, COMPENSATED, FAILED_TO_COMPENSATE, false,
- Set.of(Active, Compensated, FailedToCompensate)),
- CLIENT_COMPLETING(Completing, COMPLETED, FAILED_TO_COMPLETE, false, Set.of(Active, Completed, FailedToComplete)),
- COMPENSATING(Compensating, COMPENSATED, FAILED_TO_COMPENSATE, false, Set.of(Active, Compensated, FailedToCompensate)),
- COMPLETING(Completing, COMPLETED, FAILED_TO_COMPLETE, false, Set.of(Active, Completed, FailedToComplete));
-
- private final ParticipantStatus participantStatus;
- private final Status successFinalStatus;
- private final Status failedFinalStatus;
- private final boolean finalState;
- private final Set validNextStates;
-
- Status(ParticipantStatus participantStatus,
- Status successFinalStatus,
- Status failedFinalStatus,
- boolean finalState,
- Set validNextStates) {
- this.participantStatus = participantStatus;
- this.successFinalStatus = successFinalStatus;
- this.failedFinalStatus = failedFinalStatus;
- this.finalState = finalState;
- this.validNextStates = validNextStates;
- }
-
- ParticipantStatus participantStatus() {
- return participantStatus;
- }
-
- boolean isFinal() {
- return finalState;
- }
-
- boolean validateNextStatus(ParticipantStatus participantStatus) {
- return validNextStates.contains(participantStatus);
- }
-
- Optional successFinalStatus() {
- return Optional.ofNullable(successFinalStatus.participantStatus());
- }
-
- Optional failedFinalStatus() {
- return Optional.ofNullable(failedFinalStatus.participantStatus);
- }
- }
- enum SendingStatus {
- SENDING, NOT_SENDING;
- }
-
- enum AfterLraStatus {
- NOT_SENT, SENDING, SENT;
- }
-
- enum ForgetStatus {
- NOT_SENT, SENDING, SENT;
- }
-
- enum CompensateStatus {
- NOT_SENT, SENDING, SENT;
- }
-
- Participant(Config config) {
- timeout = config.get("timeout")
- .asLong()
- .orElse(500L);
- }
-
- void parseCompensatorLinks(String compensatorLinks) {
- Stream.of(compensatorLinks.split(","))
- .filter(s -> !s.isBlank())
- .map(Link::valueOf)
- .forEach(link -> this.compensatorLinks.put(link.rel(), link.uri()));
- }
-
- Optional getCompensatorLink(String rel) {
- return Optional.ofNullable(compensatorLinks.get(rel));
- }
+/**
+ * LRA participant managed by coordinator.
+ */
+public interface Participant {
/**
* Invoked when closed 200, 202, 409, 410.
+ *
+ * @return optional uri
*/
- Optional getCompleteURI() {
- return getCompensatorLink("complete");
- }
-
- void setCompleteURI(URI completeURI) {
- compensatorLinks.put("complete", completeURI);
- }
+ Optional completeURI();
/**
* Invoked when cancelled 200, 202, 409, 410.
+ *
+ * @return optional uri
*/
- Optional getCompensateURI() {
- return getCompensatorLink("compensate");
- }
-
- void setCompensateURI(URI compensateURI) {
- compensatorLinks.put("compensate", compensateURI);
- }
+ Optional compensateURI();
/**
* Invoked when finalized 200.
+ *
+ * @return optional uri
*/
- Optional getAfterURI() {
- return getCompensatorLink("after");
- }
-
- void setAfterURI(URI afterURI) {
- compensatorLinks.put("after", afterURI);
- }
+ Optional afterURI();
/**
* Invoked when cleaning up 200, 410.
+ *
+ * @return optional uri
*/
- Optional getForgetURI() {
- return getCompensatorLink("forget");
- }
-
- void setForgetURI(URI forgetURI) {
- compensatorLinks.put("forget", forgetURI);
- }
+ Optional forgetURI();
/**
* Directly updates status of participant 200, 202, 410.
+ *
+ * @return optional uri
*/
- Optional getStatusURI() {
- return getCompensatorLink("status");
- }
-
- void setStatusURI(URI statusURI) {
- compensatorLinks.put("status", statusURI);
- }
-
- CompensateStatus getCompensateStatus() {
- return this.compensateCalled.get();
- }
-
- void setCompensateStatus(CompensateStatus compensateStatus) {
- this.compensateCalled.set(compensateStatus);
- }
-
- void setStatus(Status status) {
- this.status.set(status);
- }
-
- ForgetStatus getForgetStatus() {
- return this.forgetCalled.get();
- }
-
- void setForgetStatus(ForgetStatus forgetStatus) {
- this.forgetCalled.set(forgetStatus);
- }
-
- AfterLraStatus getAfterLraStatus() {
- return this.afterLRACalled.get();
- }
-
- void setAfterLraStatus(AfterLraStatus afterLraStatus) {
- this.afterLRACalled.set(afterLraStatus);
- }
-
- SendingStatus getSendingStatus() {
- return this.sendingStatus.get();
- }
-
- void setSendingStatus(SendingStatus sendingStatus) {
- this.sendingStatus.set(sendingStatus);
- }
-
- int getRemainingCloseAttempts() {
- return this.remainingCloseAttempts.get();
- }
-
- void setRemainingCloseAttempts(int remainingCloseAttempts) {
- this.remainingCloseAttempts.set(remainingCloseAttempts);
- }
-
- int getRemainingAfterAttempts() {
- return this.remainingAfterLraAttempts.get();
- }
-
- void setRemainingAfterAttempts(int remainingAfterAttempts) {
- this.remainingAfterLraAttempts.set(remainingAfterAttempts);
- }
-
- Status state() {
- return status.get();
- }
-
- boolean isForgotten() {
- return forgetCalled.get() == ForgetStatus.SENT;
- }
-
- boolean isListenerOnly() {
- return getCompleteURI().isEmpty() && getCompensateURI().isEmpty();
- }
-
- boolean isInEndStateOrListenerOnly() {
- return isListenerOnly() || status.get().isFinal();
- }
-
- boolean sendCancel(Lra lra) {
- Optional endpointURI = getCompensateURI();
- for (AtomicInteger i = new AtomicInteger(0); i.getAndIncrement() < SYNCHRONOUS_RETRY_CNT;) {
- if (!sendingStatus.compareAndSet(SendingStatus.NOT_SENDING, SendingStatus.SENDING)) return false;
- if (!compensateCalled.compareAndSet(CompensateStatus.NOT_SENT, CompensateStatus.SENDING)) return false;
- LOGGER.log(Level.DEBUG, () -> "Sending compensate, sync retry: " + i.get()
- + ", status: " + status.get().name()
- + " statusUri: " + getStatusURI().map(URI::toASCIIString).orElse(null));
- HttpClientResponse response = null;
- try {
- // call for client status only on retries and when status uri is known
- if (!status.get().equals(Status.ACTIVE) && getStatusURI().isPresent()) {
- // If the participant does not support idempotency then it MUST be able to report its status
- // by annotating one of the methods with the @Status annotation which should report the status
- // in case we can't retrieve status from participant just retry n times
- ParticipantStatus reportedClientStatus = retrieveStatus(lra, Compensating).orElse(null);
- if (reportedClientStatus == Compensated) {
- LOGGER.log(Level.INFO, "Participant reports it is compensated.");
- status.set(Status.COMPENSATED);
- return true;
- } else if (reportedClientStatus == FailedToCompensate) {
- LOGGER.log(Level.INFO, "Participant reports it failed to compensate.");
- status.set(Status.FAILED_TO_COMPENSATE);
- return true;
- } else if (reportedClientStatus == Active) {
- // last call didn't reach participant, try call again
- } else if (reportedClientStatus == Completed && lra.isChild()) {
- // completed participant can be compensated again in case of nested tx
- } else if (reportedClientStatus == Compensating) {
- LOGGER.log(Level.INFO, "Participant reports it is still compensating.");
- status.set(Status.CLIENT_COMPENSATING);
- return false;
- } else if (remainingCloseAttempts.decrementAndGet() <= 0) {
- LOGGER.log(Level.INFO, "Participant didnt report final status after {0} status call retries.",
- new Object[] {RETRY_CNT});
- status.set(Status.FAILED_TO_COMPENSATE);
- return true;
- } else {
- // Unknown status, lets try in next recovery cycle
- LOGGER.log(Level.INFO, "Unknown status of " + lra.lraId());
- return false;
- }
- }
-
- response = webClient.put()
- .uri(endpointURI.get())
- .headers(lra.headers())
- .submit(LRAStatus.Cancelled.name());
-
- // When timeout occur we loose track of the participant status
- // next retry will attempt to retrieve participant status if status uri is available
-
- switch (response.status().code()) {
- // complete or compensated
- case 200:
- case 410:
- LOGGER.log(Level.INFO, "Compensated participant of LRA {0} {1}",
- new Object[] {lra.lraId(), this.getCompensateURI()});
- status.set(Status.COMPENSATED);
- compensateCalled.set(CompensateStatus.SENT);
- return true;
-
- // retryable
- case 202:
- // Still compensating, check with @Status later
- this.status.set(Status.CLIENT_COMPENSATING);
- return false;
- case 409:
- case 404:
- case 503:
- default:
- throw new Exception(response.status().code() + " " + response.status().reasonPhrase());
- }
-
- } catch (Exception e) {
- LOGGER.log(Level.WARNING,
- () -> "Can't reach participant's compensate endpoint: "
- + endpointURI.map(URI::toASCIIString).orElse("unknown"), e);
- if (remainingCloseAttempts.decrementAndGet() <= 0) {
- LOGGER.log(Level.WARNING, "Failed to compensate participant of LRA {0} {1} {2}",
- new Object[] {lra.lraId(), this.getCompensateURI(), e.getMessage()});
- status.set(Status.FAILED_TO_COMPENSATE);
- } else {
- status.set(Status.COMPENSATING);
- }
-
- } finally {
- Optional.ofNullable(response).ifPresent(HttpClientResponse::close);
- sendingStatus.set(SendingStatus.NOT_SENDING);
- compensateCalled.compareAndSet(CompensateStatus.SENDING, CompensateStatus.NOT_SENT);
- }
- }
- return false;
- }
-
- boolean sendComplete(Lra lra) {
- Optional endpointURI = getCompleteURI();
- for (AtomicInteger i = new AtomicInteger(0); i.getAndIncrement() < SYNCHRONOUS_RETRY_CNT;) {
- if (!sendingStatus.compareAndSet(SendingStatus.NOT_SENDING, SendingStatus.SENDING)) return false;
- LOGGER.log(Level.DEBUG, () -> "Sending complete, sync retry: " + i.get()
- + ", status: " + status.get().name()
- + " statusUri: " + getStatusURI().map(URI::toASCIIString).orElse(null));
- HttpClientResponse response = null;
- try {
- if (status.get().isFinal()) {
- return true;
- // call for client status only on retries and when status uri is known
- } else if (!status.get().equals(Status.ACTIVE) && getStatusURI().isPresent()) {
- // If the participant does not support idempotency then it MUST be able to report its status
- // by annotating one of the methods with the @Status annotation which should report the status
- // in case we can't retrieve status from participant just retry n times
- ParticipantStatus reportedClientStatus = retrieveStatus(lra, Completing).orElse(null);
- if (reportedClientStatus == Completed) {
- LOGGER.log(Level.INFO, "Participant reports it is completed.");
- status.set(Status.COMPLETED);
- return true;
- } else if (reportedClientStatus == FailedToComplete) {
- LOGGER.log(Level.INFO, "Participant reports it failed to complete.");
- status.set(Status.FAILED_TO_COMPLETE);
- return true;
- } else if (reportedClientStatus == Active) {
- // last call didn't reach participant, try call again
- } else if (reportedClientStatus == Completing) {
- LOGGER.log(Level.INFO, "Participant reports it is still completing.");
- status.set(Status.CLIENT_COMPLETING);
- return false;
- } else if (remainingCloseAttempts.decrementAndGet() <= 0) {
- LOGGER.log(Level.INFO, "Participant didnt report final status after {0} status call retries.",
- new Object[] {RETRY_CNT});
- status.set(Status.FAILED_TO_COMPLETE);
- return true;
- } else {
- // Unknown status, lets try in next recovery cycle
- return false;
- }
- }
- response = webClient.put()
- .uri(endpointURI.get())
- .headers(lra.headers())
- .submit(LRAStatus.Closed.name());
- // When timeout occur we loose track of the participant status
- // next retry will attempt to retrieve participant status if status uri is available
-
- switch (response.status().code()) {
- // complete or compensated
- case 200:
- case 410:
- status.set(Status.COMPLETED);
- return true;
-
- // retryable
- case 202:
- // Still completing, check with @Status later
- this.status.set(Status.CLIENT_COMPLETING);
- return false;
- case 409:
- case 404:
- case 503:
- default:
- throw new Exception(response.status().code() + " " + response.status().reasonPhrase());
- }
-
- } catch (Exception e) {
- LOGGER.log(Level.WARNING,
- () -> "Can't reach participant's complete endpoint: " + endpointURI.map(URI::toASCIIString)
- .orElse("unknown"), e);
- if (remainingCloseAttempts.decrementAndGet() <= 0) {
- LOGGER.log(Level.WARNING, "Failed to complete participant of LRA {0} {1} {2}",
- new Object[]{lra.lraId(),
- this.getCompleteURI(), e.getMessage()});
- status.set(Status.FAILED_TO_COMPLETE);
- } else {
- status.set(Status.COMPLETING);
- }
- } finally {
- Optional.ofNullable(response).ifPresent(HttpClientResponse::close);
- sendingStatus.set(SendingStatus.NOT_SENDING);
- }
- }
- return false;
- }
-
- boolean trySendAfterLRA(Lra lra) {
- for (int i = 0; i < SYNCHRONOUS_RETRY_CNT; i++) {
- // Participant in right state
- if (!isInEndStateOrListenerOnly()) return false;
- // LRA in right state
- if (!(Set.of(LRAStatus.Closed, LRAStatus.Cancelled).contains(lra.status().get()))) return false;
-
- HttpClientResponse response = null;
- try {
- Optional afterURI = getAfterURI();
- if (afterURI.isPresent() && afterLRACalled.compareAndSet(AfterLraStatus.NOT_SENT, AfterLraStatus.SENDING)) {
- response = webClient.put()
- .uri(afterURI.get())
- .headers(lra.headers())
- .submit(lra.status().get().name());
-
- if (response.status().code() == 200) {
- afterLRACalled.set(AfterLraStatus.SENT);
- } else if (remainingAfterLraAttempts.decrementAndGet() <= 0) {
- afterLRACalled.set(AfterLraStatus.SENT);
- }
- }
- } catch (Exception e) {
- LOGGER.log(Level.WARNING, "Error when sending after lra", e);
- if (remainingAfterLraAttempts.decrementAndGet() <= 0) {
- afterLRACalled.set(AfterLraStatus.SENT);
- } else {
- afterLRACalled.set(AfterLraStatus.NOT_SENT);
- }
- } finally {
- Optional.ofNullable(response).ifPresent(HttpClientResponse::close);
- }
- if (afterLRACalled.get() == AfterLraStatus.SENT) return true;
- }
- return false;
- }
-
-
- Optional retrieveStatus(Lra lra, ParticipantStatus inProgressStatus) {
- URI statusURI = this.getStatusURI().get();
- try (HttpClientResponse response = webClient.get()
- .uri(statusURI)
- .headers(h -> {
- // Dont send parent!
- h.add(LRA_HTTP_CONTEXT_HEADER_NAME, lra.lraContextId());
- h.add(LRA_HTTP_RECOVERY_HEADER_NAME, lra.lraContextId() + "/recovery");
- h.add(LRA_HTTP_ENDED_CONTEXT_HEADER_NAME, lra.lraContextId());
- })
- .request()) {
-
- int code = response.status().code();
- switch (code) {
- case 202:
- return Optional.of(inProgressStatus);
- case 410: //GONE
- //Completing -> FailedToComplete ...
- return status.get().failedFinalStatus();
- case 503:
- case 500:
- throw new IllegalStateException(String.format("Client reports unexpected status %s %s, "
- + "current participant state is %s, "
- + "lra: %s "
- + "status uri: %s",
- code,
- response.as(String.class),
- status.get(),
- lra.lraId(),
- statusURI.toASCIIString()));
- default:
- ParticipantStatus reportedStatus = valueOf(response.as(String.class));
- Status currentStatus = status.get();
- if (currentStatus.validateNextStatus(reportedStatus)) {
- return Optional.of(reportedStatus);
- } else {
- LOGGER.log(Level.WARNING,
- "Client reports unexpected status {0} {1}, "
- + "current participant state is {2}, "
- + "lra: {3} "
- + "status uri: {4}",
- new Object[] {code, reportedStatus, currentStatus, lra.lraId(), statusURI.toASCIIString()});
- return Optional.empty();
- }
- }
- } catch (Exception e) {
- LOGGER.log(Level.WARNING, "Error when getting participant status. " + statusURI, e);
- // skip dependent compensation call, another retry with status call might be luckier
- throw e;
- }
- }
-
- boolean sendForget(Lra lra) {
- if (!forgetCalled.compareAndSet(ForgetStatus.NOT_SENT, ForgetStatus.SENDING)) return false;
- try (
- HttpClientResponse response = webClient.delete()
- .uri(getForgetURI().get())
- .headers(lra.headers())
- .request()) {
-
- int responseStatus = response.status().code();
- if (responseStatus == 200 || responseStatus == 410) {
- forgetCalled.set(ForgetStatus.SENT);
- } else {
- throw new Exception("Unexpected response from participant " + response.status().code());
- }
- } catch (Throwable e) {
- LOGGER.log(Level.WARNING, "Unable to send forget of lra {0} to {1}",
- new Object[] {lra.lraId(), getForgetURI().get()});
- forgetCalled.set(ForgetStatus.NOT_SENT);
- }
- return forgetCalled.get() == ForgetStatus.SENT;
- }
-
- boolean equalCompensatorUris(String compensatorUris) {
- Set links = Arrays.stream(compensatorUris.split(","))
- .map(Link::valueOf)
- .collect(Collectors.toSet());
-
- for (Link link : links) {
- Optional participantsLink = getCompensatorLink(link.rel());
- if (participantsLink.isEmpty()) {
- continue;
- }
-
- if (Objects.equals(participantsLink.get(), link.uri())) {
- return true;
- }
- }
-
- return false;
- }
+ Optional statusURI();
}
diff --git a/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/ParticipantImpl.java b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/ParticipantImpl.java
new file mode 100644
index 00000000000..abc405007a5
--- /dev/null
+++ b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/ParticipantImpl.java
@@ -0,0 +1,592 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.lra.coordinator;
+
+import java.lang.System.Logger.Level;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import io.helidon.config.Config;
+import io.helidon.webclient.api.HttpClientResponse;
+import io.helidon.webclient.api.WebClient;
+
+import org.eclipse.microprofile.lra.annotation.LRAStatus;
+import org.eclipse.microprofile.lra.annotation.ParticipantStatus;
+
+import static io.helidon.lra.coordinator.LraImpl.LRA_HTTP_CONTEXT_HEADER_NAME;
+import static io.helidon.lra.coordinator.LraImpl.LRA_HTTP_ENDED_CONTEXT_HEADER_NAME;
+import static io.helidon.lra.coordinator.LraImpl.LRA_HTTP_RECOVERY_HEADER_NAME;
+import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.Active;
+import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.Compensated;
+import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.Compensating;
+import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.Completed;
+import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.Completing;
+import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.FailedToCompensate;
+import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.FailedToComplete;
+import static org.eclipse.microprofile.lra.annotation.ParticipantStatus.valueOf;
+
+class ParticipantImpl implements Participant {
+
+ private static final int RETRY_CNT = 60;
+ private static final int SYNCHRONOUS_RETRY_CNT = 5;
+
+ private static final System.Logger LOGGER = System.getLogger(Participant.class.getName());
+ private final AtomicReference compensateCalled = new AtomicReference<>(CompensateStatus.NOT_SENT);
+ private final AtomicReference forgetCalled = new AtomicReference<>(ForgetStatus.NOT_SENT);
+ private final AtomicReference afterLRACalled = new AtomicReference<>(AfterLraStatus.NOT_SENT);
+ private final AtomicReference sendingStatus = new AtomicReference<>(SendingStatus.NOT_SENDING);
+ private final AtomicInteger remainingCloseAttempts = new AtomicInteger(RETRY_CNT);
+ private final AtomicInteger remainingAfterLraAttempts = new AtomicInteger(RETRY_CNT);
+
+ private final AtomicReference status = new AtomicReference<>(Status.ACTIVE);
+ private final Map compensatorLinks = new HashMap<>();
+ private final long timeout;
+ private final WebClient webClient = WebClient.builder().build();
+
+ ParticipantImpl(Config config) {
+ timeout = config.get("timeout")
+ .asLong()
+ .orElse(500L);
+ }
+
+ @Override
+ public Optional completeURI() {
+ return compensatorLink("complete");
+ }
+
+ @Override
+ public Optional compensateURI() {
+ return compensatorLink("compensate");
+ }
+
+ @Override
+ public Optional afterURI() {
+ return compensatorLink("after");
+ }
+
+ @Override
+ public Optional forgetURI() {
+ return compensatorLink("forget");
+ }
+
+ @Override
+ public Optional statusURI() {
+ return compensatorLink("status");
+ }
+
+ void parseCompensatorLinks(String compensatorLinks) {
+ Stream.of(compensatorLinks.split(","))
+ .filter(s -> !s.isBlank())
+ .map(Link::valueOf)
+ .forEach(link -> this.compensatorLinks.put(link.rel(), link.uri()));
+ }
+
+ Optional compensatorLink(String rel) {
+ return Optional.ofNullable(compensatorLinks.get(rel));
+ }
+
+ void completeURI(URI completeURI) {
+ compensatorLinks.put("complete", completeURI);
+ }
+
+ void compensateURI(URI compensateURI) {
+ compensatorLinks.put("compensate", compensateURI);
+ }
+
+ void afterURI(URI afterURI) {
+ compensatorLinks.put("after", afterURI);
+ }
+
+ void forgetURI(URI forgetURI) {
+ compensatorLinks.put("forget", forgetURI);
+ }
+
+ void statusURI(URI statusURI) {
+ compensatorLinks.put("status", statusURI);
+ }
+
+ CompensateStatus compensateStatus() {
+ return this.compensateCalled.get();
+ }
+
+ void compensateStatus(CompensateStatus compensateStatus) {
+ this.compensateCalled.set(compensateStatus);
+ }
+
+ void status(Status status) {
+ this.status.set(status);
+ }
+
+ ForgetStatus forgetStatus() {
+ return this.forgetCalled.get();
+ }
+
+ void forgetStatus(ForgetStatus forgetStatus) {
+ this.forgetCalled.set(forgetStatus);
+ }
+
+ AfterLraStatus afterLraStatus() {
+ return this.afterLRACalled.get();
+ }
+
+ void afterLraStatus(AfterLraStatus afterLraStatus) {
+ this.afterLRACalled.set(afterLraStatus);
+ }
+
+ SendingStatus sendingStatus() {
+ return this.sendingStatus.get();
+ }
+
+ void sendingStatus(SendingStatus sendingStatus) {
+ this.sendingStatus.set(sendingStatus);
+ }
+
+ int remainingCloseAttempts() {
+ return this.remainingCloseAttempts.get();
+ }
+
+ void remainingCloseAttempts(int remainingCloseAttempts) {
+ this.remainingCloseAttempts.set(remainingCloseAttempts);
+ }
+
+ int remainingAfterAttempts() {
+ return this.remainingAfterLraAttempts.get();
+ }
+
+ void remainingAfterAttempts(int remainingAfterAttempts) {
+ this.remainingAfterLraAttempts.set(remainingAfterAttempts);
+ }
+
+ Status state() {
+ return status.get();
+ }
+
+ boolean isForgotten() {
+ return forgetCalled.get() == ForgetStatus.SENT;
+ }
+
+ boolean isListenerOnly() {
+ return completeURI().isEmpty() && compensateURI().isEmpty();
+ }
+
+ boolean isInEndStateOrListenerOnly() {
+ return isListenerOnly() || status.get().isFinal();
+ }
+
+ boolean sendCancel(LraImpl lra) {
+ Optional endpointURI = compensateURI();
+ for (AtomicInteger i = new AtomicInteger(0); i.getAndIncrement() < SYNCHRONOUS_RETRY_CNT;) {
+ if (!sendingStatus.compareAndSet(SendingStatus.NOT_SENDING, SendingStatus.SENDING)) {
+ return false;
+ }
+ if (!compensateCalled.compareAndSet(CompensateStatus.NOT_SENT, CompensateStatus.SENDING)) {
+ return false;
+ }
+ LOGGER.log(Level.DEBUG, () -> "Sending compensate, sync retry: " + i.get()
+ + ", status: " + status.get().name()
+ + " statusUri: " + statusURI().map(URI::toASCIIString).orElse(null));
+ HttpClientResponse response = null;
+ try {
+ // call for client status only on retries and when status uri is known
+ if (!status.get().equals(Status.ACTIVE) && statusURI().isPresent()) {
+ // If the participant does not support idempotency then it MUST be able to report its status
+ // by annotating one of the methods with the @Status annotation which should report the status
+ // in case we can't retrieve status from participant just retry n times
+ ParticipantStatus reportedClientStatus = retrieveStatus(lra, Compensating).orElse(null);
+ if (reportedClientStatus == Compensated) {
+ LOGGER.log(Level.INFO, "Participant reports it is compensated.");
+ status.set(Status.COMPENSATED);
+ return true;
+ } else if (reportedClientStatus == FailedToCompensate) {
+ LOGGER.log(Level.INFO, "Participant reports it failed to compensate.");
+ status.set(Status.FAILED_TO_COMPENSATE);
+ return true;
+ } else if (reportedClientStatus == Active) {
+ // last call didn't reach participant, try call again
+ } else if (reportedClientStatus == Completed && lra.isChild()) {
+ // completed participant can be compensated again in case of nested tx
+ } else if (reportedClientStatus == Compensating) {
+ LOGGER.log(Level.INFO, "Participant reports it is still compensating.");
+ status.set(Status.CLIENT_COMPENSATING);
+ return false;
+ } else if (remainingCloseAttempts.decrementAndGet() <= 0) {
+ LOGGER.log(Level.INFO, "Participant didnt report final status after {0} status call retries.",
+ new Object[] {RETRY_CNT});
+ status.set(Status.FAILED_TO_COMPENSATE);
+ return true;
+ } else {
+ // Unknown status, lets try in next recovery cycle
+ LOGGER.log(Level.INFO, "Unknown status of " + lra.lraId());
+ return false;
+ }
+ }
+
+ response = webClient.put()
+ .uri(endpointURI.get())
+ .headers(lra.headers())
+ .submit(LRAStatus.Cancelled.name());
+
+ // When timeout occur we loose track of the participant status
+ // next retry will attempt to retrieve participant status if status uri is available
+
+ switch (response.status().code()) {
+ // complete or compensated
+ case 200:
+ case 410:
+ LOGGER.log(Level.INFO, "Compensated participant of LRA {0} {1}",
+ new Object[] {lra.lraId(), this.compensateURI()});
+ status.set(Status.COMPENSATED);
+ compensateCalled.set(CompensateStatus.SENT);
+ return true;
+
+ // retryable
+ case 202:
+ // Still compensating, check with @Status later
+ this.status.set(Status.CLIENT_COMPENSATING);
+ return false;
+ case 409:
+ case 404:
+ case 503:
+ default:
+ throw new Exception(response.status().code() + " " + response.status().reasonPhrase());
+ }
+
+ } catch (Exception e) {
+ LOGGER.log(Level.WARNING,
+ () -> "Can't reach participant's compensate endpoint: "
+ + endpointURI.map(URI::toASCIIString).orElse("unknown"), e);
+ if (remainingCloseAttempts.decrementAndGet() <= 0) {
+ LOGGER.log(Level.WARNING, "Failed to compensate participant of LRA {0} {1} {2}",
+ new Object[] {lra.lraId(), this.compensateURI(), e.getMessage()});
+ status.set(Status.FAILED_TO_COMPENSATE);
+ } else {
+ status.set(Status.COMPENSATING);
+ }
+
+ } finally {
+ Optional.ofNullable(response).ifPresent(HttpClientResponse::close);
+ sendingStatus.set(SendingStatus.NOT_SENDING);
+ compensateCalled.compareAndSet(CompensateStatus.SENDING, CompensateStatus.NOT_SENT);
+ }
+ }
+ return false;
+ }
+
+ boolean sendComplete(LraImpl lra) {
+ Optional endpointURI = completeURI();
+ for (AtomicInteger i = new AtomicInteger(0); i.getAndIncrement() < SYNCHRONOUS_RETRY_CNT;) {
+ if (!sendingStatus.compareAndSet(SendingStatus.NOT_SENDING, SendingStatus.SENDING)) {
+ return false;
+ }
+ LOGGER.log(Level.DEBUG, () -> "Sending complete, sync retry: " + i.get()
+ + ", status: " + status.get().name()
+ + " statusUri: " + statusURI().map(URI::toASCIIString).orElse(null));
+ HttpClientResponse response = null;
+ try {
+ if (status.get().isFinal()) {
+ return true;
+ // call for client status only on retries and when status uri is known
+ } else if (!status.get().equals(Status.ACTIVE) && statusURI().isPresent()) {
+ // If the participant does not support idempotency then it MUST be able to report its status
+ // by annotating one of the methods with the @Status annotation which should report the status
+ // in case we can't retrieve status from participant just retry n times
+ ParticipantStatus reportedClientStatus = retrieveStatus(lra, Completing).orElse(null);
+ if (reportedClientStatus == Completed) {
+ LOGGER.log(Level.INFO, "Participant reports it is completed.");
+ status.set(Status.COMPLETED);
+ return true;
+ } else if (reportedClientStatus == FailedToComplete) {
+ LOGGER.log(Level.INFO, "Participant reports it failed to complete.");
+ status.set(Status.FAILED_TO_COMPLETE);
+ return true;
+ } else if (reportedClientStatus == Active) {
+ // last call didn't reach participant, try call again
+ } else if (reportedClientStatus == Completing) {
+ LOGGER.log(Level.INFO, "Participant reports it is still completing.");
+ status.set(Status.CLIENT_COMPLETING);
+ return false;
+ } else if (remainingCloseAttempts.decrementAndGet() <= 0) {
+ LOGGER.log(Level.INFO, "Participant didnt report final status after {0} status call retries.",
+ new Object[] {RETRY_CNT});
+ status.set(Status.FAILED_TO_COMPLETE);
+ return true;
+ } else {
+ // Unknown status, lets try in next recovery cycle
+ return false;
+ }
+ }
+ response = webClient.put()
+ .uri(endpointURI.get())
+ .headers(lra.headers())
+ .submit(LRAStatus.Closed.name());
+ // When timeout occur we loose track of the participant status
+ // next retry will attempt to retrieve participant status if status uri is available
+
+ switch (response.status().code()) {
+ // complete or compensated
+ case 200:
+ case 410:
+ status.set(Status.COMPLETED);
+ return true;
+
+ // retryable
+ case 202:
+ // Still completing, check with @Status later
+ this.status.set(Status.CLIENT_COMPLETING);
+ return false;
+ case 409:
+ case 404:
+ case 503:
+ default:
+ throw new Exception(response.status().code() + " " + response.status().reasonPhrase());
+ }
+
+ } catch (Exception e) {
+ LOGGER.log(Level.WARNING,
+ () -> "Can't reach participant's complete endpoint: " + endpointURI.map(URI::toASCIIString)
+ .orElse("unknown"), e);
+ if (remainingCloseAttempts.decrementAndGet() <= 0) {
+ LOGGER.log(Level.WARNING, "Failed to complete participant of LRA {0} {1} {2}",
+ new Object[] {lra.lraId(),
+ this.completeURI(), e.getMessage()});
+ status.set(Status.FAILED_TO_COMPLETE);
+ } else {
+ status.set(Status.COMPLETING);
+ }
+ } finally {
+ Optional.ofNullable(response).ifPresent(HttpClientResponse::close);
+ sendingStatus.set(SendingStatus.NOT_SENDING);
+ }
+ }
+ return false;
+ }
+
+ boolean trySendAfterLRA(LraImpl lra) {
+ for (int i = 0; i < SYNCHRONOUS_RETRY_CNT; i++) {
+ // Participant in right state
+ if (!isInEndStateOrListenerOnly()) {
+ return false;
+ }
+ // LRA in right state
+ if (!(Set.of(LRAStatus.Closed, LRAStatus.Cancelled).contains(lra.lraStatus().get()))) {
+ return false;
+ }
+
+ HttpClientResponse response = null;
+ try {
+ Optional afterURI = afterURI();
+ if (afterURI.isPresent() && afterLRACalled.compareAndSet(AfterLraStatus.NOT_SENT, AfterLraStatus.SENDING)) {
+ response = webClient.put()
+ .uri(afterURI.get())
+ .headers(lra.headers())
+ .submit(lra.lraStatus().get().name());
+
+ if (response.status().code() == 200) {
+ afterLRACalled.set(AfterLraStatus.SENT);
+ } else if (remainingAfterLraAttempts.decrementAndGet() <= 0) {
+ afterLRACalled.set(AfterLraStatus.SENT);
+ }
+ }
+ } catch (Exception e) {
+ LOGGER.log(Level.WARNING, "Error when sending after lra", e);
+ if (remainingAfterLraAttempts.decrementAndGet() <= 0) {
+ afterLRACalled.set(AfterLraStatus.SENT);
+ } else {
+ afterLRACalled.set(AfterLraStatus.NOT_SENT);
+ }
+ } finally {
+ Optional.ofNullable(response).ifPresent(HttpClientResponse::close);
+ }
+ if (afterLRACalled.get() == AfterLraStatus.SENT) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ Optional retrieveStatus(LraImpl lra, ParticipantStatus inProgressStatus) {
+ URI statusURI = this.statusURI().get();
+ try (HttpClientResponse response = webClient.get()
+ .uri(statusURI)
+ .headers(h -> {
+ // Dont send parent!
+ h.add(LRA_HTTP_CONTEXT_HEADER_NAME, lra.lraContextId());
+ h.add(LRA_HTTP_RECOVERY_HEADER_NAME, lra.lraContextId() + "/recovery");
+ h.add(LRA_HTTP_ENDED_CONTEXT_HEADER_NAME, lra.lraContextId());
+ })
+ .request()) {
+
+ int code = response.status().code();
+ switch (code) {
+ case 202:
+ return Optional.of(inProgressStatus);
+ case 410: //GONE
+ //Completing -> FailedToComplete ...
+ return status.get().failedFinalStatus();
+ case 503:
+ case 500:
+ throw new IllegalStateException(String.format("Client reports unexpected status %s %s, "
+ + "current participant state is %s, "
+ + "lra: %s "
+ + "status uri: %s",
+ code,
+ response.as(String.class),
+ status.get(),
+ lra.lraId(),
+ statusURI.toASCIIString()));
+ default:
+ ParticipantStatus reportedStatus = valueOf(response.as(String.class));
+ Status currentStatus = status.get();
+ if (currentStatus.validateNextStatus(reportedStatus)) {
+ return Optional.of(reportedStatus);
+ } else {
+ LOGGER.log(Level.WARNING,
+ "Client reports unexpected status {0} {1}, "
+ + "current participant state is {2}, "
+ + "lra: {3} "
+ + "status uri: {4}",
+ new Object[] {code, reportedStatus, currentStatus, lra.lraId(), statusURI.toASCIIString()});
+ return Optional.empty();
+ }
+ }
+ } catch (Exception e) {
+ LOGGER.log(Level.WARNING, "Error when getting participant status. " + statusURI, e);
+ // skip dependent compensation call, another retry with status call might be luckier
+ throw e;
+ }
+ }
+
+ boolean sendForget(LraImpl lra) {
+ if (!forgetCalled.compareAndSet(ForgetStatus.NOT_SENT, ForgetStatus.SENDING)) {
+ return false;
+ }
+ try (
+ HttpClientResponse response = webClient.delete()
+ .uri(forgetURI().get())
+ .headers(lra.headers())
+ .request()) {
+
+ int responseStatus = response.status().code();
+ if (responseStatus == 200 || responseStatus == 410) {
+ forgetCalled.set(ForgetStatus.SENT);
+ } else {
+ throw new Exception("Unexpected response from participant " + response.status().code());
+ }
+ } catch (Throwable e) {
+ LOGGER.log(Level.WARNING, "Unable to send forget of lra {0} to {1}",
+ new Object[] {lra.lraId(), forgetURI().get()});
+ forgetCalled.set(ForgetStatus.NOT_SENT);
+ }
+ return forgetCalled.get() == ForgetStatus.SENT;
+ }
+
+ boolean equalCompensatorUris(String compensatorUris) {
+ Set links = Arrays.stream(compensatorUris.split(","))
+ .map(Link::valueOf)
+ .collect(Collectors.toSet());
+
+ for (Link link : links) {
+ Optional participantsLink = compensatorLink(link.rel());
+ if (participantsLink.isEmpty()) {
+ continue;
+ }
+
+ if (Objects.equals(participantsLink.get(), link.uri())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ enum Status {
+ ACTIVE(Active, null, null, false, Set.of(Completing, Compensating)),
+
+ COMPENSATED(Compensated, null, null, true, Set.of(Compensated)),
+ COMPLETED(Completed, null, null, true, Set.of(Completed)),
+ FAILED_TO_COMPENSATE(FailedToCompensate, null, null, true, Set.of()),
+ FAILED_TO_COMPLETE(FailedToComplete, null, null, true, Set.of()),
+
+ CLIENT_COMPENSATING(Compensating, COMPENSATED, FAILED_TO_COMPENSATE, false,
+ Set.of(Active, Compensated, FailedToCompensate)),
+ CLIENT_COMPLETING(Completing, COMPLETED, FAILED_TO_COMPLETE, false, Set.of(Active, Completed, FailedToComplete)),
+ COMPENSATING(Compensating, COMPENSATED, FAILED_TO_COMPENSATE, false, Set.of(Active, Compensated, FailedToCompensate)),
+ COMPLETING(Completing, COMPLETED, FAILED_TO_COMPLETE, false, Set.of(Active, Completed, FailedToComplete));
+
+ private final ParticipantStatus participantStatus;
+ private final Status successFinalStatus;
+ private final Status failedFinalStatus;
+ private final boolean finalState;
+ private final Set validNextStates;
+
+ Status(ParticipantStatus participantStatus,
+ Status successFinalStatus,
+ Status failedFinalStatus,
+ boolean finalState,
+ Set validNextStates) {
+ this.participantStatus = participantStatus;
+ this.successFinalStatus = successFinalStatus;
+ this.failedFinalStatus = failedFinalStatus;
+ this.finalState = finalState;
+ this.validNextStates = validNextStates;
+ }
+
+ ParticipantStatus participantStatus() {
+ return participantStatus;
+ }
+
+ boolean isFinal() {
+ return finalState;
+ }
+
+ boolean validateNextStatus(ParticipantStatus participantStatus) {
+ return validNextStates.contains(participantStatus);
+ }
+
+ Optional successFinalStatus() {
+ return Optional.ofNullable(successFinalStatus.participantStatus());
+ }
+
+ Optional failedFinalStatus() {
+ return Optional.ofNullable(failedFinalStatus.participantStatus);
+ }
+ }
+
+ enum SendingStatus {
+ SENDING, NOT_SENDING;
+ }
+
+ enum AfterLraStatus {
+ NOT_SENT, SENDING, SENT;
+ }
+
+ enum ForgetStatus {
+ NOT_SENT, SENDING, SENT;
+ }
+
+ enum CompensateStatus {
+ NOT_SENT, SENDING, SENT;
+ }
+}
diff --git a/lra/coordinator/server/src/main/java/module-info.java b/lra/coordinator/server/src/main/java/module-info.java
index 41fb4c00841..e97b6dafb7c 100644
--- a/lra/coordinator/server/src/main/java/module-info.java
+++ b/lra/coordinator/server/src/main/java/module-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
* Helidon LRA coordinator.
*/
module io.helidon.lra.coordinator {
+ exports io.helidon.lra.coordinator;
requires io.helidon.dbclient.jdbc;
requires io.helidon.dbclient;
diff --git a/lra/pom.xml b/lra/pom.xml
index 3f4e71c6b33..2601a99f13f 100644
--- a/lra/pom.xml
+++ b/lra/pom.xml
@@ -24,7 +24,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.lrahelidon-lra-project
diff --git a/messaging/connectors/aq/pom.xml b/messaging/connectors/aq/pom.xml
index 0b7a51a630e..38ebfa0e6be 100644
--- a/messaging/connectors/aq/pom.xml
+++ b/messaging/connectors/aq/pom.xml
@@ -24,7 +24,7 @@
io.helidon.messaginghelidon-messaging-connectors-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.messaging.aq
diff --git a/messaging/connectors/jms-shim/pom.xml b/messaging/connectors/jms-shim/pom.xml
index 3e368497f6c..110e94e9348 100644
--- a/messaging/connectors/jms-shim/pom.xml
+++ b/messaging/connectors/jms-shim/pom.xml
@@ -24,7 +24,7 @@
io.helidon.messaginghelidon-messaging-connectors-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-messaging-jms-shim
diff --git a/messaging/connectors/jms/pom.xml b/messaging/connectors/jms/pom.xml
index bbe8b062535..7f734118910 100644
--- a/messaging/connectors/jms/pom.xml
+++ b/messaging/connectors/jms/pom.xml
@@ -24,7 +24,7 @@
io.helidon.messaginghelidon-messaging-connectors-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.messaging.jms
diff --git a/messaging/connectors/kafka/pom.xml b/messaging/connectors/kafka/pom.xml
index 4dd8f1870cf..be093582dc1 100644
--- a/messaging/connectors/kafka/pom.xml
+++ b/messaging/connectors/kafka/pom.xml
@@ -25,7 +25,7 @@
io.helidon.messaginghelidon-messaging-connectors-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.messaging.kafka
diff --git a/messaging/connectors/mock/pom.xml b/messaging/connectors/mock/pom.xml
index aaf467f8859..8e1856cbed9 100644
--- a/messaging/connectors/mock/pom.xml
+++ b/messaging/connectors/mock/pom.xml
@@ -24,7 +24,7 @@
io.helidon.messaginghelidon-messaging-connectors-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.messaging.mock
diff --git a/messaging/connectors/pom.xml b/messaging/connectors/pom.xml
index b228af13ea7..48b997b6ad2 100644
--- a/messaging/connectors/pom.xml
+++ b/messaging/connectors/pom.xml
@@ -23,7 +23,7 @@
io.helidon.messaginghelidon-messaging-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-messaging-connectors-projectHelidon Messaging Connectors Project
diff --git a/messaging/connectors/wls-jms/pom.xml b/messaging/connectors/wls-jms/pom.xml
index fb06c76e3e9..67ec92d5f3c 100644
--- a/messaging/connectors/wls-jms/pom.xml
+++ b/messaging/connectors/wls-jms/pom.xml
@@ -24,7 +24,7 @@
io.helidon.messaginghelidon-messaging-connectors-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.messaging.wls-jms
diff --git a/messaging/messaging/pom.xml b/messaging/messaging/pom.xml
index df8cc937c15..b343b78c65a 100644
--- a/messaging/messaging/pom.xml
+++ b/messaging/messaging/pom.xml
@@ -23,7 +23,7 @@
io.helidon.messaginghelidon-messaging-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-messaging
diff --git a/messaging/pom.xml b/messaging/pom.xml
index 76b2371a4e9..e94df6126c9 100644
--- a/messaging/pom.xml
+++ b/messaging/pom.xml
@@ -24,7 +24,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.messaginghelidon-messaging-project
diff --git a/metadata/hson/pom.xml b/metadata/hson/pom.xml
index 95d50b2e4a5..5eacc7d13e3 100644
--- a/metadata/hson/pom.xml
+++ b/metadata/hson/pom.xml
@@ -22,7 +22,7 @@
io.helidon.metadatahelidon-metadata-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-metadata-hson
diff --git a/metadata/hson/src/main/java/io/helidon/metadata/hson/HsonValues.java b/metadata/hson/src/main/java/io/helidon/metadata/hson/HsonValues.java
index 6c94cb8e497..54095e99492 100644
--- a/metadata/hson/src/main/java/io/helidon/metadata/hson/HsonValues.java
+++ b/metadata/hson/src/main/java/io/helidon/metadata/hson/HsonValues.java
@@ -76,13 +76,15 @@ private String quote(String value) {
}
private String escape(String string) {
- return string.replaceAll("\n", "\\\\n")
- .replaceAll("\"", "\\\\\"")
- .replaceAll("\t", "\\\\\t")
- .replaceAll("\r", "\\\\\r")
- // replace two backslashes with four backslashes
- .replaceAll("\\\\\\\\", "\\\\\\\\\\\\\\\\")
- .replaceAll("\f", "\\\\\f");
+ String result = string.replaceAll("\n", "\\\\n");
+
+ result = result.replaceAll("\"", "\\\\\"");
+ result = result.replaceAll("\t", "\\\\t");
+ result = result.replaceAll("\r", "\\\\r");
+ // replace two backslashes with four backslashes
+ result = result.replaceAll("\\\\\\\\", "\\\\\\\\\\\\\\\\");
+ result = result.replaceAll("\f", "\\\\f");
+ return result;
}
}
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/metadata/pom.xml b/metadata/pom.xml
index 0926ca062d2..36a18f6ab47 100644
--- a/metadata/pom.xml
+++ b/metadata/pom.xml
@@ -22,7 +22,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpom
diff --git a/metrics/api/pom.xml b/metrics/api/pom.xml
index 0f0d4a86372..b5e17fa81ab 100644
--- a/metrics/api/pom.xml
+++ b/metrics/api/pom.xml
@@ -22,7 +22,7 @@
io.helidon.metricshelidon-metrics-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-metrics-api
diff --git a/metrics/metrics/pom.xml b/metrics/metrics/pom.xml
index adba141799b..cb7a8a4989c 100644
--- a/metrics/metrics/pom.xml
+++ b/metrics/metrics/pom.xml
@@ -24,7 +24,7 @@
io.helidon.metricshelidon-metrics-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-metricsHelidon Metrics
diff --git a/metrics/pom.xml b/metrics/pom.xml
index 3a2474738f6..40717afbf18 100644
--- a/metrics/pom.xml
+++ b/metrics/pom.xml
@@ -22,7 +22,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomio.helidon.metrics
diff --git a/metrics/prometheus/pom.xml b/metrics/prometheus/pom.xml
index f19cd578d34..ab87f257bc5 100644
--- a/metrics/prometheus/pom.xml
+++ b/metrics/prometheus/pom.xml
@@ -24,7 +24,7 @@
io.helidon.metricshelidon-metrics-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-metrics-prometheusHelidon Metrics Prometheus
diff --git a/metrics/provider-tests/pom.xml b/metrics/provider-tests/pom.xml
index efb9dce4511..b4a44b13c2e 100644
--- a/metrics/provider-tests/pom.xml
+++ b/metrics/provider-tests/pom.xml
@@ -23,7 +23,7 @@
io.helidon.metricshelidon-metrics-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-metrics-provider-testsHelidon Metrics Provider Tests
diff --git a/metrics/provider-tests/src/main/java/io/helidon/metrics/provider/tests/SimpleMeterRegistryTests.java b/metrics/provider-tests/src/main/java/io/helidon/metrics/provider/tests/SimpleMeterRegistryTests.java
index 86a93dcf45e..5335fa74a30 100644
--- a/metrics/provider-tests/src/main/java/io/helidon/metrics/provider/tests/SimpleMeterRegistryTests.java
+++ b/metrics/provider-tests/src/main/java/io/helidon/metrics/provider/tests/SimpleMeterRegistryTests.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,10 +16,16 @@
package io.helidon.metrics.provider.tests;
import java.util.List;
+import java.util.Map;
+import io.helidon.common.testing.junit5.OptionalMatcher;
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
import io.helidon.metrics.api.Counter;
import io.helidon.metrics.api.MeterRegistry;
import io.helidon.metrics.api.Metrics;
+import io.helidon.metrics.api.MetricsConfig;
+import io.helidon.metrics.api.MetricsFactory;
import io.helidon.metrics.api.Tag;
import io.helidon.metrics.api.Timer;
@@ -28,7 +34,9 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.emptyIterable;
import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
@@ -78,4 +86,38 @@ void testSameNameSameTwoTags() {
.tags(tags));
assertThat("Counter with same name, same two tags", counter1, is(sameInstance(counter2)));
}
+
+ @Test
+ void testDisabledYieldsNoOp() {
+ // Disable metrics using config.
+ Config metricsDisabledConfig = Config.just(ConfigSources.create(Map.of("enabled", "false")));
+ MeterRegistry shouldBeNoOp = MetricsFactory.getInstance()
+ .createMeterRegistry(MetricsConfig.create(metricsDisabledConfig));
+
+ Counter shouldBeNoOpCounter = shouldBeNoOp.getOrCreate(Counter.builder("shouldBeNoOpCounter"));
+ assertThat("Counters after registration", shouldBeNoOp.meters(), is(emptyIterable()));
+
+ shouldBeNoOpCounter.increment();
+ assertThat("Counter value after increment", shouldBeNoOpCounter.count(), is(0L));
+ }
+
+ @Test
+ void testDisabledMeters() {
+ Config config = Config.just(ConfigSources.create(Map.of("scoping.scopes.0.name", "application",
+ "scoping.scopes.0.filter.exclude", ".*Ignore.*")));
+ MeterRegistry selectiveRegistry = MetricsFactory.getInstance().createMeterRegistry(MetricsConfig.create(config));
+
+ Counter shouldBeNoOpCounter = selectiveRegistry.getOrCreate(Counter.builder("pleaseIgnoreThis"));
+ Counter shouldBeLive = selectiveRegistry.getOrCreate(Counter.builder("pleaseIncludeThis"));
+ assertThat("Counters after ignored registration", selectiveRegistry.meters(), hasSize(1));
+ assertThat("Counter retrieved",
+ selectiveRegistry.counter("pleaseIncludeThis", List.of()),
+ OptionalMatcher.optionalPresent());
+
+ shouldBeNoOpCounter.increment();
+ shouldBeLive.increment();
+
+ assertThat("Incremented disabled counter", shouldBeNoOpCounter.count(), is(0L));
+ assertThat("Incremented live counter", shouldBeLive.count(), is(1L));
+ }
}
diff --git a/metrics/providers/micrometer/pom.xml b/metrics/providers/micrometer/pom.xml
index 89847d75cce..c0d1f1f6a02 100644
--- a/metrics/providers/micrometer/pom.xml
+++ b/metrics/providers/micrometer/pom.xml
@@ -24,7 +24,7 @@
io.helidon.metrics.providershelidon-metrics-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-metrics-providers-micrometerHelidon Metrics Providers Micrometer
@@ -47,6 +47,10 @@
io.micrometermicrometer-registry-prometheus
+
+ io.micrometer
+ micrometer-registry-prometheus-simpleclient
+ io.helidon.confighelidon-config
diff --git a/metrics/providers/micrometer/src/main/java/module-info.java b/metrics/providers/micrometer/src/main/java/module-info.java
index a76661d745b..d3361b87bd0 100644
--- a/metrics/providers/micrometer/src/main/java/module-info.java
+++ b/metrics/providers/micrometer/src/main/java/module-info.java
@@ -21,6 +21,7 @@
requires io.helidon.metrics.api;
requires micrometer.core;
requires static micrometer.registry.prometheus;
+ requires static micrometer.registry.prometheus.simpleclient;
requires io.helidon.common;
requires io.helidon.common.media.type;
requires io.helidon.config;
diff --git a/metrics/providers/pom.xml b/metrics/providers/pom.xml
index 8f700fdc789..fc7a7f1cb4e 100644
--- a/metrics/providers/pom.xml
+++ b/metrics/providers/pom.xml
@@ -22,7 +22,7 @@
io.helidon.metricshelidon-metrics-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomio.helidon.metrics.providers
diff --git a/metrics/system-meters/pom.xml b/metrics/system-meters/pom.xml
index b9f997a8b83..bd17ebc3dad 100644
--- a/metrics/system-meters/pom.xml
+++ b/metrics/system-meters/pom.xml
@@ -22,7 +22,7 @@
io.helidon.metricshelidon-metrics-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-metrics-system-meters
diff --git a/metrics/trace-exemplar/pom.xml b/metrics/trace-exemplar/pom.xml
index 79078238d1e..d4c18a3d24a 100644
--- a/metrics/trace-exemplar/pom.xml
+++ b/metrics/trace-exemplar/pom.xml
@@ -23,7 +23,7 @@
helidon-metrics-projectio.helidon.metrics
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/microprofile/access-log/pom.xml b/microprofile/access-log/pom.xml
index f5a2cb6bf52..ce7c9df9741 100644
--- a/microprofile/access-log/pom.xml
+++ b/microprofile/access-log/pom.xml
@@ -23,7 +23,7 @@
helidon-microprofile-projectio.helidon.microprofile
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/microprofile/bean-validation/pom.xml b/microprofile/bean-validation/pom.xml
index aa3cfed9bb9..a0d46f024aa 100644
--- a/microprofile/bean-validation/pom.xml
+++ b/microprofile/bean-validation/pom.xml
@@ -21,7 +21,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.bean-validationhelidon-microprofile-bean-validation
diff --git a/microprofile/bundles/helidon-microprofile-core/pom.xml b/microprofile/bundles/helidon-microprofile-core/pom.xml
index 643efbdcbb6..0cbec512486 100644
--- a/microprofile/bundles/helidon-microprofile-core/pom.xml
+++ b/microprofile/bundles/helidon-microprofile-core/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.bundlesbundles-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-coreHelidon Microprofile Core Bundle
diff --git a/microprofile/bundles/helidon-microprofile/pom.xml b/microprofile/bundles/helidon-microprofile/pom.xml
index 0bd3e8d1e0d..bd1ba22548e 100644
--- a/microprofile/bundles/helidon-microprofile/pom.xml
+++ b/microprofile/bundles/helidon-microprofile/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.bundlesbundles-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofileHelidon Microprofile Full Bundle
diff --git a/microprofile/bundles/pom.xml b/microprofile/bundles/pom.xml
index 8b2141ab83f..2bace9c9b88 100644
--- a/microprofile/bundles/pom.xml
+++ b/microprofile/bundles/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomio.helidon.microprofile.bundles
diff --git a/microprofile/cdi/pom.xml b/microprofile/cdi/pom.xml
index c8b0dd515e9..598c0dca03d 100644
--- a/microprofile/cdi/pom.xml
+++ b/microprofile/cdi/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.cdi
diff --git a/microprofile/config/pom.xml b/microprofile/config/pom.xml
index 799e2a2ec5d..089c16a6418 100644
--- a/microprofile/config/pom.xml
+++ b/microprofile/config/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.confighelidon-microprofile-config
diff --git a/microprofile/cors/pom.xml b/microprofile/cors/pom.xml
index 64d244c88e8..58d47ba1779 100644
--- a/microprofile/cors/pom.xml
+++ b/microprofile/cors/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-cors
diff --git a/microprofile/fault-tolerance/pom.xml b/microprofile/fault-tolerance/pom.xml
index 895c13bb7d3..4a712031e1e 100644
--- a/microprofile/fault-tolerance/pom.xml
+++ b/microprofile/fault-tolerance/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-fault-toleranceHelidon Microprofile Fault Tolerance
diff --git a/microprofile/graphql/pom.xml b/microprofile/graphql/pom.xml
index 99cfb08e03f..8d28d5d2363 100644
--- a/microprofile/graphql/pom.xml
+++ b/microprofile/graphql/pom.xml
@@ -20,7 +20,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/microprofile/graphql/server/pom.xml b/microprofile/graphql/server/pom.xml
index 4ad742156e0..46d0ff2029b 100644
--- a/microprofile/graphql/server/pom.xml
+++ b/microprofile/graphql/server/pom.xml
@@ -20,7 +20,7 @@
io.helidon.microprofile.graphqlhelidon-microprofile-graphql
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/microprofile/grpc/client/pom.xml b/microprofile/grpc/client/pom.xml
index f2b2c481fba..ef9c623363b 100644
--- a/microprofile/grpc/client/pom.xml
+++ b/microprofile/grpc/client/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.grpchelidon-microprofile-grpc-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-grpc-client
diff --git a/microprofile/grpc/core/pom.xml b/microprofile/grpc/core/pom.xml
index 1627be21150..af369256f98 100644
--- a/microprofile/grpc/core/pom.xml
+++ b/microprofile/grpc/core/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.grpchelidon-microprofile-grpc-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-grpc-core
diff --git a/microprofile/grpc/pom.xml b/microprofile/grpc/pom.xml
index 99bf0a4e4ab..87accbcf72f 100644
--- a/microprofile/grpc/pom.xml
+++ b/microprofile/grpc/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.grpc
diff --git a/microprofile/grpc/server/pom.xml b/microprofile/grpc/server/pom.xml
index 443eaac5759..3dc3885c9a5 100644
--- a/microprofile/grpc/server/pom.xml
+++ b/microprofile/grpc/server/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.grpchelidon-microprofile-grpc-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-grpc-server
diff --git a/microprofile/grpc/tests/pom.xml b/microprofile/grpc/tests/pom.xml
index d2ecf9306b2..724420758e3 100644
--- a/microprofile/grpc/tests/pom.xml
+++ b/microprofile/grpc/tests/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.grpchelidon-microprofile-grpc-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-grpc-tests
diff --git a/microprofile/grpc/tracing/pom.xml b/microprofile/grpc/tracing/pom.xml
index ecfc63cdb6d..27ffe3b9347 100644
--- a/microprofile/grpc/tracing/pom.xml
+++ b/microprofile/grpc/tracing/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.grpchelidon-microprofile-grpc-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-grpc-tracing
diff --git a/microprofile/health/pom.xml b/microprofile/health/pom.xml
index e504fe15795..73bf423f707 100644
--- a/microprofile/health/pom.xml
+++ b/microprofile/health/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.healthhelidon-microprofile-health
diff --git a/microprofile/jwt-auth/pom.xml b/microprofile/jwt-auth/pom.xml
index faf7b10dea6..8c74fd1e7d7 100644
--- a/microprofile/jwt-auth/pom.xml
+++ b/microprofile/jwt-auth/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.jwt
diff --git a/microprofile/lra/jax-rs/pom.xml b/microprofile/lra/jax-rs/pom.xml
index d1676e48b8c..8d7842ff389 100644
--- a/microprofile/lra/jax-rs/pom.xml
+++ b/microprofile/lra/jax-rs/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.lrahelidon-microprofile-lra-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-lra
diff --git a/microprofile/lra/jax-rs/src/main/java/io/helidon/microprofile/lra/LraCdiExtension.java b/microprofile/lra/jax-rs/src/main/java/io/helidon/microprofile/lra/LraCdiExtension.java
index 26f4e2d8ef8..6fcea662735 100644
--- a/microprofile/lra/jax-rs/src/main/java/io/helidon/microprofile/lra/LraCdiExtension.java
+++ b/microprofile/lra/jax-rs/src/main/java/io/helidon/microprofile/lra/LraCdiExtension.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +34,8 @@
import java.util.stream.Stream;
import io.helidon.common.Reflected;
+import io.helidon.config.Config;
+import io.helidon.config.mp.MpConfig;
import io.helidon.microprofile.server.ServerCdiExtension;
import io.helidon.webserver.http.HttpService;
@@ -60,6 +62,7 @@
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Application;
+import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.lra.annotation.AfterLRA;
import org.eclipse.microprofile.lra.annotation.Compensate;
import org.eclipse.microprofile.lra.annotation.Complete;
@@ -84,6 +87,7 @@
public class LraCdiExtension implements Extension {
private static final System.Logger LOGGER = System.getLogger(LraCdiExtension.class.getName());
+ private static final String CONFIG_PREFIX = "helidon.lra.participant";
private static final Set> EXPECTED_ANNOTATIONS = Set.of(
AfterLRA.class,
@@ -98,6 +102,7 @@ public class LraCdiExtension implements Extension {
private final Map, Bean>> lraCdiBeanReferences = new HashMap<>();
private final Indexer indexer;
private final ClassLoader classLoader;
+ private final Config config;
private IndexView index;
@@ -105,6 +110,7 @@ public class LraCdiExtension implements Extension {
* Initialize MicroProfile Long Running Actions CDI extension.
*/
public LraCdiExtension() {
+ config = MpConfig.toHelidonConfig(ConfigProvider.getConfig()).get(CONFIG_PREFIX);
indexer = new Indexer();
classLoader = Thread.currentThread().getContextClassLoader();
// Needs to be always indexed
@@ -117,17 +123,9 @@ public LraCdiExtension() {
Application.class,
NonJaxRsResource.class).forEach(c -> runtimeIndex(DotName.createSimple(c.getName())));
- List indexFiles;
- try {
- indexFiles = findIndexFiles("META-INF/jandex.idx");
- if (!indexFiles.isEmpty()) {
- index = CompositeIndex.create(indexer.complete(), existingIndexFileReader(indexFiles));
- } else {
- index = null;
- }
- } catch (IOException e) {
- LOGGER.log(Level.WARNING, "Error when locating Jandex index, fall-back to runtime computed index.", e);
- index = null;
+ Boolean useBuildTimeIndex = config.get("use-build-time-index").asBoolean().orElse(Boolean.TRUE);
+ if (useBuildTimeIndex) {
+ resolveBuildTimeIndex();
}
}
@@ -139,7 +137,7 @@ private void index(
LRA.class,
AfterLRA.class, Compensate.class, Complete.class, Forget.class, Status.class
}) ProcessAnnotatedType> pat) {
- // compile time bilt index
+ // compile time built index
if (index != null) return;
// create runtime index when pre-built index is not available
runtimeIndex(DotName.createSimple(pat.getAnnotatedType().getJavaClass().getName()));
@@ -278,6 +276,21 @@ void runtimeIndex(DotName fqdn) {
}
}
+ private void resolveBuildTimeIndex() {
+ List indexFiles;
+ try {
+ indexFiles = findIndexFiles(config.get("index-resource").asString().orElse("META-INF/jandex.idx"));
+ if (!indexFiles.isEmpty()) {
+ index = CompositeIndex.create(indexer.complete(), existingIndexFileReader(indexFiles));
+ } else {
+ index = null;
+ }
+ } catch (IOException e) {
+ LOGGER.log(Level.WARNING, "Error when locating Jandex index, fall-back to runtime computed index.", e);
+ index = null;
+ }
+ }
+
private IndexView existingIndexFileReader(List indexUrls) throws IOException {
List indices = new ArrayList<>();
for (URL indexURL : indexUrls) {
diff --git a/microprofile/lra/jax-rs/src/main/java/io/helidon/microprofile/lra/ParticipantImpl.java b/microprofile/lra/jax-rs/src/main/java/io/helidon/microprofile/lra/ParticipantImpl.java
index 923e1a9b418..cc2b0355093 100644
--- a/microprofile/lra/jax-rs/src/main/java/io/helidon/microprofile/lra/ParticipantImpl.java
+++ b/microprofile/lra/jax-rs/src/main/java/io/helidon/microprofile/lra/ParticipantImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,6 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.stream.Collectors;
import io.helidon.lra.coordinator.client.Participant;
@@ -150,9 +149,9 @@ public Optional status() {
static Optional getLRAAnnotation(Method m) {
List found = Arrays.stream(m.getDeclaredAnnotations())
.filter(a -> LRA_ANNOTATIONS.contains(a.annotationType()))
- .collect(Collectors.toList());
+ .toList();
- if (found.size() == 0) {
+ if (found.isEmpty()) {
// LRA can be inherited from class or its predecessors
var clazz = m.getDeclaringClass();
do {
diff --git a/microprofile/lra/jax-rs/src/main/java/module-info.java b/microprofile/lra/jax-rs/src/main/java/module-info.java
index 1d778858091..df670692c40 100644
--- a/microprofile/lra/jax-rs/src/main/java/module-info.java
+++ b/microprofile/lra/jax-rs/src/main/java/module-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
)
@SuppressWarnings({ "requires-automatic", "requires-transitive-automatic" })
module io.helidon.microprofile.lra {
+ exports io.helidon.microprofile.lra;
requires io.helidon.common.reactive;
requires io.helidon.config;
@@ -46,6 +47,7 @@
requires jakarta.cdi;
requires transitive jersey.common;
+ requires io.helidon.config.mp;
uses io.helidon.lra.coordinator.client.CoordinatorClient;
diff --git a/microprofile/lra/pom.xml b/microprofile/lra/pom.xml
index 1448ff3df54..8ff245a2f44 100644
--- a/microprofile/lra/pom.xml
+++ b/microprofile/lra/pom.xml
@@ -1,7 +1,7 @@
+
+
+ 4.0.0
+
+ io.helidon.microprofile.lra
+ helidon-microprofile-lra-project
+ 4.2.0-SNAPSHOT
+ ../pom.xml
+
+
+ helidon-microprofile-lra-testing
+ Helidon Microprofile LRA Testing
+
+
+ LRA test support
+
+
+
+
+ io.helidon.microprofile.server
+ helidon-microprofile-server
+
+
+ io.helidon.microprofile.lra
+ helidon-microprofile-lra
+
+
+ io.helidon.lra
+ helidon-lra-coordinator-narayana-client
+
+
+ io.helidon.lra
+ helidon-lra-coordinator-server
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.logging
+ helidon-logging-jul
+ test
+
+
+ io.helidon.microprofile.testing
+ helidon-microprofile-testing-junit5
+ test
+
+
+
+
diff --git a/microprofile/lra/testing/src/main/java/io/helidon/microprofile/testing/lra/TestLraCoordinator.java b/microprofile/lra/testing/src/main/java/io/helidon/microprofile/testing/lra/TestLraCoordinator.java
new file mode 100644
index 00000000000..e3c6bf3a4da
--- /dev/null
+++ b/microprofile/lra/testing/src/main/java/io/helidon/microprofile/testing/lra/TestLraCoordinator.java
@@ -0,0 +1,112 @@
+/*
+ * 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.microprofile.testing.lra;
+
+import java.net.URI;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import io.helidon.config.Config;
+import io.helidon.lra.coordinator.CoordinatorService;
+import io.helidon.lra.coordinator.Lra;
+import io.helidon.microprofile.lra.CoordinatorLocatorService;
+import io.helidon.microprofile.server.RoutingName;
+import io.helidon.microprofile.server.RoutingPath;
+import io.helidon.microprofile.server.ServerCdiExtension;
+import io.helidon.webserver.http.HttpService;
+
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.context.Initialized;
+import jakarta.enterprise.event.Observes;
+import jakarta.enterprise.inject.Produces;
+import jakarta.enterprise.inject.spi.BeanManager;
+import jakarta.inject.Inject;
+
+import static jakarta.interceptor.Interceptor.Priority.PLATFORM_AFTER;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+/**
+ * Enables LRA coordinator on another socket of this server with random port.
+ */
+@ApplicationScoped
+public class TestLraCoordinator {
+
+ static final String ROUTING_NAME = "test-lra-coordinator";
+ static final String CONTEXT_PATH = "/lra-coordinator";
+ private final CompletableFuture port = new CompletableFuture<>();
+ private final ServerCdiExtension serverCdiExtension;
+ private final CoordinatorService coordinatorService;
+
+ @Inject
+ TestLraCoordinator(Config config,
+ ServerCdiExtension serverCdiExtension,
+ CoordinatorLocatorService coordinatorLocator) {
+ this.serverCdiExtension = serverCdiExtension;
+ this.coordinatorService = CoordinatorService.builder()
+ .url(this::coordinatorUri)
+ .config(config.get(CoordinatorService.CONFIG_PREFIX))
+ .build();
+ coordinatorLocator.overrideCoordinatorUriSupplier(this::coordinatorUri);
+ }
+
+ @Produces
+ @ApplicationScoped
+ @RoutingName(value = ROUTING_NAME, required = true)
+ @RoutingPath(CONTEXT_PATH)
+ HttpService coordinatorService() {
+ return coordinatorService;
+ }
+
+ /**
+ * Return test LRA coordinator URL.
+ *
+ * @return coordinator url
+ */
+ public URI coordinatorUri() {
+ return URI.create("http://localhost:" + awaitPort() + CONTEXT_PATH);
+ }
+
+ /**
+ * Get LRA by LraId.
+ *
+ * @param lraId with or without coordinator url prefix.
+ * @return lra when registered by text coordinator
+ */
+ public Lra lra(String lraId) {
+ if (lraId == null) {
+ return null;
+ }
+ if (lraId.startsWith(coordinatorUri() + "/")) {
+ return coordinatorService.lra(lraId.substring(coordinatorUri().toString().length() + 1));
+ }
+ return coordinatorService.lra(lraId);
+ }
+
+ private void ready(@Observes @Priority(PLATFORM_AFTER + 101) @Initialized(ApplicationScoped.class) Object e, BeanManager b) {
+ port.complete(serverCdiExtension.port(ROUTING_NAME));
+ }
+
+ private int awaitPort() {
+ try {
+ return port.get(20, SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/microprofile/lra/testing/src/main/java/io/helidon/microprofile/testing/lra/TestLraCoordinatorConfigSource.java b/microprofile/lra/testing/src/main/java/io/helidon/microprofile/testing/lra/TestLraCoordinatorConfigSource.java
new file mode 100644
index 00000000000..ad1a1e89f7e
--- /dev/null
+++ b/microprofile/lra/testing/src/main/java/io/helidon/microprofile/testing/lra/TestLraCoordinatorConfigSource.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.microprofile.testing.lra;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.microprofile.config.spi.ConfigSource;
+
+/**
+ * Configuration for {@code @HelidonTest} with LRA coordinator running on random port.
+ * Any of the properties can be overridden with {@code @AddConfig}.
+ * Example of running test coordinator on port 8070:
+ *
+ */
+public class TestLraCoordinatorConfigSource implements ConfigSource {
+
+ private static final String PORT_IDX = System.getProperty("helidon.lra.coordinator.test-socket.index", "500");
+ private static final Map CONFIG = Map.of(
+ // Extra socket for coordinator on random port
+ "server.sockets." + PORT_IDX + ".name", TestLraCoordinator.ROUTING_NAME,
+ "server.sockets." + PORT_IDX + ".port", "0",
+ "server.sockets." + PORT_IDX + ".bind-address", "localhost",
+ // Avoid using persistent tx log in test LRA coordinator
+ "helidon.lra.coordinator.persistence", "false",
+ // Avoid using build time Jandex index
+ "helidon.lra.participant.use-build-time-index", "false");
+
+ /**
+ * Initialized by service locator.
+ */
+ public TestLraCoordinatorConfigSource() {
+ }
+
+ @Override
+ public Set getPropertyNames() {
+ return CONFIG.keySet();
+ }
+
+ @Override
+ public int getOrdinal() {
+ return 5000;
+ }
+
+ @Override
+ public String getValue(String propertyName) {
+ return CONFIG.get(propertyName);
+ }
+
+ @Override
+ public String getName() {
+ return TestLraCoordinator.ROUTING_NAME;
+ }
+}
diff --git a/microprofile/lra/testing/src/main/java/io/helidon/microprofile/testing/lra/package-info.java b/microprofile/lra/testing/src/main/java/io/helidon/microprofile/testing/lra/package-info.java
new file mode 100644
index 00000000000..a1737087e36
--- /dev/null
+++ b/microprofile/lra/testing/src/main/java/io/helidon/microprofile/testing/lra/package-info.java
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+/**
+ * Test LRA coordinator.
+ * Allows simplified testing of LRA enabled JAX-RS resources.
+ *
+ *
+ */
+package io.helidon.microprofile.testing.lra;
diff --git a/microprofile/lra/testing/src/main/java/module-info.java b/microprofile/lra/testing/src/main/java/module-info.java
new file mode 100644
index 00000000000..d6c8c402868
--- /dev/null
+++ b/microprofile/lra/testing/src/main/java/module-info.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.eclipse.microprofile.config.spi.ConfigSource;
+
+/**
+ * Test LRA coordinator.
+ */
+module io.helidon.microprofile.testing.lra {
+
+ requires io.helidon.config.mp;
+ requires io.helidon.config.yaml.mp;
+ requires io.helidon.microprofile.cdi;
+ requires jakarta.inject;
+
+ requires transitive jakarta.cdi;
+ requires transitive jakarta.ws.rs;
+
+ requires static io.helidon.microprofile.server;
+ requires io.helidon.lra.coordinator;
+ requires io.helidon.microprofile.lra;
+
+ exports io.helidon.microprofile.testing.lra;
+
+ provides org.eclipse.microprofile.config.spi.ConfigSource
+ with io.helidon.microprofile.testing.lra.TestLraCoordinatorConfigSource;
+
+}
diff --git a/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraDisabledDiscoveryResourceTest.java b/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraDisabledDiscoveryResourceTest.java
new file mode 100644
index 00000000000..77607fc7005
--- /dev/null
+++ b/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraDisabledDiscoveryResourceTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.microprofile.testing.lra;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import io.helidon.lra.coordinator.Lra;
+import io.helidon.microprofile.config.ConfigCdiExtension;
+import io.helidon.microprofile.lra.LraCdiExtension;
+import io.helidon.microprofile.testing.junit5.AddBean;
+import io.helidon.microprofile.testing.junit5.AddExtension;
+import io.helidon.microprofile.testing.junit5.AddJaxRs;
+import io.helidon.microprofile.testing.junit5.DisableDiscovery;
+import io.helidon.microprofile.testing.junit5.HelidonTest;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.eclipse.microprofile.lra.annotation.Compensate;
+import org.eclipse.microprofile.lra.annotation.Complete;
+import org.eclipse.microprofile.lra.annotation.LRAStatus;
+import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.is;
+
+@HelidonTest
+@DisableDiscovery
+@AddJaxRs
+@AddBean(TestLraCoordinator.class)
+@AddExtension(LraCdiExtension.class)
+@AddExtension(ConfigCdiExtension.class)
+@Path("/test/internal")
+public class LraDisabledDiscoveryResourceTest {
+
+ private final WebTarget target;
+ private final Set completedLras;
+ private final Set cancelledLras;
+ private final TestLraCoordinator coordinator;
+
+ @Inject
+ public LraDisabledDiscoveryResourceTest(WebTarget target,
+ TestLraCoordinator coordinator) {
+ this.target = target;
+ this.coordinator = coordinator;
+ this.completedLras = new CopyOnWriteArraySet<>();
+ this.cancelledLras = new CopyOnWriteArraySet<>();
+ }
+
+ @PUT
+ @Path("/withdraw")
+ @LRA(LRA.Type.REQUIRES_NEW)
+ public Response withdraw(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) Optional lraId, String content) {
+ if ("BOOM".equals(content)) {
+ throw new IllegalArgumentException("BOOM");
+ }
+ return Response.ok().build();
+ }
+
+ @Complete
+ public void complete(URI lraId) {
+ completedLras.add(lraId.toString());
+ }
+
+ @Compensate
+ public void rollback(URI lraId) {
+ cancelledLras.add(lraId.toString());
+ }
+
+ @Test
+ public void testLraComplete() {
+ try (Response res = target
+ .path("/test/internal/withdraw")
+ .request()
+ .put(Entity.entity("test", MediaType.TEXT_PLAIN_TYPE))) {
+ assertThat(res.getStatus(), is(200));
+ String lraId = res.getHeaderString(LRA.LRA_HTTP_CONTEXT_HEADER);
+ Lra lra = coordinator.lra(lraId);
+ assertThat(lra.status(), is(LRAStatus.Closed));
+ assertThat(completedLras, contains(lraId));
+ }
+ }
+
+ @Test
+ public void testLraCompensate() {
+ try (Response res = target
+ .path("/test/internal/withdraw")
+ .request()
+ .put(Entity.entity("BOOM", MediaType.TEXT_PLAIN_TYPE))) {
+ assertThat(res.getStatus(), is(500));
+ String lraId = res.getHeaderString(LRA.LRA_HTTP_CONTEXT_HEADER);
+ Lra lra = coordinator.lra(lraId);
+ assertThat(lra.status(), is(LRAStatus.Cancelled));
+ assertThat(cancelledLras, contains(lraId));
+ }
+ }
+}
diff --git a/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraExternalResourceTest.java b/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraExternalResourceTest.java
new file mode 100644
index 00000000000..b2cf90842d6
--- /dev/null
+++ b/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraExternalResourceTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.microprofile.testing.lra;
+
+import io.helidon.lra.coordinator.Lra;
+import io.helidon.microprofile.testing.junit5.AddBean;
+import io.helidon.microprofile.testing.junit5.HelidonTest;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.eclipse.microprofile.lra.annotation.LRAStatus;
+import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.is;
+
+@HelidonTest
+@AddBean(WithdrawTestResource.class)
+@AddBean(TestLraCoordinator.class)
+public class LraExternalResourceTest {
+
+ private final WithdrawTestResource withdrawTestResource;
+ private final TestLraCoordinator coordinator;
+ private final WebTarget target;
+
+ @Inject
+ public LraExternalResourceTest(WithdrawTestResource withdrawTestResource, TestLraCoordinator coordinator, WebTarget target) {
+ this.withdrawTestResource = withdrawTestResource;
+ this.coordinator = coordinator;
+ this.target = target;
+ }
+
+ @Test
+ public void testLraComplete() {
+ try (Response res = target
+ .path("/test/external/withdraw")
+ .request()
+ .put(Entity.entity("test", MediaType.TEXT_PLAIN_TYPE))) {
+ assertThat(res.getStatus(), is(200));
+ String lraId = res.getHeaderString(LRA.LRA_HTTP_CONTEXT_HEADER);
+ Lra lra = coordinator.lra(lraId);
+ assertThat(lra.status(), is(LRAStatus.Closed));
+ assertThat(withdrawTestResource.getCompletedLras(), contains(lraId));
+ }
+ }
+
+ @Test
+ public void testLraCompensate() {
+ try (Response res = target
+ .path("/test/external/withdraw")
+ .request()
+ .put(Entity.entity("BOOM", MediaType.TEXT_PLAIN_TYPE))) {
+ assertThat(res.getStatus(), is(500));
+ String lraId = res.getHeaderString(LRA.LRA_HTTP_CONTEXT_HEADER);
+ Lra lra = coordinator.lra(lraId);
+ assertThat(lra.status(), is(LRAStatus.Cancelled));
+ assertThat(withdrawTestResource.getCancelledLras(), contains(lraId));
+ }
+ }
+}
diff --git a/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraMultiPortTest.java b/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraMultiPortTest.java
new file mode 100644
index 00000000000..a0487365e1a
--- /dev/null
+++ b/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/LraMultiPortTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.microprofile.testing.lra;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import io.helidon.lra.coordinator.Lra;
+import io.helidon.microprofile.server.RoutingName;
+import io.helidon.microprofile.server.RoutingPath;
+import io.helidon.microprofile.testing.junit5.AddBean;
+import io.helidon.microprofile.testing.junit5.AddConfig;
+import io.helidon.microprofile.testing.junit5.HelidonTest;
+import io.helidon.microprofile.testing.junit5.Socket;
+import io.helidon.webserver.http.HttpService;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Produces;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.eclipse.microprofile.lra.annotation.Compensate;
+import org.eclipse.microprofile.lra.annotation.Complete;
+import org.eclipse.microprofile.lra.annotation.LRAStatus;
+import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.is;
+
+@HelidonTest
+@AddConfig(key = "server.sockets.0.name", value = "test-route")
+@AddConfig(key = "server.sockets.0.port", value = "0")
+@AddConfig(key = "server.sockets.0.bind-address", value = "localhost")
+@AddBean(TestLraCoordinator.class)
+@Path("/test/multi-port")
+public class LraMultiPortTest {
+
+ private final WebTarget target;
+ private final WebTarget otherTarget;
+ private final Set completedLras;
+ private final Set cancelledLras;
+ private final TestLraCoordinator coordinator;
+
+ @Inject
+ public LraMultiPortTest(WebTarget target,
+ TestLraCoordinator coordinator,
+ @Socket("test-route") WebTarget otherTarget) {
+ this.target = target;
+ this.coordinator = coordinator;
+ this.otherTarget = otherTarget;
+ this.completedLras = new CopyOnWriteArraySet<>();
+ this.cancelledLras = new CopyOnWriteArraySet<>();
+ }
+
+ @Produces
+ @ApplicationScoped
+ @RoutingName("test-route")
+ @RoutingPath("/test/route")
+ public HttpService anotherRoute() {
+ return r -> r.any((req, res) -> res.send("Hello from test route!"));
+ }
+
+ @PUT
+ @Path("/outer")
+ @LRA(LRA.Type.REQUIRES_NEW)
+ public Response withdraw(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) Optional lraId, String content) {
+ try (Response res = target.path("/test/multi-port/inner")
+ .request()
+ .put(Entity.entity(content, MediaType.TEXT_PLAIN_TYPE))) {
+ assertThat(res.getStatus(), is(Response.Status.OK.getStatusCode()));
+ }
+ return Response.ok().build();
+ }
+
+ @PUT
+ @Path("/inner")
+ @LRA(LRA.Type.REQUIRED)
+ public Response deposit(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) Optional lraId, String content) {
+ if ("BOOM".equals(content)) {
+ throw new IllegalArgumentException("BOOM");
+ }
+ return Response.ok().build();
+ }
+
+ @Complete
+ public void complete(URI lraId) {
+ completedLras.add(lraId.toString());
+ }
+
+ @Compensate
+ public void rollback(URI lraId) {
+ cancelledLras.add(lraId.toString());
+ }
+
+ @Test
+ public void testLra() {
+ try (Response res = target
+ .path("/test/multi-port/outer")
+ .request()
+ .put(Entity.entity("test", MediaType.TEXT_PLAIN_TYPE))) {
+ assertThat(res.getStatus(), is(200));
+ String lraId = res.getHeaderString(LRA.LRA_HTTP_CONTEXT_HEADER);
+ Lra lra = coordinator.lra(lraId);
+ assertThat(lra.status(), is(LRAStatus.Closed));
+ assertThat(completedLras, contains(lraId));
+ }
+ }
+
+ @Test
+ public void testCompensatedLra() {
+ try (Response res = target
+ .path("/test/multi-port/outer")
+ .request()
+ .put(Entity.entity("BOOM", MediaType.TEXT_PLAIN_TYPE))) {
+ assertThat(res.getStatus(), is(500));
+ String lraId = res.getHeaderString(LRA.LRA_HTTP_CONTEXT_HEADER);
+ Lra lra = coordinator.lra(lraId);
+ assertThat(lra.status(), is(LRAStatus.Cancelled));
+ assertThat(cancelledLras, contains(lraId));
+ }
+ }
+
+ @Test
+ public void testOtherRoute() {
+ try (Response res = otherTarget
+ .path("/test/route")
+ .request()
+ .get()) {
+ assertThat(res.getStatus(), is(200));
+ assertThat(res.readEntity(String.class), is("Hello from test route!"));
+ }
+ }
+
+}
diff --git a/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/NonLraResourceTest.java b/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/NonLraResourceTest.java
new file mode 100644
index 00000000000..774541b117c
--- /dev/null
+++ b/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/NonLraResourceTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.microprofile.testing.lra;
+
+import io.helidon.microprofile.testing.junit5.HelidonTest;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Response;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+@HelidonTest
+@Path("/test/non-lra")
+public class NonLraResourceTest {
+
+ private final WebTarget target;
+
+ @Inject
+ public NonLraResourceTest(WebTarget target) {
+ this.target = target;
+ }
+
+ @GET
+ @Path("/say-hi")
+ public String sayHi() {
+ return "Hi!";
+ }
+
+ @Test
+ public void testNonLraResource() {
+ try (Response res = target
+ .path("/test/non-lra/say-hi")
+ .request()
+ .get()) {
+ assertThat(res.getStatus(), is(200));
+ assertThat(res.readEntity(String.class), is("Hi!"));
+ }
+ }
+}
diff --git a/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/WithdrawTestResource.java b/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/WithdrawTestResource.java
new file mode 100644
index 00000000000..9487bc54b50
--- /dev/null
+++ b/microprofile/lra/testing/src/test/java/io/helidon/microprofile/testing/lra/WithdrawTestResource.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.microprofile.testing.lra;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Response;
+import org.eclipse.microprofile.lra.annotation.Compensate;
+import org.eclipse.microprofile.lra.annotation.Complete;
+import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
+
+@ApplicationScoped
+@Path("/test/external")
+public class WithdrawTestResource {
+
+ private final Set completedLras;
+ private final Set cancelledLras;
+
+ public WithdrawTestResource() {
+ this.completedLras = new CopyOnWriteArraySet<>();
+ this.cancelledLras = new CopyOnWriteArraySet<>();
+ }
+
+ @PUT
+ @Path("/withdraw")
+ @LRA(LRA.Type.REQUIRES_NEW)
+ public Response withdraw(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) Optional lraId, String content) {
+ if ("BOOM".equals(content)) {
+ throw new IllegalArgumentException("BOOM");
+ }
+ return Response.ok().build();
+ }
+
+ @Complete
+ public void complete(URI lraId) {
+ completedLras.add(lraId.toString());
+ }
+
+ @Compensate
+ public void rollback(URI lraId) {
+ cancelledLras.add(lraId.toString());
+ }
+
+ Set getCompletedLras() {
+ return completedLras;
+ }
+
+ Set getCancelledLras() {
+ return cancelledLras;
+ }
+}
diff --git a/microprofile/lra/testing/src/test/resources/logging-test.properties b/microprofile/lra/testing/src/test/resources/logging-test.properties
new file mode 100644
index 00000000000..a61f39ea0cb
--- /dev/null
+++ b/microprofile/lra/testing/src/test/resources/logging-test.properties
@@ -0,0 +1,25 @@
+#
+# 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.
+#
+
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=WARNING
+
+io.helidon.level=INFO
diff --git a/microprofile/messaging/core/pom.xml b/microprofile/messaging/core/pom.xml
index eb8a1d95b98..95d03e55c74 100644
--- a/microprofile/messaging/core/pom.xml
+++ b/microprofile/messaging/core/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.messaginghelidon-microprofile-messaging-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-messaging
diff --git a/microprofile/messaging/health/pom.xml b/microprofile/messaging/health/pom.xml
index db4a7fd548b..9da91150737 100644
--- a/microprofile/messaging/health/pom.xml
+++ b/microprofile/messaging/health/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.messaginghelidon-microprofile-messaging-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-messaging-health
diff --git a/microprofile/messaging/metrics/pom.xml b/microprofile/messaging/metrics/pom.xml
index 2fc436f3ddf..e07edf4f76c 100644
--- a/microprofile/messaging/metrics/pom.xml
+++ b/microprofile/messaging/metrics/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.messaginghelidon-microprofile-messaging-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-messaging-metrics
diff --git a/microprofile/messaging/pom.xml b/microprofile/messaging/pom.xml
index 9459a75c439..006d4294f77 100644
--- a/microprofile/messaging/pom.xml
+++ b/microprofile/messaging/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.messaging
diff --git a/microprofile/metrics/pom.xml b/microprofile/metrics/pom.xml
index 8a629850243..aba57224710 100644
--- a/microprofile/metrics/pom.xml
+++ b/microprofile/metrics/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.metricshelidon-microprofile-metrics
diff --git a/microprofile/oidc/pom.xml b/microprofile/oidc/pom.xml
index 8b498d5aa64..d60405b0d32 100644
--- a/microprofile/oidc/pom.xml
+++ b/microprofile/oidc/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-oidcHelidon Microprofile Security OIDC Integration
diff --git a/microprofile/openapi/pom.xml b/microprofile/openapi/pom.xml
index 0adf9f47f46..78bc463c743 100644
--- a/microprofile/openapi/pom.xml
+++ b/microprofile/openapi/pom.xml
@@ -21,7 +21,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.openapihelidon-microprofile-openapi
diff --git a/microprofile/pom.xml b/microprofile/pom.xml
index b1e99f51df2..5f157a3c6d8 100644
--- a/microprofile/pom.xml
+++ b/microprofile/pom.xml
@@ -24,7 +24,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomio.helidon.microprofile
diff --git a/microprofile/reactive-streams/pom.xml b/microprofile/reactive-streams/pom.xml
index 8532a8f57ce..68bd8493505 100644
--- a/microprofile/reactive-streams/pom.xml
+++ b/microprofile/reactive-streams/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.reactive-streams
diff --git a/microprofile/rest-client/pom.xml b/microprofile/rest-client/pom.xml
index af4c65e87eb..a0a9ffbe1ef 100644
--- a/microprofile/rest-client/pom.xml
+++ b/microprofile/rest-client/pom.xml
@@ -23,7 +23,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.rest-client
diff --git a/microprofile/scheduling/pom.xml b/microprofile/scheduling/pom.xml
index 875690936bc..1b9d5470bc8 100644
--- a/microprofile/scheduling/pom.xml
+++ b/microprofile/scheduling/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.scheduling
diff --git a/microprofile/security/pom.xml b/microprofile/security/pom.xml
index ead9cde3743..4c7c4e2c16e 100644
--- a/microprofile/security/pom.xml
+++ b/microprofile/security/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-securityHelidon Microprofile Security Integration
diff --git a/microprofile/security/src/main/java/io/helidon/microprofile/security/JerseySecurityContext.java b/microprofile/security/src/main/java/io/helidon/microprofile/security/JerseySecurityContext.java
index 76bd0453d9e..479d3e782df 100644
--- a/microprofile/security/src/main/java/io/helidon/microprofile/security/JerseySecurityContext.java
+++ b/microprofile/security/src/main/java/io/helidon/microprofile/security/JerseySecurityContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 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.
@@ -36,7 +36,7 @@ public Principal getUserPrincipal() {
@Override
public boolean isUserInRole(String role) {
- return securityContext.isUserInRole(role, methodSecurity.getAuthorizer());
+ return securityContext.isUserInRole(role, methodSecurity.authorizer());
}
@Override
diff --git a/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityDefinition.java b/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityDefinition.java
index 4e3df064f93..53624d5f9ab 100644
--- a/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityDefinition.java
+++ b/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityDefinition.java
@@ -21,6 +21,7 @@
import java.util.List;
import java.util.Map;
+import io.helidon.common.config.Config;
import io.helidon.security.AuditEvent;
import io.helidon.security.SecurityLevel;
import io.helidon.security.annotations.Audited;
@@ -97,7 +98,21 @@ SecurityDefinition copyMe() {
return result;
}
- public void add(Authenticated atn) {
+ void fromConfig(Config config) {
+ config.get("authorize").as(Boolean.class).ifPresent(this::requiresAuthorization);
+ config.get("authorizer").as(String.class).ifPresent(this::authorizer);
+ config.get("authorization-explicit").as(Boolean.class).ifPresent(this::atzExplicit);
+ config.get("authenticate").as(Boolean.class).ifPresent(this::requiresAuthentication);
+ config.get("authenticator").as(String.class).ifPresent(this::authenticator);
+ config.get("authentication-optional").as(Boolean.class).ifPresent(this::authenticationOptional);
+ config.get("audit").as(Boolean.class).ifPresent(this::audited);
+ config.get("audit-event-type").as(String.class).ifPresent(this::auditEventType);
+ config.get("audit-message-format").as(String.class).ifPresent(this::auditMessageFormat);
+ config.get("audit-ok-severity").as(AuditEvent.AuditSeverity.class).ifPresent(this::auditOkSeverity);
+ config.get("audit-error-severity").as(AuditEvent.AuditSeverity.class).ifPresent(this::auditErrorSeverity);
+ }
+
+ void add(Authenticated atn) {
if (null == atn) {
return;
}
@@ -106,7 +121,7 @@ public void add(Authenticated atn) {
this.authenticator = "".equals(atn.provider()) ? null : atn.provider();
}
- public void add(Authorized atz) {
+ void add(Authorized atz) {
if (null == atz) {
return;
}
@@ -130,7 +145,7 @@ void requiresAuthentication(boolean atn) {
this.requiresAuthentication = atn;
}
- void setRequiresAuthorization(boolean atz) {
+ void requiresAuthorization(boolean atz) {
this.requiresAuthorization = atz;
}
@@ -154,10 +169,18 @@ boolean authenticationOptional() {
return authnOptional;
}
+ void authenticationOptional(boolean authnOptional) {
+ this.authnOptional = authnOptional;
+ }
+
boolean failOnFailureIfOptional() {
return failOnFailureIfOptional;
}
+ void failOnFailureIfOptional(boolean failOnFailureIfOptional) {
+ this.failOnFailureIfOptional = failOnFailureIfOptional;
+ }
+
boolean requiresAuthorization() {
if (null != requiresAuthorization) {
return requiresAuthorization;
@@ -171,47 +194,79 @@ boolean requiresAuthorization() {
return (count != 0) || authorizeByDefault;
}
- public boolean isAtzExplicit() {
+ boolean atzExplicit() {
return atzExplicit;
}
- String getAuthenticator() {
+ void atzExplicit(boolean atzExplicit) {
+ this.atzExplicit = atzExplicit;
+ }
+
+ String authenticator() {
return authenticator;
}
- String getAuthorizer() {
+ void authenticator(String authenticator) {
+ this.authenticator = authenticator;
+ }
+
+ String authorizer() {
return authorizer;
}
- public List getSecurityLevels() {
+ void authorizer(String authorizer) {
+ this.authorizer = authorizer;
+ }
+
+ List securityLevels() {
return securityLevels;
}
- public boolean isAudited() {
+ boolean audited() {
return audited;
}
- public String getAuditEventType() {
+ void audited(boolean audited) {
+ this.audited = audited;
+ }
+
+ String auditEventType() {
return auditEventType;
}
- public String getAuditMessageFormat() {
+ void auditEventType(String auditEventType) {
+ this.auditEventType = auditEventType;
+ }
+
+ String auditMessageFormat() {
return auditMessageFormat;
}
- public AuditEvent.AuditSeverity getAuditOkSeverity() {
+ void auditMessageFormat(String auditMessageFormat) {
+ this.auditMessageFormat = auditMessageFormat;
+ }
+
+ AuditEvent.AuditSeverity auditOkSeverity() {
return auditOkSeverity;
}
- public AuditEvent.AuditSeverity getAuditErrorSeverity() {
+ void auditOkSeverity(AuditEvent.AuditSeverity auditOkSeverity) {
+ this.auditOkSeverity = auditOkSeverity;
+ }
+
+ AuditEvent.AuditSeverity auditErrorSeverity() {
return auditErrorSeverity;
}
- public AnnotationAnalyzer.AnalyzerResponse analyzerResponse(AnnotationAnalyzer analyzer) {
+ void auditErrorSeverity(AuditEvent.AuditSeverity auditOkSeverity) {
+ this.auditErrorSeverity = auditOkSeverity;
+ }
+
+ AnnotationAnalyzer.AnalyzerResponse analyzerResponse(AnnotationAnalyzer analyzer) {
return analyzerResponses.get(analyzer);
}
- public void analyzerResponse(AnnotationAnalyzer analyzer, AnnotationAnalyzer.AnalyzerResponse analyzerResponse) {
+ void analyzerResponse(AnnotationAnalyzer analyzer, AnnotationAnalyzer.AnalyzerResponse analyzerResponse) {
analyzerResponses.put(analyzer, analyzerResponse);
switch (analyzerResponse.authenticationResponse()) {
diff --git a/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityFilter.java b/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityFilter.java
index 8fc0fc23e98..101a640e81f 100644
--- a/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityFilter.java
+++ b/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityFilter.java
@@ -31,6 +31,7 @@
import io.helidon.common.HelidonServiceLoader;
import io.helidon.common.config.Config;
import io.helidon.common.context.Contexts;
+import io.helidon.common.uri.UriPath;
import io.helidon.jersey.common.InvokedResource;
import io.helidon.security.AuditEvent;
import io.helidon.security.Security;
@@ -155,9 +156,9 @@ protected void processSecurity(ContainerRequestContext request,
* Authentication
*/
authenticate(filterContext, securityContext, tracing.atnTracing());
- LOGGER.log(Level.TRACE, () -> "Filter after authentication. Should finish: " + filterContext.isShouldFinish());
+ LOGGER.log(Level.TRACE, () -> "Filter after authentication. Should finish: " + filterContext.shouldFinish());
// authentication failed
- if (filterContext.isShouldFinish()) {
+ if (filterContext.shouldFinish()) {
return;
}
@@ -203,7 +204,7 @@ public void filter(ContainerRequestContext requestContext, ContainerResponseCont
SecurityDefinition methodSecurity = jerseySecurityContext.methodSecurity();
SecurityContext securityContext = jerseySecurityContext.securityContext();
- if (fc.isExplicitAtz() && !securityContext.isAuthorized()) {
+ if (fc.explicitAtz() && !securityContext.isAuthorized()) {
// now we have an option that the response code is already an error (e.g. BadRequest)
// in such a case we return the original error, as we may have never reached the method code
switch (responseContext.getStatusInfo().getFamily()) {
@@ -223,11 +224,11 @@ public void filter(ContainerRequestContext requestContext, ContainerResponseCont
responseContext.setEntity("");
}
responseContext.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
- LOGGER.log(Level.ERROR, "Authorization failure. Request for" + fc.getResourcePath()
+ LOGGER.log(Level.ERROR, "Authorization failure. Request for" + fc.resourcePath()
+ " has failed, as it was marked"
+ "as explicitly authorized, yet authorization was never called on security context. The "
+ "method was invoked and may have changed data. Marking as internal server error");
- fc.setShouldFinish(true);
+ fc.shouldFinish(true);
break;
}
}
@@ -235,26 +236,26 @@ public void filter(ContainerRequestContext requestContext, ContainerResponseCont
ResponseTracing responseTracing = SecurityTracing.get().responseTracing();
try {
- if (methodSecurity.isAudited()) {
+ if (methodSecurity.audited()) {
AuditEvent.AuditSeverity auditSeverity;
if (responseContext.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) {
- auditSeverity = methodSecurity.getAuditOkSeverity();
+ auditSeverity = methodSecurity.auditOkSeverity();
} else {
- auditSeverity = methodSecurity.getAuditErrorSeverity();
+ auditSeverity = methodSecurity.auditErrorSeverity();
}
SecurityAuditEvent auditEvent = SecurityAuditEvent
- .audit(auditSeverity, methodSecurity.getAuditEventType(), methodSecurity.getAuditMessageFormat())
- .addParam(AuditEvent.AuditParam.plain("method", fc.getMethod()))
- .addParam(AuditEvent.AuditParam.plain("path", fc.getResourcePath()))
+ .audit(auditSeverity, methodSecurity.auditEventType(), methodSecurity.auditMessageFormat())
+ .addParam(AuditEvent.AuditParam.plain("method", fc.method()))
+ .addParam(AuditEvent.AuditParam.plain("path", fc.resourcePath()))
.addParam(AuditEvent.AuditParam.plain("status", String.valueOf(responseContext.getStatus())))
.addParam(AuditEvent.AuditParam.plain("subject",
securityContext.user()
.or(securityContext::service)
.orElse(SecurityContext.ANONYMOUS)))
.addParam(AuditEvent.AuditParam.plain("transport", "http"))
- .addParam(AuditEvent.AuditParam.plain("resourceType", fc.getResourceName()))
- .addParam(AuditEvent.AuditParam.plain("targetUri", fc.getTargetUri()));
+ .addParam(AuditEvent.AuditParam.plain("resourceType", fc.resourceName()))
+ .addParam(AuditEvent.AuditParam.plain("targetUri", fc.targetUri()));
securityContext.audit(auditEvent);
}
@@ -271,16 +272,16 @@ protected SecurityFilterContext initRequestFiltering(ContainerRequestContext req
return invokedResource
.definitionMethod()
.map(definitionMethod -> {
- context.setMethodSecurity(getMethodSecurity(invokedResource,
- definitionMethod,
- (ExtendedUriInfo) requestContext.getUriInfo()));
- context.setResourceName(definitionMethod.getDeclaringClass().getSimpleName());
+ context.methodSecurity(getMethodSecurity(invokedResource,
+ definitionMethod,
+ (ExtendedUriInfo) requestContext.getUriInfo()));
+ context.resourceName(definitionMethod.getDeclaringClass().getSimpleName());
return configureContext(context, requestContext, requestContext.getUriInfo());
})
.orElseGet(() -> {
// this will end in 404, just let it on
- context.setShouldFinish(true);
+ context.shouldFinish(true);
return context;
});
}
@@ -325,7 +326,7 @@ private SecurityDefinition securityForClass(Class> theClass, SecurityDefinitio
SecurityLevel securityLevel = SecurityLevel.create(realClass.getName())
.withClassAnnotations(customAnnotsMap)
.build();
- definition.getSecurityLevels().add(securityLevel);
+ definition.securityLevels().add(securityLevel);
for (AnnotationAnalyzer analyzer : analyzers) {
AnnotationAnalyzer.AnalyzerResponse analyzerResponse;
@@ -344,20 +345,6 @@ private SecurityDefinition securityForClass(Class> theClass, SecurityDefinitio
return definition;
}
- /**
- * Returns the real class of this object, skipping proxies.
- *
- * @param object The object.
- * @return Its class.
- */
- private static Class> getRealClass(Class> object) {
- Class> result = object;
- while (result.isSynthetic()) {
- result = result.getSuperclass();
- }
- return result;
- }
-
private SecurityDefinition getMethodSecurity(InvokedResource invokedResource,
Method definitionMethod,
ExtendedUriInfo uriInfo) {
@@ -427,9 +414,9 @@ private SecurityDefinition getMethodSecurity(InvokedResource invokedResource,
for (Method method : methodsToProcess) {
Class> clazz = method.getDeclaringClass();
current = securityForClass(clazz, current);
- SecurityDefinition methodDef = processMethod(current.copyMe(), method);
+ SecurityDefinition methodDef = processMethod(current.copyMe(), uriInfo.getPath(), method);
- SecurityLevel currentSecurityLevel = methodDef.getSecurityLevels().get(methodDef.getSecurityLevels().size() - 1);
+ SecurityLevel currentSecurityLevel = methodDef.securityLevels().get(methodDef.securityLevels().size() - 1);
Map, List> methodAnnotations = new HashMap<>();
addCustomAnnotations(methodAnnotations, method);
@@ -437,7 +424,7 @@ private SecurityDefinition getMethodSecurity(InvokedResource invokedResource,
.withMethodName(method.getName())
.withMethodAnnotations(methodAnnotations)
.build();
- methodDef.getSecurityLevels().set(methodDef.getSecurityLevels().size() - 1, newSecurityLevel);
+ methodDef.securityLevels().set(methodDef.securityLevels().size() - 1, newSecurityLevel);
for (AnnotationAnalyzer analyzer : analyzers) {
AnnotationAnalyzer.AnalyzerResponse analyzerResponse = analyzer.analyze(method,
current.analyzerResponse(analyzer));
@@ -466,16 +453,14 @@ private SecurityDefinition getMethodSecurity(InvokedResource invokedResource,
}
SecurityDefinition resClassSecurity = obtainClassSecurityDefinition(appRealClass, appClassSecurity, definitionClass);
+ SecurityDefinition methodDef = processMethod(resClassSecurity, uriInfo.getRequestUri().getPath(), definitionMethod);
-
- SecurityDefinition methodDef = processMethod(resClassSecurity, definitionMethod);
-
- int index = methodDef.getSecurityLevels().size() - 1;
- SecurityLevel currentSecurityLevel = methodDef.getSecurityLevels().get(index);
+ int index = methodDef.securityLevels().size() - 1;
+ SecurityLevel currentSecurityLevel = methodDef.securityLevels().get(index);
Map, List> methodLevelAnnotations = new HashMap<>();
addCustomAnnotations(methodLevelAnnotations, definitionMethod);
- methodDef.getSecurityLevels().set(index, SecurityLevel.create(currentSecurityLevel)
+ methodDef.securityLevels().set(index, SecurityLevel.create(currentSecurityLevel)
.withMethodName(definitionMethod.getName())
.withMethodAnnotations(methodLevelAnnotations)
.build());
@@ -533,14 +518,19 @@ List analyzers() {
return this.analyzers;
}
- private static SecurityDefinition processMethod(SecurityDefinition current, Method method) {
- Authenticated atn = method.getAnnotation(Authenticated.class);
- Authorized atz = method.getAnnotation(Authorized.class);
- Audited audited = method.getAnnotation(Audited.class);
+ private SecurityDefinition processMethod(SecurityDefinition current, String path, Method method) {
SecurityDefinition methodDef = current.copyMe();
- methodDef.add(atn);
- methodDef.add(atz);
- methodDef.add(audited);
+ findMethodConfig(UriPath.create(path))
+ .asNode()
+ .ifPresentOrElse(methodDef::fromConfig,
+ () -> {
+ Authenticated atn = method.getAnnotation(Authenticated.class);
+ Authorized atz = method.getAnnotation(Authorized.class);
+ Audited audited = method.getAnnotation(Audited.class);
+ methodDef.add(atn);
+ methodDef.add(atz);
+ methodDef.add(audited);
+ });
return methodDef;
}
diff --git a/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityFilterCommon.java b/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityFilterCommon.java
index a2b7578b5b8..f37a97024ff 100644
--- a/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityFilterCommon.java
+++ b/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityFilterCommon.java
@@ -23,9 +23,14 @@
import java.util.ServiceLoader;
import io.helidon.common.HelidonServiceLoader;
+import io.helidon.common.LazyValue;
import io.helidon.common.config.Config;
import io.helidon.common.context.Contexts;
+import io.helidon.common.uri.UriPath;
import io.helidon.common.uri.UriQuery;
+import io.helidon.config.mp.MpConfig;
+import io.helidon.http.PathMatcher;
+import io.helidon.http.PathMatchers;
import io.helidon.microprofile.security.spi.SecurityResponseMapper;
import io.helidon.security.AuthenticationResponse;
import io.helidon.security.AuthorizationResponse;
@@ -45,6 +50,7 @@
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
+import org.eclipse.microprofile.config.ConfigProvider;
import org.glassfish.jersey.server.ContainerRequest;
/**
@@ -55,6 +61,7 @@ abstract class SecurityFilterCommon {
private static final List RESPONSE_MAPPERS = HelidonServiceLoader
.builder(ServiceLoader.load(SecurityResponseMapper.class)).build().asList();
+ private static final LazyValue> PATH_CONFIGS = LazyValue.create(SecurityFilterCommon::createPathConfigs);
private final Security security;
@@ -67,6 +74,30 @@ abstract class SecurityFilterCommon {
this.featureConfig = featureConfig;
}
+ private static List createPathConfigs() {
+ return MpConfig.toHelidonConfig(ConfigProvider.getConfig())
+ .get("server.features.security.endpoints")
+ .asNodeList()
+ .orElse(List.of())
+ .stream()
+ .map(PathConfig::create)
+ .toList();
+ }
+
+ /**
+ * Returns the real class of this object, skipping proxies.
+ *
+ * @param object The object.
+ * @return Its class.
+ */
+ static Class> getRealClass(Class> object) {
+ Class> result = object;
+ while (result.isSynthetic()) {
+ result = result.getSuperclass();
+ }
+ return result;
+ }
+
protected void doFilter(ContainerRequestContext request, SecurityContext securityContext) {
SecurityTracing tracing = SecurityTracing.get();
tracing.securityContext(securityContext);
@@ -79,7 +110,7 @@ protected void doFilter(ContainerRequestContext request, SecurityContext securit
filterContext);
}
- if (filterContext.isShouldFinish()) {
+ if (filterContext.shouldFinish()) {
if (logger().isLoggable(Level.TRACE)) {
logger().log(Level.TRACE, "Endpoint %s not found, no security", request.getUriInfo().getRequestUri());
}
@@ -96,17 +127,17 @@ protected void doFilter(ContainerRequestContext request, SecurityContext securit
} else {
origRequest = requestUri.getPath() + "?" + query;
}
- Map> allHeaders = new HashMap<>(filterContext.getHeaders());
+ Map> allHeaders = new HashMap<>(filterContext.headers());
allHeaders.put(Security.HEADER_ORIG_URI, List.of(origRequest));
SecurityEnvironment.Builder envBuilder = SecurityEnvironment.builder(security.serverTime())
.transport(requestUri.getScheme())
- .path(filterContext.getResourcePath())
- .targetUri(filterContext.getTargetUri())
- .method(filterContext.getMethod())
- .queryParams(filterContext.getQueryParams())
+ .path(filterContext.resourcePath())
+ .targetUri(filterContext.targetUri())
+ .method(filterContext.method())
+ .queryParams(filterContext.queryParams())
.headers(allHeaders)
- .addAttribute("resourceType", filterContext.getResourceName());
+ .addAttribute("resourceType", filterContext.resourceName());
// The following two lines are not possible in JAX-RS or Jersey - we would have to touch
// underlying web server's request...
@@ -120,9 +151,14 @@ protected void doFilter(ContainerRequestContext request, SecurityContext securit
}
SecurityEnvironment env = envBuilder.build();
+ Map configMap = new HashMap<>();
+ findMethodConfig(UriPath.create(requestUri.getPath()))
+ .asNode()
+ .ifPresent(conf -> conf.asNodeList().get().forEach(node -> configMap.put(node.name(), node)));
EndpointConfig ec = EndpointConfig.builder()
- .securityLevels(filterContext.getMethodSecurity().getSecurityLevels())
+ .securityLevels(filterContext.methodSecurity().securityLevels())
+ .configMap(configMap)
.build();
try {
@@ -132,12 +168,12 @@ protected void doFilter(ContainerRequestContext request, SecurityContext securit
request.setProperty(PROP_FILTER_CONTEXT, filterContext);
//context is needed even if authn/authz fails - for auditing
request.setSecurityContext(new JerseySecurityContext(securityContext,
- filterContext.getMethodSecurity(),
- "https".equals(filterContext.getTargetUri().getScheme())));
+ filterContext.methodSecurity(),
+ "https".equals(filterContext.targetUri().getScheme())));
processSecurity(request, filterContext, tracing, securityContext);
} finally {
- if (filterContext.isTraceSuccess()) {
+ if (filterContext.traceSuccess()) {
tracing.logProceed();
tracing.finish();
} else {
@@ -147,13 +183,22 @@ protected void doFilter(ContainerRequestContext request, SecurityContext securit
}
}
+ Config findMethodConfig(UriPath path) {
+ return PATH_CONFIGS.get()
+ .stream()
+ .filter(pathConfig -> pathConfig.pathMatcher.prefixMatch(path).accepted())
+ .findFirst()
+ .map(PathConfig::config)
+ .orElseGet(Config::empty);
+ }
+
protected void authenticate(SecurityFilterContext context, SecurityContext securityContext, AtnTracing atnTracing) {
try {
- SecurityDefinition methodSecurity = context.getMethodSecurity();
+ SecurityDefinition methodSecurity = context.methodSecurity();
if (methodSecurity.requiresAuthentication()) {
if (logger().isLoggable(Level.TRACE)) {
- logger().log(Level.TRACE, "Endpoint {0} requires authentication", context.getTargetUri());
+ logger().log(Level.TRACE, "Endpoint {0} requires authentication", context.targetUri());
}
//authenticate request
SecurityClientBuilder clientBuilder = securityContext
@@ -161,15 +206,15 @@ protected void authenticate(SecurityFilterContext context, SecurityContext secur
.optional(methodSecurity.authenticationOptional())
.tracingSpan(atnTracing.findParent().orElse(null));
- clientBuilder.explicitProvider(methodSecurity.getAuthenticator());
+ clientBuilder.explicitProvider(methodSecurity.authenticator());
processAuthentication(context, clientBuilder, methodSecurity, atnTracing);
} else {
if (logger().isLoggable(Level.TRACE)) {
- logger().log(Level.TRACE, "Endpoint {0} does not require authentication", context.getTargetUri());
+ logger().log(Level.TRACE, "Endpoint {0} does not require authentication", context.targetUri());
}
}
} finally {
- if (context.isTraceSuccess()) {
+ if (context.traceSuccess()) {
securityContext.user()
.ifPresent(atnTracing::logUser);
@@ -178,9 +223,9 @@ protected void authenticate(SecurityFilterContext context, SecurityContext secur
atnTracing.finish();
} else {
- Throwable ctxThrowable = context.getTraceThrowable();
+ Throwable ctxThrowable = context.traceThrowable();
if (null == ctxThrowable) {
- atnTracing.error(context.getTraceDescription());
+ atnTracing.error(context.traceDescription());
} else {
atnTracing.error(ctxThrowable);
}
@@ -209,17 +254,17 @@ protected void processAuthentication(SecurityFilterContext context,
if (methodSecurity.authenticationOptional()) {
logger().log(Level.TRACE, "Authentication failed, but was optional, so assuming anonymous");
} else {
- context.setTraceSuccess(false);
- context.setTraceDescription(response.description().orElse(responseStatus.toString()));
- context.setTraceThrowable(response.throwable().orElse(null));
- context.setShouldFinish(true);
+ context.traceSuccess(false);
+ context.traceDescription(response.description().orElse(responseStatus.toString()));
+ context.traceThrowable(response.throwable().orElse(null));
+ context.shouldFinish(true);
int status = response.statusCode().orElse(Response.Status.UNAUTHORIZED.getStatusCode());
abortRequest(context, response, status, Map.of());
}
}
case SUCCESS_FINISH -> {
- context.setShouldFinish(true);
+ context.shouldFinish(true);
int status = response.statusCode().orElse(Response.Status.OK.getStatusCode());
abortRequest(context, response, status, Map.of());
}
@@ -227,9 +272,9 @@ protected void processAuthentication(SecurityFilterContext context,
if (methodSecurity.authenticationOptional()) {
logger().log(Level.TRACE, "Authentication failed, but was optional, so assuming anonymous");
} else {
- context.setTraceSuccess(false);
- context.setTraceDescription(response.description().orElse(responseStatus.toString()));
- context.setShouldFinish(true);
+ context.traceSuccess(false);
+ context.traceDescription(response.description().orElse(responseStatus.toString()));
+ context.shouldFinish(true);
abortRequest(context,
response,
Response.Status.UNAUTHORIZED.getStatusCode(),
@@ -240,23 +285,23 @@ protected void processAuthentication(SecurityFilterContext context,
if (methodSecurity.authenticationOptional() && !methodSecurity.failOnFailureIfOptional()) {
logger().log(Level.TRACE, "Authentication failed, but was optional, so assuming anonymous");
} else {
- context.setTraceDescription(response.description().orElse(responseStatus.toString()));
- context.setTraceThrowable(response.throwable().orElse(null));
- context.setTraceSuccess(false);
+ context.traceDescription(response.description().orElse(responseStatus.toString()));
+ context.traceThrowable(response.throwable().orElse(null));
+ context.traceSuccess(false);
abortRequest(context,
response,
Response.Status.UNAUTHORIZED.getStatusCode(),
Map.of());
- context.setShouldFinish(true);
+ context.shouldFinish(true);
}
}
//noinspection DuplicatedCode
default -> {
- context.setTraceSuccess(false);
- context.setTraceDescription(response.description().orElse("UNKNOWN_RESPONSE: " + responseStatus));
- context.setShouldFinish(true);
+ context.traceSuccess(false);
+ context.traceDescription(response.description().orElse("UNKNOWN_RESPONSE: " + responseStatus));
+ context.shouldFinish(true);
SecurityException throwable = new SecurityException("Invalid SecurityStatus returned: " + responseStatus);
- context.setTraceThrowable(throwable);
+ context.traceThrowable(throwable);
throw throwable;
}
}
@@ -267,41 +312,41 @@ protected void processAuthentication(SecurityFilterContext context,
protected void authorize(SecurityFilterContext context,
SecurityContext securityContext,
AtzTracing atzTracing) {
- if (context.getMethodSecurity().isAtzExplicit()) {
+ if (context.methodSecurity().atzExplicit()) {
// authorization is explicitly done by user, we MUST skip it here
if (logger().isLoggable(Level.TRACE)) {
- logger().log(Level.TRACE, "Endpoint {0} uses explicit authorization, skipping", context.getTargetUri());
+ logger().log(Level.TRACE, "Endpoint {0} uses explicit authorization, skipping", context.targetUri());
}
- context.setExplicitAtz(true);
+ context.explicitAtz(true);
return;
}
try {
//now authorize (also authorize anonymous requests, as we may have a path-based authorization that allows public
// access
- if (context.getMethodSecurity().requiresAuthorization()) {
+ if (context.methodSecurity().requiresAuthorization()) {
if (logger().isLoggable(Level.TRACE)) {
- logger().log(Level.TRACE, "Endpoint {0} requires authorization", context.getTargetUri());
+ logger().log(Level.TRACE, "Endpoint {0} requires authorization", context.targetUri());
}
SecurityClientBuilder clientBuilder = securityContext.atzClientBuilder()
.tracingSpan(atzTracing.findParent().orElse(null))
- .explicitProvider(context.getMethodSecurity().getAuthorizer());
+ .explicitProvider(context.methodSecurity().authorizer());
processAuthorization(context, clientBuilder);
} else {
if (logger().isLoggable(Level.TRACE)) {
logger().log(Level.TRACE, "Endpoint {0} does not require authorization. Method security: {1}",
- context.getTargetUri(),
- context.getMethodSecurity());
+ context.targetUri(),
+ context.methodSecurity());
}
}
} finally {
- if (context.isTraceSuccess()) {
+ if (context.traceSuccess()) {
atzTracing.finish();
} else {
- Throwable throwable = context.getTraceThrowable();
+ Throwable throwable = context.traceThrowable();
if (null == throwable) {
- atzTracing.error(context.getTraceDescription());
+ atzTracing.error(context.traceDescription());
} else {
atzTracing.error(throwable);
}
@@ -321,32 +366,32 @@ protected void processAuthorization(SecurityFilterContext context,
//everything is fine, we can continue with processing
}
case FAILURE_FINISH -> {
- context.setTraceSuccess(false);
- context.setTraceDescription(response.description().orElse(responseStatus.toString()));
- context.setTraceThrowable(response.throwable().orElse(null));
- context.setShouldFinish(true);
+ context.traceSuccess(false);
+ context.traceDescription(response.description().orElse(responseStatus.toString()));
+ context.traceThrowable(response.throwable().orElse(null));
+ context.shouldFinish(true);
int status = response.statusCode().orElse(Response.Status.FORBIDDEN.getStatusCode());
abortRequest(context, response, status, Map.of());
}
case SUCCESS_FINISH -> {
- context.setShouldFinish(true);
+ context.shouldFinish(true);
int status = response.statusCode().orElse(Response.Status.OK.getStatusCode());
abortRequest(context, response, status, Map.of());
}
case FAILURE -> {
- context.setTraceSuccess(false);
- context.setTraceDescription(response.description().orElse(responseStatus.toString()));
- context.setTraceThrowable(response.throwable().orElse(null));
- context.setShouldFinish(true);
+ context.traceSuccess(false);
+ context.traceDescription(response.description().orElse(responseStatus.toString()));
+ context.traceThrowable(response.throwable().orElse(null));
+ context.shouldFinish(true);
abortRequest(context,
response,
response.statusCode().orElse(Response.Status.FORBIDDEN.getStatusCode()),
Map.of());
}
case ABSTAIN -> {
- context.setTraceSuccess(false);
- context.setTraceDescription(response.description().orElse(responseStatus.toString()));
- context.setShouldFinish(true);
+ context.traceSuccess(false);
+ context.traceDescription(response.description().orElse(responseStatus.toString()));
+ context.shouldFinish(true);
abortRequest(context,
response,
response.statusCode().orElse(Response.Status.FORBIDDEN.getStatusCode()),
@@ -354,11 +399,11 @@ protected void processAuthorization(SecurityFilterContext context,
}
//noinspection DuplicatedCode
default -> {
- context.setTraceSuccess(false);
- context.setTraceDescription(response.description().orElse("UNKNOWN_RESPONSE: " + responseStatus));
- context.setShouldFinish(true);
+ context.traceSuccess(false);
+ context.traceDescription(response.description().orElse("UNKNOWN_RESPONSE: " + responseStatus));
+ context.shouldFinish(true);
SecurityException throwable = new SecurityException("Invalid SecurityStatus returned: " + responseStatus);
- context.setTraceThrowable(throwable);
+ context.traceThrowable(throwable);
throw throwable;
}
}
@@ -389,7 +434,7 @@ protected void abortRequest(SecurityFilterContext context,
}
if (featureConfig.useAbortWith()) {
- context.getJerseyRequest().abortWith(responseBuilder.build());
+ context.jerseyRequest().abortWith(responseBuilder.build());
} else {
String description = response.description()
.orElse("Security did not allow this request to proceed.");
@@ -408,17 +453,17 @@ protected void updateHeaders(Map> responseHeaders, Response
protected SecurityFilterContext configureContext(SecurityFilterContext context,
ContainerRequestContext requestContext,
UriInfo uriInfo) {
- context.setMethod(requestContext.getMethod());
- context.setHeaders(requestContext.getHeaders());
- context.setTargetUri(requestContext.getUriInfo().getRequestUri());
- context.setResourcePath(context.getTargetUri().getPath());
- context.setQueryParams(UriQuery.create(uriInfo.getRequestUri()));
+ context.method(requestContext.getMethod());
+ context.headers(requestContext.getHeaders());
+ context.targetUri(requestContext.getUriInfo().getRequestUri());
+ context.resourcePath(context.targetUri().getPath());
+ context.queryParams(UriQuery.create(uriInfo.getRequestUri()));
- context.setJerseyRequest((ContainerRequest) requestContext);
+ context.jerseyRequest((ContainerRequest) requestContext);
// now extract headers
featureConfig().getQueryParamHandlers()
- .forEach(handler -> handler.extract(uriInfo, context.getHeaders()));
+ .forEach(handler -> handler.extract(uriInfo, context.headers()));
return context;
}
@@ -441,4 +486,14 @@ protected abstract void processSecurity(ContainerRequestContext request,
Config config(String child) {
return security.configFor(child);
}
+
+ private record PathConfig(PathMatcher pathMatcher, Config config) {
+
+ static PathConfig create(Config config) {
+ String path = config.get("path").asString().orElseThrow();
+ PathMatcher matcher = PathMatchers.create(path);
+ return new PathConfig(matcher, config.get("config"));
+ }
+
+ }
}
diff --git a/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityFilterContext.java b/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityFilterContext.java
index 20d082c48ba..946308d8390 100644
--- a/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityFilterContext.java
+++ b/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityFilterContext.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.
@@ -28,6 +28,9 @@
*/
public class SecurityFilterContext {
private String resourceName;
+ private String fullResourceName;
+ private String resourceMethod;
+ private String fullResourceMethod;
private String resourcePath;
private String method;
private Map> headers;
@@ -60,113 +63,137 @@ public String toString() {
+ '}';
}
- String getResourceName() {
+ String resourceName() {
return resourceName;
}
- void setResourceName(String resourceName) {
+ void resourceName(String resourceName) {
this.resourceName = resourceName;
}
- String getResourcePath() {
+ String fullResourceName() {
+ return fullResourceName;
+ }
+
+ void fullResourceName(String fullResourceName) {
+ this.fullResourceName = fullResourceName;
+ }
+
+ String resourceMethod() {
+ return resourceMethod;
+ }
+
+ void resourceMethod(String resourceMethod) {
+ this.resourceMethod = resourceMethod;
+ }
+
+ String fullResourceMethod() {
+ return fullResourceMethod;
+ }
+
+ void fullResourceMethod(String fullResourceMethod) {
+ this.fullResourceMethod = fullResourceMethod;
+ }
+
+ String resourcePath() {
return resourcePath;
}
- void setResourcePath(String resourcePath) {
+ void resourcePath(String resourcePath) {
this.resourcePath = resourcePath;
}
- String getMethod() {
+ String method() {
return method;
}
- void setMethod(String method) {
+ void method(String method) {
this.method = method;
}
- Map> getHeaders() {
+ Map> headers() {
return headers;
}
- void setHeaders(Map> headers) {
+ void headers(Map> headers) {
this.headers = headers;
}
- URI getTargetUri() {
+ URI targetUri() {
return targetUri;
}
- void setTargetUri(URI targetUri) {
+ void targetUri(URI targetUri) {
this.targetUri = targetUri;
}
- ContainerRequest getJerseyRequest() {
+ ContainerRequest jerseyRequest() {
return jerseyRequest;
}
- void setJerseyRequest(ContainerRequest jerseyRequest) {
+ void jerseyRequest(ContainerRequest jerseyRequest) {
this.jerseyRequest = jerseyRequest;
}
- boolean isShouldFinish() {
+ boolean shouldFinish() {
return shouldFinish;
}
- void setShouldFinish(boolean shouldFinish) {
+ void shouldFinish(boolean shouldFinish) {
this.shouldFinish = shouldFinish;
}
- SecurityDefinition getMethodSecurity() {
+ SecurityDefinition methodSecurity() {
return methodSecurity;
}
- void setMethodSecurity(SecurityDefinition methodSecurity) {
+ void methodSecurity(SecurityDefinition methodSecurity) {
this.methodSecurity = methodSecurity;
}
- boolean isExplicitAtz() {
+ boolean explicitAtz() {
return explicitAtz;
}
- void setExplicitAtz(boolean explicitAtz) {
+ void explicitAtz(boolean explicitAtz) {
this.explicitAtz = explicitAtz;
}
- boolean isTraceSuccess() {
+ boolean traceSuccess() {
return traceSuccess;
}
- void setTraceSuccess(boolean traceSuccess) {
+ void traceSuccess(boolean traceSuccess) {
this.traceSuccess = traceSuccess;
}
- String getTraceDescription() {
+ String traceDescription() {
return traceDescription;
}
- void setTraceDescription(String traceDescription) {
+ void traceDescription(String traceDescription) {
this.traceDescription = traceDescription;
}
- Throwable getTraceThrowable() {
+ Throwable traceThrowable() {
return traceThrowable;
}
- void setTraceThrowable(Throwable traceThrowable) {
+ void traceThrowable(Throwable traceThrowable) {
this.traceThrowable = traceThrowable;
}
- UriQuery getQueryParams() {
+ UriQuery queryParams() {
return queryParams;
}
- void setQueryParams(UriQuery queryParams) {
+ void queryParams(UriQuery queryParams) {
this.queryParams = queryParams;
}
void clearTrace() {
- setTraceSuccess(true);
- setTraceDescription(null);
- setTraceThrowable(null);
+ traceSuccess(true);
+ traceDescription(null);
+ traceThrowable(null);
}
}
diff --git a/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityPreMatchingFilter.java b/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityPreMatchingFilter.java
index 45be298e0cc..65a3faef81a 100644
--- a/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityPreMatchingFilter.java
+++ b/microprofile/security/src/main/java/io/helidon/microprofile/security/SecurityPreMatchingFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 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.
@@ -97,10 +97,10 @@ protected void processSecurity(ContainerRequestContext request,
// when I reach this point, I am sure we should at least authenticate in prematching filter
authenticate(filterContext, securityContext, tracing.atnTracing());
- LOGGER.log(Level.TRACE, () -> "Filter after authentication. Should finish: " + filterContext.isShouldFinish());
+ LOGGER.log(Level.TRACE, () -> "Filter after authentication. Should finish: " + filterContext.shouldFinish());
// authentication failed
- if (filterContext.isShouldFinish()) {
+ if (filterContext.shouldFinish()) {
return;
}
@@ -122,9 +122,9 @@ protected SecurityFilterContext initRequestFiltering(ContainerRequestContext req
SecurityDefinition methodDef = new SecurityDefinition(false, false);
methodDef.requiresAuthentication(true);
- methodDef.setRequiresAuthorization(featureConfig().shouldUsePrematchingAuthorization());
- context.setMethodSecurity(methodDef);
- context.setResourceName("jax-rs");
+ methodDef.requiresAuthorization(featureConfig().shouldUsePrematchingAuthorization());
+ context.methodSecurity(methodDef);
+ context.resourceName("jax-rs");
return configureContext(context, requestContext, uriInfo);
}
diff --git a/microprofile/security/src/main/java/module-info.java b/microprofile/security/src/main/java/module-info.java
index ebf03e7c62c..aeceddd027c 100644
--- a/microprofile/security/src/main/java/module-info.java
+++ b/microprofile/security/src/main/java/module-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
@SuppressWarnings({ "requires-automatic", "requires-transitive-automatic" })
module io.helidon.microprofile.security {
+ requires io.helidon.config.mp;
requires io.helidon.jersey.common;
requires io.helidon.microprofile.cdi;
requires io.helidon.microprofile.server;
diff --git a/microprofile/security/src/test/java/io/helidon/microprofile/security/OptionalSecurityTest.java b/microprofile/security/src/test/java/io/helidon/microprofile/security/OptionalSecurityTest.java
index 4bb0afe14e1..5e55a002bbc 100644
--- a/microprofile/security/src/test/java/io/helidon/microprofile/security/OptionalSecurityTest.java
+++ b/microprofile/security/src/test/java/io/helidon/microprofile/security/OptionalSecurityTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 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.
@@ -100,7 +100,7 @@ void testOptional() {
*/
securityFilter.processAuthentication(filterContext, clientBuilder, methodSecurity, tracing.atnTracing());
- assertThat(filterContext.isShouldFinish(), is(false));
+ assertThat(filterContext.shouldFinish(), is(false));
assertThat(securityContext.user(), is(Optional.empty()));
}
@@ -108,7 +108,7 @@ void testOptional() {
void testNotOptional() {
SecurityContext securityContext = security.createContext("context_id");
SecurityFilterContext filterContext = new SecurityFilterContext();
- filterContext.setJerseyRequest(mock(ContainerRequest.class));
+ filterContext.jerseyRequest(mock(ContainerRequest.class));
SecurityDefinition methodSecurity = mock(SecurityDefinition.class);
when(methodSecurity.authenticationOptional()).thenReturn(false);
@@ -123,7 +123,7 @@ void testNotOptional() {
*/
securityFilter.processAuthentication(filterContext, clientBuilder, methodSecurity, tracing.atnTracing());
- assertThat(filterContext.isShouldFinish(), is(true));
+ assertThat(filterContext.shouldFinish(), is(true));
assertThat(securityContext.user(), is(Optional.empty()));
}
diff --git a/microprofile/security/src/test/java/io/helidon/microprofile/security/SecurityFilterTest.java b/microprofile/security/src/test/java/io/helidon/microprofile/security/SecurityFilterTest.java
index 4176a55a166..1c66526c140 100644
--- a/microprofile/security/src/test/java/io/helidon/microprofile/security/SecurityFilterTest.java
+++ b/microprofile/security/src/test/java/io/helidon/microprofile/security/SecurityFilterTest.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.
@@ -66,7 +66,7 @@ void testAtnAbortWith() {
ContainerRequest request = mock(ContainerRequest.class);
SecurityFilterContext filterContext = new SecurityFilterContext();
- filterContext.setJerseyRequest(request);
+ filterContext.jerseyRequest(request);
SecurityDefinition methodSecurity = mock(SecurityDefinition.class);
@@ -74,7 +74,7 @@ void testAtnAbortWith() {
when(clientBuilder.submit()).thenReturn(AuthenticationResponse.failed("Unit-test"));
sf.processAuthentication(filterContext, clientBuilder, methodSecurity, tracing.atnTracing());
- assertThat(filterContext.isShouldFinish(), is(true));
+ assertThat(filterContext.shouldFinish(), is(true));
verify(request).abortWith(argThat(response -> response.getStatus() == 401));
}
@@ -94,7 +94,7 @@ void testAtnThrowException() {
ContainerRequest request = mock(ContainerRequest.class);
SecurityFilterContext filterContext = new SecurityFilterContext();
- filterContext.setJerseyRequest(request);
+ filterContext.jerseyRequest(request);
SecurityDefinition methodSecurity = mock(SecurityDefinition.class);
@@ -125,7 +125,7 @@ void testAtzAbortWith() {
ContainerRequest request = mock(ContainerRequest.class);
SecurityFilterContext filterContext = new SecurityFilterContext();
- filterContext.setJerseyRequest(request);
+ filterContext.jerseyRequest(request);
SecurityClientBuilder clientBuilder = mock(SecurityClientBuilder.class);
when(clientBuilder.submit()).thenReturn(AuthorizationResponse.builder()
@@ -134,7 +134,7 @@ void testAtzAbortWith() {
.build());
sf.processAuthorization(filterContext, clientBuilder);
- assertThat(filterContext.isShouldFinish(), is(true));
+ assertThat(filterContext.shouldFinish(), is(true));
verify(request).abortWith(argThat(response -> response.getStatus() == 403));
}
@@ -154,7 +154,7 @@ void testAtzThrowException() {
ContainerRequest request = mock(ContainerRequest.class);
SecurityFilterContext filterContext = new SecurityFilterContext();
- filterContext.setJerseyRequest(request);
+ filterContext.jerseyRequest(request);
SecurityClientBuilder clientBuilder = mock(SecurityClientBuilder.class);
when(clientBuilder.submit()).thenReturn(AuthorizationResponse.builder()
diff --git a/microprofile/server/pom.xml b/microprofile/server/pom.xml
index 43bc7a6e93c..1238517002f 100644
--- a/microprofile/server/pom.xml
+++ b/microprofile/server/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.serverhelidon-microprofile-server
diff --git a/microprofile/service-common/pom.xml b/microprofile/service-common/pom.xml
index f34b650cb5d..baffd12cf4f 100644
--- a/microprofile/service-common/pom.xml
+++ b/microprofile/service-common/pom.xml
@@ -20,7 +20,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/microprofile/telemetry/pom.xml b/microprofile/telemetry/pom.xml
index fa3e4bc53ce..cb75c632365 100644
--- a/microprofile/telemetry/pom.xml
+++ b/microprofile/telemetry/pom.xml
@@ -22,7 +22,7 @@
helidon-microprofile-projectio.helidon.microprofile
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.telemetryhelidon-microprofile-telemetry
diff --git a/microprofile/testing/junit5/pom.xml b/microprofile/testing/junit5/pom.xml
index 788f1f11861..40fe01c0dad 100644
--- a/microprofile/testing/junit5/pom.xml
+++ b/microprofile/testing/junit5/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.testinghelidon-microprofile-testing-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-testing-junit5
diff --git a/microprofile/testing/mocking/pom.xml b/microprofile/testing/mocking/pom.xml
index baa919f9fde..7b8cbc3b001 100644
--- a/microprofile/testing/mocking/pom.xml
+++ b/microprofile/testing/mocking/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.testinghelidon-microprofile-testing-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-testing-mocking
diff --git a/microprofile/testing/pom.xml b/microprofile/testing/pom.xml
index 5e2b83b6683..f4fbdb87a39 100644
--- a/microprofile/testing/pom.xml
+++ b/microprofile/testing/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomio.helidon.microprofile.testing
diff --git a/microprofile/testing/testng/pom.xml b/microprofile/testing/testng/pom.xml
index 84a17ced784..9f781af4fc2 100644
--- a/microprofile/testing/testng/pom.xml
+++ b/microprofile/testing/testng/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.testinghelidon-microprofile-testing-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-testing-testng
diff --git a/microprofile/tests/arquillian/pom.xml b/microprofile/tests/arquillian/pom.xml
index 979d3db768f..44a5ec8a173 100644
--- a/microprofile/tests/arquillian/pom.xml
+++ b/microprofile/tests/arquillian/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.testshelidon-microprofile-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-arquillianHelidon Microprofile Arquillian Integration
diff --git a/microprofile/tests/config/pom.xml b/microprofile/tests/config/pom.xml
index 78b3b3e1e03..8ffcee52216 100644
--- a/microprofile/tests/config/pom.xml
+++ b/microprofile/tests/config/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.testshelidon-microprofile-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-tests-configHelidon Microprofile Config Tests
diff --git a/microprofile/tests/pom.xml b/microprofile/tests/pom.xml
index c409b59e0f7..d71ae81b3cc 100644
--- a/microprofile/tests/pom.xml
+++ b/microprofile/tests/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomio.helidon.microprofile.tests
diff --git a/microprofile/tests/server/pom.xml b/microprofile/tests/server/pom.xml
index 82ef7e5dd68..789b8d9f6bf 100644
--- a/microprofile/tests/server/pom.xml
+++ b/microprofile/tests/server/pom.xml
@@ -23,7 +23,7 @@
io.helidon.microprofile.testshelidon-microprofile-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0helidon-microprofile-tests-server
@@ -65,5 +65,10 @@
helidon-logging-jultest
+
+ io.helidon.webclient
+ helidon-webclient-http1
+ test
+
diff --git a/microprofile/tests/server/src/test/java/io/helidon/microprofile/tests/server/BadHostHeaderTest.java b/microprofile/tests/server/src/test/java/io/helidon/microprofile/tests/server/BadHostHeaderTest.java
new file mode 100644
index 00000000000..1efb71706b2
--- /dev/null
+++ b/microprofile/tests/server/src/test/java/io/helidon/microprofile/tests/server/BadHostHeaderTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.microprofile.tests.server;
+
+import io.helidon.http.Header;
+import io.helidon.http.HeaderValues;
+import io.helidon.http.Status;
+import io.helidon.microprofile.testing.junit5.AddBean;
+import io.helidon.microprofile.testing.junit5.HelidonTest;
+import io.helidon.webclient.api.WebClient;
+import io.helidon.webserver.http.ServerRequest;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.MediaType;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@HelidonTest
+@AddBean(BadHostHeaderTest.TestResource.class)
+public class BadHostHeaderTest {
+ private static final Header BAD_HOST_HEADER = HeaderValues.create("Host", "localhost:808a");
+
+ @Test
+ void testGetGoodHeader(WebTarget target) {
+ String getResponse = target.path("/get").request().get(String.class);
+ assertThat(getResponse, is("localhost"));
+ }
+
+ @Test
+ void testGetBadHeader(WebTarget target) {
+ WebClient webClient = WebClient.builder()
+ .baseUri(target.getUri())
+ .build();
+ var response = webClient.get("/get")
+ .header(BAD_HOST_HEADER)
+ .request(String.class);
+ assertThat(response.status(), is(Status.BAD_REQUEST_400));
+ assertThat(response.entity(), is("Invalid port of the host header: 808a"));
+ }
+
+ @Path("/")
+ public static class TestResource {
+ @Context ServerRequest request;
+
+ @GET
+ @Path("get")
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getIt() {
+ return request.requestedUri().host();
+ }
+ }
+}
diff --git a/microprofile/tests/tck/pom.xml b/microprofile/tests/tck/pom.xml
index bd69c003f64..a6b41d491e3 100644
--- a/microprofile/tests/tck/pom.xml
+++ b/microprofile/tests/tck/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.testshelidon-microprofile-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomio.helidon.microprofile.tests.tck
diff --git a/microprofile/tests/tck/tck-annotations/pom.xml b/microprofile/tests/tck/tck-annotations/pom.xml
index f901bdf77ac..7edfc54c98a 100644
--- a/microprofile/tests/tck/tck-annotations/pom.xml
+++ b/microprofile/tests/tck/tck-annotations/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTtck-annotationsHelidon Microprofile Tests TCK Annotations
diff --git a/microprofile/tests/tck/tck-cdi-lang-model/pom.xml b/microprofile/tests/tck/tck-cdi-lang-model/pom.xml
index 9a7b63c25c3..bc4b646bc57 100644
--- a/microprofile/tests/tck/tck-cdi-lang-model/pom.xml
+++ b/microprofile/tests/tck/tck-cdi-lang-model/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTtck-cdi-lang-modelHelidon Microprofile Tests TCK CDI Lang model
diff --git a/microprofile/tests/tck/tck-cdi/pom.xml b/microprofile/tests/tck/tck-cdi/pom.xml
index 8a206739591..c22b6cc2a9e 100644
--- a/microprofile/tests/tck/tck-cdi/pom.xml
+++ b/microprofile/tests/tck/tck-cdi/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTtck-cdiHelidon Microprofile Tests TCK CDI
diff --git a/microprofile/tests/tck/tck-config/pom.xml b/microprofile/tests/tck/tck-config/pom.xml
index 179c8486e43..6e0164a84e5 100644
--- a/microprofile/tests/tck/tck-config/pom.xml
+++ b/microprofile/tests/tck/tck-config/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTtck-configHelidon Microprofile Tests TCK Config
diff --git a/microprofile/tests/tck/tck-core-profile/pom.xml b/microprofile/tests/tck/tck-core-profile/pom.xml
index 6611a1c57e7..44092348e5a 100644
--- a/microprofile/tests/tck/tck-core-profile/pom.xml
+++ b/microprofile/tests/tck/tck-core-profile/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomhelidon-microprofile-tests-tck-core-profile
diff --git a/microprofile/tests/tck/tck-core-profile/tck-core-profile-test/pom.xml b/microprofile/tests/tck/tck-core-profile/tck-core-profile-test/pom.xml
index 1349f4a1daf..dd4fd624c91 100644
--- a/microprofile/tests/tck/tck-core-profile/tck-core-profile-test/pom.xml
+++ b/microprofile/tests/tck/tck-core-profile/tck-core-profile-test/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-core-profile
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-tests-tck-core-profile-testHelidon Microprofile Tests TCK Core Profile
diff --git a/microprofile/tests/tck/tck-fault-tolerance/pom.xml b/microprofile/tests/tck/tck-fault-tolerance/pom.xml
index 1fd068c1c6b..4d8eb43bbb1 100644
--- a/microprofile/tests/tck/tck-fault-tolerance/pom.xml
+++ b/microprofile/tests/tck/tck-fault-tolerance/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTtck-fault-toleranceHelidon Microprofile Tests TCK Fault Tolerance
diff --git a/microprofile/tests/tck/tck-graphql/pom.xml b/microprofile/tests/tck/tck-graphql/pom.xml
index 63ae713acf3..32e5d9981dc 100644
--- a/microprofile/tests/tck/tck-graphql/pom.xml
+++ b/microprofile/tests/tck/tck-graphql/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTtck-graphqlHelidon Microprofile Tests TCK GraphQL
diff --git a/microprofile/tests/tck/tck-health/pom.xml b/microprofile/tests/tck/tck-health/pom.xml
index 39820dc4189..51f3ca254a0 100644
--- a/microprofile/tests/tck/tck-health/pom.xml
+++ b/microprofile/tests/tck/tck-health/pom.xml
@@ -23,7 +23,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/microprofile/tests/tck/tck-inject/pom.xml b/microprofile/tests/tck/tck-inject/pom.xml
index d29aa59b95d..5b333997bf2 100644
--- a/microprofile/tests/tck/tck-inject/pom.xml
+++ b/microprofile/tests/tck/tck-inject/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomhelidon-microprofile-tests-tck-inject
diff --git a/microprofile/tests/tck/tck-inject/tck-inject-test/pom.xml b/microprofile/tests/tck/tck-inject/tck-inject-test/pom.xml
index 8a725bf9ac0..0defcc444a5 100644
--- a/microprofile/tests/tck/tck-inject/tck-inject-test/pom.xml
+++ b/microprofile/tests/tck/tck-inject/tck-inject-test/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-inject
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-tests-tck-inject-testHelidon Microprofile Tests TCK Inject
diff --git a/microprofile/tests/tck/tck-jsonb/pom.xml b/microprofile/tests/tck/tck-jsonb/pom.xml
index d21ac59993c..bb1f6865586 100644
--- a/microprofile/tests/tck/tck-jsonb/pom.xml
+++ b/microprofile/tests/tck/tck-jsonb/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomhelidon-microprofile-tests-tck-jsonb
diff --git a/microprofile/tests/tck/tck-jsonb/tck-jsonb-test/pom.xml b/microprofile/tests/tck/tck-jsonb/tck-jsonb-test/pom.xml
index 0386f730ecb..f2b76d3ad44 100644
--- a/microprofile/tests/tck/tck-jsonb/tck-jsonb-test/pom.xml
+++ b/microprofile/tests/tck/tck-jsonb/tck-jsonb-test/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-jsonb
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-tests-tck-jsonb-testHelidon Microprofile Tests TCK JSONB
diff --git a/microprofile/tests/tck/tck-jsonp/pom.xml b/microprofile/tests/tck/tck-jsonp/pom.xml
index 177e48f1171..93fa13b9845 100644
--- a/microprofile/tests/tck/tck-jsonp/pom.xml
+++ b/microprofile/tests/tck/tck-jsonp/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomhelidon-microprofile-tests-tck-jsonp
diff --git a/microprofile/tests/tck/tck-jsonp/tck-jsonp-pluggability-test/pom.xml b/microprofile/tests/tck/tck-jsonp/tck-jsonp-pluggability-test/pom.xml
index 9bb77957b48..97410e1b438 100644
--- a/microprofile/tests/tck/tck-jsonp/tck-jsonp-pluggability-test/pom.xml
+++ b/microprofile/tests/tck/tck-jsonp/tck-jsonp-pluggability-test/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-jsonp
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-tests-tck-jsonp-pluggability-testHelidon Microprofile Tests TCK JSONP
diff --git a/microprofile/tests/tck/tck-jsonp/tck-jsonp-test/pom.xml b/microprofile/tests/tck/tck-jsonp/tck-jsonp-test/pom.xml
index ff477d86d40..8fce844b494 100644
--- a/microprofile/tests/tck/tck-jsonp/tck-jsonp-test/pom.xml
+++ b/microprofile/tests/tck/tck-jsonp/tck-jsonp-test/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-jsonp
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-tests-tck-jsonp-testHelidon Microprofile Tests TCK JSONP
diff --git a/microprofile/tests/tck/tck-jwt-auth/pom.xml b/microprofile/tests/tck/tck-jwt-auth/pom.xml
index 2cc820921be..7fff4a8c0a2 100644
--- a/microprofile/tests/tck/tck-jwt-auth/pom.xml
+++ b/microprofile/tests/tck/tck-jwt-auth/pom.xml
@@ -21,7 +21,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTtck-jwt-authHelidon Microprofile Tests TCK JWT-Auth
diff --git a/microprofile/tests/tck/tck-lra/pom.xml b/microprofile/tests/tck/tck-lra/pom.xml
index 98dbf742b40..a21d64af366 100644
--- a/microprofile/tests/tck/tck-lra/pom.xml
+++ b/microprofile/tests/tck/tck-lra/pom.xml
@@ -21,7 +21,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTtck-lra
diff --git a/microprofile/tests/tck/tck-messaging/pom.xml b/microprofile/tests/tck/tck-messaging/pom.xml
index 409ba4cc64d..820cdc74673 100644
--- a/microprofile/tests/tck/tck-messaging/pom.xml
+++ b/microprofile/tests/tck/tck-messaging/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTtck-messagingHelidon Microprofile Tests TCK Messaging
diff --git a/microprofile/tests/tck/tck-metrics/pom.xml b/microprofile/tests/tck/tck-metrics/pom.xml
index e90782b5743..869504c18b2 100644
--- a/microprofile/tests/tck/tck-metrics/pom.xml
+++ b/microprofile/tests/tck/tck-metrics/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTtck-metricsHelidon Microprofile Tests TCK Metrics
diff --git a/microprofile/tests/tck/tck-openapi/pom.xml b/microprofile/tests/tck/tck-openapi/pom.xml
index 97496350777..ce5a4d6e1b4 100644
--- a/microprofile/tests/tck/tck-openapi/pom.xml
+++ b/microprofile/tests/tck/tck-openapi/pom.xml
@@ -23,7 +23,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/microprofile/tests/tck/tck-opentracing/pom.xml b/microprofile/tests/tck/tck-opentracing/pom.xml
index 2e12cd0415a..649a05bf4b0 100644
--- a/microprofile/tests/tck/tck-opentracing/pom.xml
+++ b/microprofile/tests/tck/tck-opentracing/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTtck-opentracingHelidon Microprofile Tests TCK Opentracing
diff --git a/microprofile/tests/tck/tck-reactive-operators/pom.xml b/microprofile/tests/tck/tck-reactive-operators/pom.xml
index 79a003ca564..e843cf99a5c 100644
--- a/microprofile/tests/tck/tck-reactive-operators/pom.xml
+++ b/microprofile/tests/tck/tck-reactive-operators/pom.xml
@@ -23,7 +23,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTtck-reactive-operatorsHelidon Microprofile Tests TCK Reactive Operators
diff --git a/microprofile/tests/tck/tck-rest-client/pom.xml b/microprofile/tests/tck/tck-rest-client/pom.xml
index 75b5a0a9d75..a95eace7734 100644
--- a/microprofile/tests/tck/tck-rest-client/pom.xml
+++ b/microprofile/tests/tck/tck-rest-client/pom.xml
@@ -21,7 +21,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/microprofile/tests/tck/tck-restful/pom.xml b/microprofile/tests/tck/tck-restful/pom.xml
index f3c3e2a6ea1..f45fa352ebe 100644
--- a/microprofile/tests/tck/tck-restful/pom.xml
+++ b/microprofile/tests/tck/tck-restful/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomtck-restful
diff --git a/microprofile/tests/tck/tck-restful/tck-restful-test/pom.xml b/microprofile/tests/tck/tck-restful/tck-restful-test/pom.xml
index c9ac94f793c..2128082717a 100644
--- a/microprofile/tests/tck/tck-restful/tck-restful-test/pom.xml
+++ b/microprofile/tests/tck/tck-restful/tck-restful-test/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.tests.tcktck-restful
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-tests-tck-restful-testHelidon Microprofile Tests TCK Restful
diff --git a/microprofile/tests/tck/tck-telemetry/pom.xml b/microprofile/tests/tck/tck-telemetry/pom.xml
index a41b2577b2a..a85d8c7e99e 100644
--- a/microprofile/tests/tck/tck-telemetry/pom.xml
+++ b/microprofile/tests/tck/tck-telemetry/pom.xml
@@ -21,7 +21,7 @@
io.helidon.microprofile.tests.tckhelidon-microprofile-tests-tck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/microprofile/tests/telemetry/pom.xml b/microprofile/tests/telemetry/pom.xml
index 03adc04cd7d..80d90e09843 100644
--- a/microprofile/tests/telemetry/pom.xml
+++ b/microprofile/tests/telemetry/pom.xml
@@ -21,7 +21,7 @@
io.helidon.microprofile.testshelidon-microprofile-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0helidon-microprofile-tests-telemetry
diff --git a/microprofile/tests/testing/junit5/pom.xml b/microprofile/tests/testing/junit5/pom.xml
index 6e1a3d1b6fd..3c48ca8a3e8 100644
--- a/microprofile/tests/testing/junit5/pom.xml
+++ b/microprofile/tests/testing/junit5/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofile.tests.testinghelidon-microprofile-tests-testing-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-tests-testing-junit5
diff --git a/microprofile/tests/testing/pom.xml b/microprofile/tests/testing/pom.xml
index 5077722b737..c177242051e 100644
--- a/microprofile/tests/testing/pom.xml
+++ b/microprofile/tests/testing/pom.xml
@@ -23,9 +23,9 @@
io.helidon.microprofile.testshelidon-microprofile-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0pomio.helidon.microprofile.tests.testing
diff --git a/microprofile/tests/testing/testng/pom.xml b/microprofile/tests/testing/testng/pom.xml
index 3a49dbefb8d..8b439c66b58 100644
--- a/microprofile/tests/testing/testng/pom.xml
+++ b/microprofile/tests/testing/testng/pom.xml
@@ -21,7 +21,7 @@
io.helidon.microprofile.tests.testinghelidon-microprofile-tests-testing-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-microprofile-tests-testing-testng
diff --git a/microprofile/tracing/pom.xml b/microprofile/tracing/pom.xml
index 4abedf4e277..947afe315a2 100644
--- a/microprofile/tracing/pom.xml
+++ b/microprofile/tracing/pom.xml
@@ -22,7 +22,7 @@
helidon-microprofile-projectio.helidon.microprofile
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.tracinghelidon-microprofile-tracing
diff --git a/microprofile/websocket/pom.xml b/microprofile/websocket/pom.xml
index a0972b0188f..ab00465935a 100644
--- a/microprofile/websocket/pom.xml
+++ b/microprofile/websocket/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.microprofile.websocket
@@ -76,6 +76,10 @@
jakarta.websocketjakarta.websocket-client-api
+
+ io.helidon.common.concurrency
+ helidon-common-concurrency-limits
+ io.helidon.common.featureshelidon-common-features-api
diff --git a/microprofile/websocket/src/main/java/io/helidon/microprofile/tyrus/TyrusConnection.java b/microprofile/websocket/src/main/java/io/helidon/microprofile/tyrus/TyrusConnection.java
index 581f8d0abeb..3172a3cd32c 100644
--- a/microprofile/websocket/src/main/java/io/helidon/microprofile/tyrus/TyrusConnection.java
+++ b/microprofile/websocket/src/main/java/io/helidon/microprofile/tyrus/TyrusConnection.java
@@ -25,8 +25,12 @@
import io.helidon.common.buffers.BufferData;
import io.helidon.common.buffers.DataReader;
+import io.helidon.common.concurrency.limits.FixedLimit;
+import io.helidon.common.concurrency.limits.Limit;
+import io.helidon.common.concurrency.limits.LimitException;
import io.helidon.common.socket.SocketContext;
import io.helidon.http.DateTime;
+import io.helidon.webserver.CloseConnectionException;
import io.helidon.webserver.ConnectionContext;
import io.helidon.webserver.spi.ServerConnection;
import io.helidon.websocket.WsCloseCodes;
@@ -67,33 +71,45 @@ class TyrusConnection implements ServerConnection, WsSession {
}
@Override
- public void handle(Semaphore requestSemaphore) {
+ public void handle(Limit limit) {
myThread = Thread.currentThread();
DataReader dataReader = ctx.dataReader();
- listener.onOpen(this);
- if (requestSemaphore.tryAcquire()) {
+
+ try {
+ limit.invoke(() -> listener.onOpen(this));
+ } catch (LimitException e) {
+ listener.onError(this, e);
+ throw new CloseConnectionException("Too many concurrent requests");
+ } catch (Exception e) {
+ listener.onError(this, e);
+ listener.onClose(this, WsCloseCodes.UNEXPECTED_CONDITION, e.getMessage());
+ return;
+ }
+
+ while (canRun) {
try {
- while (canRun) {
- try {
- readingNetwork = true;
- BufferData buffer = dataReader.readBuffer();
- readingNetwork = false;
- lastRequestTimestamp = DateTime.timestamp();
- listener.onMessage(this, buffer, true);
- lastRequestTimestamp = DateTime.timestamp();
- } catch (Exception e) {
- listener.onError(this, e);
- listener.onClose(this, WsCloseCodes.UNEXPECTED_CONDITION, e.getMessage());
- return;
- }
- }
- listener.onClose(this, WsCloseCodes.NORMAL_CLOSE, "Idle timeout");
- } finally {
- requestSemaphore.release();
+ readingNetwork = true;
+ BufferData buffer = dataReader.readBuffer();
+ readingNetwork = false;
+ lastRequestTimestamp = DateTime.timestamp();
+ limit.invoke(() -> listener.onMessage(this, buffer, true));
+ lastRequestTimestamp = DateTime.timestamp();
+ } catch (LimitException e) {
+ listener.onClose(this, WsCloseCodes.TRY_AGAIN_LATER, "Too Many Concurrent Requests");
+ return;
+ } catch (Exception e) {
+ listener.onError(this, e);
+ listener.onClose(this, WsCloseCodes.UNEXPECTED_CONDITION, e.getMessage());
+ return;
}
- } else {
- listener.onClose(this, WsCloseCodes.TRY_AGAIN_LATER, "Too Many Concurrent Requests");
}
+ listener.onClose(this, WsCloseCodes.NORMAL_CLOSE, "Idle timeout");
+ }
+
+ @SuppressWarnings("removal")
+ @Override
+ public void handle(Semaphore requestSemaphore) {
+ handle(FixedLimit.create(requestSemaphore));
}
@Override
diff --git a/microprofile/websocket/src/main/java/module-info.java b/microprofile/websocket/src/main/java/module-info.java
index 939671a2800..da725db8de5 100644
--- a/microprofile/websocket/src/main/java/module-info.java
+++ b/microprofile/websocket/src/main/java/module-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,6 +46,7 @@
requires static io.helidon.common.features.api;
requires transitive jakarta.websocket;
+ requires transitive io.helidon.common.concurrency.limits;
exports io.helidon.microprofile.tyrus;
diff --git a/microprofile/weld/pom.xml b/microprofile/weld/pom.xml
index 46835c5d20d..7c69182580f 100644
--- a/microprofile/weld/pom.xml
+++ b/microprofile/weld/pom.xml
@@ -22,7 +22,7 @@
io.helidon.microprofilehelidon-microprofile-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/microprofile/weld/weld-core-impl/pom.xml b/microprofile/weld/weld-core-impl/pom.xml
index 1184c011477..a4bdbf1e0bf 100644
--- a/microprofile/weld/weld-core-impl/pom.xml
+++ b/microprofile/weld/weld-core-impl/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.weldhelidon-microprofile-weld-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTweld-core-implHelidon Weld SE Impl
diff --git a/microprofile/weld/weld-se-core/pom.xml b/microprofile/weld/weld-se-core/pom.xml
index 0a20e272ae6..fd50544c174 100644
--- a/microprofile/weld/weld-se-core/pom.xml
+++ b/microprofile/weld/weld-se-core/pom.xml
@@ -24,7 +24,7 @@
io.helidon.microprofile.weldhelidon-microprofile-weld-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTweld-se-coreHelidon Weld SE Core
diff --git a/openapi/openapi/pom.xml b/openapi/openapi/pom.xml
index 1b016979bea..673ac2be23c 100644
--- a/openapi/openapi/pom.xml
+++ b/openapi/openapi/pom.xml
@@ -21,7 +21,7 @@
io.helidon.openapihelidon-openapi-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-openapi
diff --git a/openapi/pom.xml b/openapi/pom.xml
index 09b56ad512e..0c71bb86c19 100644
--- a/openapi/pom.xml
+++ b/openapi/pom.xml
@@ -22,7 +22,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomio.helidon.openapi
diff --git a/openapi/tests/gh-5792/pom.xml b/openapi/tests/gh-5792/pom.xml
index 10215aa64ee..380b7e9a2b3 100644
--- a/openapi/tests/gh-5792/pom.xml
+++ b/openapi/tests/gh-5792/pom.xml
@@ -24,7 +24,7 @@
io.helidon.openapi.testshelidon-openapi-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-openapi-tests-yaml-parsing
diff --git a/openapi/tests/pom.xml b/openapi/tests/pom.xml
index 869e0131cce..369f21afe3d 100644
--- a/openapi/tests/pom.xml
+++ b/openapi/tests/pom.xml
@@ -24,7 +24,7 @@
io.helidon.openapihelidon-openapi-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.openapi.tests
diff --git a/parent/pom.xml b/parent/pom.xml
index d41cbb96500..2430c5d6c16 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -22,7 +22,7 @@
4.0.0io.helidonhelidon-parent
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomHelidon Parent
@@ -168,7 +168,7 @@
io.helidon.build-toolshelidon-build-cache-maven-plugin
- 4.0.11
+ 4.0.14
@@ -199,7 +199,7 @@
- staging
+ ossrh-stagingossrh-staging
diff --git a/pom.xml b/pom.xml
index 2e175a2ba98..0e0503a8b07 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,7 +25,7 @@
io.helidonhelidon-dependencies
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT./dependencies/pom.xmlhelidon-project
@@ -65,7 +65,7 @@
1.7.0.Final9.710.12.5
- 2.11.0
+ 2.14.02.4.1410.0.31.4
@@ -117,7 +117,7 @@
3.1.03.1.22.3
- 4.0.11
+ 4.0.14${version.lib.hibernate}3.1.20.8.5
@@ -135,7 +135,7 @@
3.0.14.7.3.51.12.0
- 10.0.3
+ 10.0.43.1.01.12.3
diff --git a/scheduling/pom.xml b/scheduling/pom.xml
index 1461040a9df..e509dd38d81 100644
--- a/scheduling/pom.xml
+++ b/scheduling/pom.xml
@@ -22,7 +22,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.scheduling
diff --git a/security/abac/policy-el/pom.xml b/security/abac/policy-el/pom.xml
index ef4447f0583..4a8518c6f21 100644
--- a/security/abac/policy-el/pom.xml
+++ b/security/abac/policy-el/pom.xml
@@ -23,7 +23,7 @@
io.helidon.security.abachelidon-security-abac-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0helidon-security-abac-policy-el
diff --git a/security/abac/policy/pom.xml b/security/abac/policy/pom.xml
index 1bddf59134a..5c87114cea4 100644
--- a/security/abac/policy/pom.xml
+++ b/security/abac/policy/pom.xml
@@ -24,7 +24,7 @@
io.helidon.security.abachelidon-security-abac-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-abac-policyHelidon Security Validators Policy
diff --git a/security/abac/pom.xml b/security/abac/pom.xml
index 28c7b109615..75e4b7aa68f 100644
--- a/security/abac/pom.xml
+++ b/security/abac/pom.xml
@@ -23,7 +23,7 @@
io.helidon.securityhelidon-security-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpom
diff --git a/security/abac/role/pom.xml b/security/abac/role/pom.xml
index d4b02529247..180e646ab51 100644
--- a/security/abac/role/pom.xml
+++ b/security/abac/role/pom.xml
@@ -24,7 +24,7 @@
io.helidon.security.abachelidon-security-abac-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-abac-roleHelidon Security Validators Role
diff --git a/security/abac/scope/pom.xml b/security/abac/scope/pom.xml
index 03283e78cfe..168b28ba86e 100644
--- a/security/abac/scope/pom.xml
+++ b/security/abac/scope/pom.xml
@@ -24,7 +24,7 @@
io.helidon.security.abachelidon-security-abac-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-abac-scopeHelidon Security Validators Scope
diff --git a/security/abac/time/pom.xml b/security/abac/time/pom.xml
index 8d3a3a963f2..98d5eac7f0f 100644
--- a/security/abac/time/pom.xml
+++ b/security/abac/time/pom.xml
@@ -24,7 +24,7 @@
io.helidon.security.abachelidon-security-abac-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-abac-timeHelidon Security Validators Time
diff --git a/security/annotations/pom.xml b/security/annotations/pom.xml
index 9fd32380ae1..3ce07e4ce89 100644
--- a/security/annotations/pom.xml
+++ b/security/annotations/pom.xml
@@ -24,7 +24,7 @@
io.helidon.securityhelidon-security-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-annotationsHelidon Security Integration Annotations
diff --git a/security/integration/common/pom.xml b/security/integration/common/pom.xml
index dec522d9bf1..d2310a916ce 100644
--- a/security/integration/common/pom.xml
+++ b/security/integration/common/pom.xml
@@ -24,7 +24,7 @@
io.helidon.security.integrationhelidon-security-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-integration-commonHelidon Security Integration Common
diff --git a/security/integration/pom.xml b/security/integration/pom.xml
index a5fac28050d..c61cb420ad9 100644
--- a/security/integration/pom.xml
+++ b/security/integration/pom.xml
@@ -24,7 +24,7 @@
io.helidon.securityhelidon-security-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpom
diff --git a/security/jwt/pom.xml b/security/jwt/pom.xml
index 5d7eb057e29..6fcd22d8fbe 100644
--- a/security/jwt/pom.xml
+++ b/security/jwt/pom.xml
@@ -24,7 +24,7 @@
io.helidon.securityhelidon-security-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-jwtHelidon Security JWT
diff --git a/security/pom.xml b/security/pom.xml
index 43e3970c756..a1f44e917ef 100644
--- a/security/pom.xml
+++ b/security/pom.xml
@@ -24,7 +24,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomio.helidon.security
diff --git a/security/providers/abac/pom.xml b/security/providers/abac/pom.xml
index eb906e2e798..94e2fd64b8c 100644
--- a/security/providers/abac/pom.xml
+++ b/security/providers/abac/pom.xml
@@ -24,7 +24,7 @@
io.helidon.security.providershelidon-security-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-providers-abacHelidon Security Providers ABAC
diff --git a/security/providers/common/pom.xml b/security/providers/common/pom.xml
index 9a70ea9df1b..0423a51b68d 100644
--- a/security/providers/common/pom.xml
+++ b/security/providers/common/pom.xml
@@ -24,7 +24,7 @@
io.helidon.security.providershelidon-security-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-providers-commonHelidon Security Providers Common
diff --git a/security/providers/config-vault/pom.xml b/security/providers/config-vault/pom.xml
index 113153474e1..da667dcd5be 100644
--- a/security/providers/config-vault/pom.xml
+++ b/security/providers/config-vault/pom.xml
@@ -24,7 +24,7 @@
io.helidon.security.providershelidon-security-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-providers-config-vaultHelidon Security Providers Config Vault
diff --git a/security/providers/google-login/pom.xml b/security/providers/google-login/pom.xml
index ee0ba444c41..e4894609a80 100644
--- a/security/providers/google-login/pom.xml
+++ b/security/providers/google-login/pom.xml
@@ -24,7 +24,7 @@
io.helidon.security.providershelidon-security-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-providers-google-loginHelidon Security Providers Google Login
diff --git a/security/providers/header/pom.xml b/security/providers/header/pom.xml
index ff9fde5dd51..f7c20e8df99 100644
--- a/security/providers/header/pom.xml
+++ b/security/providers/header/pom.xml
@@ -24,7 +24,7 @@
io.helidon.security.providershelidon-security-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-providers-headerHelidon Security Providers Header authentication
diff --git a/security/providers/http-auth/pom.xml b/security/providers/http-auth/pom.xml
index 9b5a7643187..baa9f241fda 100644
--- a/security/providers/http-auth/pom.xml
+++ b/security/providers/http-auth/pom.xml
@@ -24,7 +24,7 @@
io.helidon.security.providershelidon-security-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-providers-http-authHelidon Security Providers HTTP Authentication
diff --git a/security/providers/http-sign/pom.xml b/security/providers/http-sign/pom.xml
index c03bf298b61..d46f65ff572 100644
--- a/security/providers/http-sign/pom.xml
+++ b/security/providers/http-sign/pom.xml
@@ -24,7 +24,7 @@
io.helidon.security.providershelidon-security-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-providers-http-signHelidon Security Providers HTTP Signature
diff --git a/security/providers/idcs-mapper/pom.xml b/security/providers/idcs-mapper/pom.xml
index 3e2041ccff6..dafdb692a8c 100644
--- a/security/providers/idcs-mapper/pom.xml
+++ b/security/providers/idcs-mapper/pom.xml
@@ -21,7 +21,7 @@
io.helidon.security.providershelidon-security-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/security/providers/jwt/pom.xml b/security/providers/jwt/pom.xml
index e711f38b58e..216677268b9 100644
--- a/security/providers/jwt/pom.xml
+++ b/security/providers/jwt/pom.xml
@@ -24,7 +24,7 @@
io.helidon.security.providershelidon-security-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-providers-jwtHelidon Security Providers JWT
diff --git a/security/providers/oidc-common/pom.xml b/security/providers/oidc-common/pom.xml
index 9bea3aa1634..568214d659a 100644
--- a/security/providers/oidc-common/pom.xml
+++ b/security/providers/oidc-common/pom.xml
@@ -21,7 +21,7 @@
io.helidon.security.providershelidon-security-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0helidon-security-providers-oidc-common
diff --git a/security/providers/oidc/pom.xml b/security/providers/oidc/pom.xml
index 2b3095a28c1..0750ec066e9 100644
--- a/security/providers/oidc/pom.xml
+++ b/security/providers/oidc/pom.xml
@@ -23,7 +23,7 @@
io.helidon.security.providershelidon-security-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0helidon-security-providers-oidc
diff --git a/security/providers/pom.xml b/security/providers/pom.xml
index 3aa2479302e..210caff1fc6 100644
--- a/security/providers/pom.xml
+++ b/security/providers/pom.xml
@@ -24,7 +24,7 @@
io.helidon.securityhelidon-security-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.security.providershelidon-security-providers-project
diff --git a/security/security/pom.xml b/security/security/pom.xml
index 1a2aeeb02e8..c62c640c2fa 100644
--- a/security/security/pom.xml
+++ b/security/security/pom.xml
@@ -24,7 +24,7 @@
io.helidon.securityhelidon-security-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-securityHelidon Security
diff --git a/security/util/pom.xml b/security/util/pom.xml
index ab205d55c09..bf808c1a581 100644
--- a/security/util/pom.xml
+++ b/security/util/pom.xml
@@ -24,7 +24,7 @@
io.helidon.securityhelidon-security-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-security-utilHelidon Security Utilities
diff --git a/service/codegen/pom.xml b/service/codegen/pom.xml
index 2ba57ed268f..4ce71e87d91 100644
--- a/service/codegen/pom.xml
+++ b/service/codegen/pom.xml
@@ -23,7 +23,7 @@
io.helidon.servicehelidon-service-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/service/metadata/pom.xml b/service/metadata/pom.xml
index 80c50c75729..785dd4b3dac 100644
--- a/service/metadata/pom.xml
+++ b/service/metadata/pom.xml
@@ -23,7 +23,7 @@
io.helidon.servicehelidon-service-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/service/pom.xml b/service/pom.xml
index 118ca67fdd6..db67a8faa3f 100644
--- a/service/pom.xml
+++ b/service/pom.xml
@@ -24,7 +24,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.service
diff --git a/service/registry/pom.xml b/service/registry/pom.xml
index 76b62d33456..500631ccdd8 100644
--- a/service/registry/pom.xml
+++ b/service/registry/pom.xml
@@ -23,7 +23,7 @@
io.helidon.servicehelidon-service-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/service/tests/codegen/pom.xml b/service/tests/codegen/pom.xml
index e7a7eae89f3..13a4c39c24b 100644
--- a/service/tests/codegen/pom.xml
+++ b/service/tests/codegen/pom.xml
@@ -23,7 +23,7 @@
io.helidon.service.testshelidon-service-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/service/tests/pom.xml b/service/tests/pom.xml
index 96051764aab..245ccea4b69 100644
--- a/service/tests/pom.xml
+++ b/service/tests/pom.xml
@@ -22,7 +22,7 @@
io.helidon.servicehelidon-service-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/service/tests/registry/pom.xml b/service/tests/registry/pom.xml
index 0a66188f428..219a7a56447 100644
--- a/service/tests/registry/pom.xml
+++ b/service/tests/registry/pom.xml
@@ -22,7 +22,7 @@
io.helidon.service.testshelidon-service-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/apps/bookstore/bookstore-mp/pom.xml b/tests/apps/bookstore/bookstore-mp/pom.xml
index f3e868178e0..49d11a6aa8e 100644
--- a/tests/apps/bookstore/bookstore-mp/pom.xml
+++ b/tests/apps/bookstore/bookstore-mp/pom.xml
@@ -22,7 +22,7 @@
io.helidon.applicationshelidon-mp
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../../../../applications/mp/pom.xmlio.helidon.tests.apps.bookstore.bookstore-mp
diff --git a/tests/apps/bookstore/bookstore-se/pom.xml b/tests/apps/bookstore/bookstore-se/pom.xml
index 24ae2da6f4c..5fb8e21f406 100644
--- a/tests/apps/bookstore/bookstore-se/pom.xml
+++ b/tests/apps/bookstore/bookstore-se/pom.xml
@@ -22,7 +22,7 @@
io.helidon.applicationshelidon-se
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../../../../applications/se/pom.xmlio.helidon.tests.apps.bookstore.bookstore-se
diff --git a/tests/apps/bookstore/common/pom.xml b/tests/apps/bookstore/common/pom.xml
index b25cd0f314c..8e117b33e77 100644
--- a/tests/apps/bookstore/common/pom.xml
+++ b/tests/apps/bookstore/common/pom.xml
@@ -23,7 +23,7 @@
io.helidon.applicationshelidon-se
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../../../../applications/se/pom.xmlio.helidon.tests.apps.bookstore.common
diff --git a/tests/apps/bookstore/pom.xml b/tests/apps/bookstore/pom.xml
index 994e284190a..ad39a1e6de2 100644
--- a/tests/apps/bookstore/pom.xml
+++ b/tests/apps/bookstore/pom.xml
@@ -24,7 +24,7 @@
io.helidon.tests.appshelidon-tests-apps-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests.apps.bookstore
diff --git a/tests/apps/pom.xml b/tests/apps/pom.xml
index 1a3091e1a40..5553b9b0cf9 100644
--- a/tests/apps/pom.xml
+++ b/tests/apps/pom.xml
@@ -23,7 +23,7 @@
io.helidon.testshelidon-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests.appshelidon-tests-apps-project
diff --git a/tests/benchmark/jmh/pom.xml b/tests/benchmark/jmh/pom.xml
index ad05c95f711..46184d66eae 100644
--- a/tests/benchmark/jmh/pom.xml
+++ b/tests/benchmark/jmh/pom.xml
@@ -21,7 +21,7 @@
io.helidon.tests.benchmarkhelidon-tests-benchmark-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-benchmark-jmh
diff --git a/tests/benchmark/pom.xml b/tests/benchmark/pom.xml
index 0120617e48d..f8bdc436a9a 100644
--- a/tests/benchmark/pom.xml
+++ b/tests/benchmark/pom.xml
@@ -21,7 +21,7 @@
io.helidon.testshelidon-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests.benchmark
diff --git a/tests/functional/bookstore/pom.xml b/tests/functional/bookstore/pom.xml
index 84c8e77108a..b19bf119cbd 100644
--- a/tests/functional/bookstore/pom.xml
+++ b/tests/functional/bookstore/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.functionalhelidon-tests-functional-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests.functional.bookstorehelidon-tests-functional-bookstore
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/functional/config-profiles/pom.xml b/tests/functional/config-profiles/pom.xml
index 5f93dadd05b..9b10b4dd141 100644
--- a/tests/functional/config-profiles/pom.xml
+++ b/tests/functional/config-profiles/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.functionalhelidon-tests-functional-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests.functional.configprofilehelidon-tests-functional-configprofile
diff --git a/tests/functional/context-propagation/pom.xml b/tests/functional/context-propagation/pom.xml
index 1f326134ed4..a3475b9d11a 100644
--- a/tests/functional/context-propagation/pom.xml
+++ b/tests/functional/context-propagation/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-functional-projectio.helidon.tests.functional
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-functional-context-propagation
diff --git a/tests/functional/jax-rs-multiple-apps/pom.xml b/tests/functional/jax-rs-multiple-apps/pom.xml
index 37acd4003e3..0a2572516f4 100644
--- a/tests/functional/jax-rs-multiple-apps/pom.xml
+++ b/tests/functional/jax-rs-multiple-apps/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-functional-projectio.helidon.tests.functional
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-functional-jax-rs-multiple-apps
diff --git a/tests/functional/jax-rs-subresource/pom.xml b/tests/functional/jax-rs-subresource/pom.xml
index 26fa0c56d9e..8f49f98a259 100644
--- a/tests/functional/jax-rs-subresource/pom.xml
+++ b/tests/functional/jax-rs-subresource/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-functional-projectio.helidon.tests.functional
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-functional-jax-rs-subresourceHelidon Tests Functional JAX-RS Subresources
diff --git a/tests/functional/mp-compression/pom.xml b/tests/functional/mp-compression/pom.xml
index 14553630d98..59c70828aee 100644
--- a/tests/functional/mp-compression/pom.xml
+++ b/tests/functional/mp-compression/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-functional-projectio.helidon.tests.functional
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-functional-mp-compressionHelidon Tests Functional HTTP compression
diff --git a/tests/functional/mp-synthetic-app/pom.xml b/tests/functional/mp-synthetic-app/pom.xml
index a9a3eb26218..f0035b0c575 100644
--- a/tests/functional/mp-synthetic-app/pom.xml
+++ b/tests/functional/mp-synthetic-app/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-functional-projectio.helidon.tests.functional
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-functional-mp-synthetic-appHelidon Tests Functional MP Synthetic Application
diff --git a/tests/functional/multiport/pom.xml b/tests/functional/multiport/pom.xml
index eda9c7315ae..59b357a4fb6 100644
--- a/tests/functional/multiport/pom.xml
+++ b/tests/functional/multiport/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-functional-projectio.helidon.tests.functional
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-functional-multiportHelidon Tests Functional Multiport with MP
diff --git a/tests/functional/param-converter-provider/pom.xml b/tests/functional/param-converter-provider/pom.xml
index 21244a55c17..6f388f74204 100644
--- a/tests/functional/param-converter-provider/pom.xml
+++ b/tests/functional/param-converter-provider/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-functional-projectio.helidon.tests.functional
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-param-converter-provider
diff --git a/tests/functional/pom.xml b/tests/functional/pom.xml
index 5ff5cf64bb0..5d6dbadd30a 100644
--- a/tests/functional/pom.xml
+++ b/tests/functional/pom.xml
@@ -24,7 +24,7 @@
io.helidon.testshelidon-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests.functional
diff --git a/tests/functional/request-scope-cdi/pom.xml b/tests/functional/request-scope-cdi/pom.xml
index ab3c328792d..c31b7381e55 100644
--- a/tests/functional/request-scope-cdi/pom.xml
+++ b/tests/functional/request-scope-cdi/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-functional-projectio.helidon.tests.functional
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-functional-request-scope-cdi
diff --git a/tests/functional/request-scope-injection/pom.xml b/tests/functional/request-scope-injection/pom.xml
index a05da71fbdf..e20cb65bbfb 100644
--- a/tests/functional/request-scope-injection/pom.xml
+++ b/tests/functional/request-scope-injection/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-functional-projectio.helidon.tests.functional
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-functional-request-scope-injection
diff --git a/tests/functional/request-scope/pom.xml b/tests/functional/request-scope/pom.xml
index 603d5d6ed88..f94d4e41545 100644
--- a/tests/functional/request-scope/pom.xml
+++ b/tests/functional/request-scope/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-functional-projectio.helidon.tests.functional
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-functional-request-scope
diff --git a/tests/integration/config/gh-2171-yml/pom.xml b/tests/integration/config/gh-2171-yml/pom.xml
index cc8cf601c64..4be7f49f2b0 100644
--- a/tests/integration/config/gh-2171-yml/pom.xml
+++ b/tests/integration/config/gh-2171-yml/pom.xml
@@ -20,7 +20,7 @@
io.helidon.tests.integrationhelidon-tests-integration-config
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/config/gh-2171/pom.xml b/tests/integration/config/gh-2171/pom.xml
index 2c0dc8129e0..03afe24ac31 100644
--- a/tests/integration/config/gh-2171/pom.xml
+++ b/tests/integration/config/gh-2171/pom.xml
@@ -20,7 +20,7 @@
io.helidon.tests.integrationhelidon-tests-integration-config
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/config/gh-4375/pom.xml b/tests/integration/config/gh-4375/pom.xml
index 87556183304..8dcbf67aeb2 100644
--- a/tests/integration/config/gh-4375/pom.xml
+++ b/tests/integration/config/gh-4375/pom.xml
@@ -24,7 +24,7 @@
io.helidon.tests.integrationhelidon-tests-integration-config
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-config-gh-4375
diff --git a/tests/integration/config/hocon-mp/pom.xml b/tests/integration/config/hocon-mp/pom.xml
index 93efd367759..ac103b534de 100644
--- a/tests/integration/config/hocon-mp/pom.xml
+++ b/tests/integration/config/hocon-mp/pom.xml
@@ -24,7 +24,7 @@
io.helidon.tests.integrationhelidon-tests-integration-config
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-config-hocon-mp
diff --git a/tests/integration/config/pom.xml b/tests/integration/config/pom.xml
index a5c3dd3fbb6..9513f6beea2 100644
--- a/tests/integration/config/pom.xml
+++ b/tests/integration/config/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpom
diff --git a/tests/integration/dbclient/common/pom.xml b/tests/integration/dbclient/common/pom.xml
index ae04c67ab2d..5eac060ef25 100644
--- a/tests/integration/dbclient/common/pom.xml
+++ b/tests/integration/dbclient/common/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integration.dbclienthelidon-tests-integration-dbclient-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-dbclient-commonHelidon Tests Integration DbClient Common
diff --git a/tests/integration/dbclient/h2/pom.xml b/tests/integration/dbclient/h2/pom.xml
index c5880b5c983..afbc7eb0791 100644
--- a/tests/integration/dbclient/h2/pom.xml
+++ b/tests/integration/dbclient/h2/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integration.dbclienthelidon-tests-integration-dbclient-parent
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../parent/pom.xmlhelidon-tests-integration-dbclient-h2
diff --git a/tests/integration/dbclient/mongodb/pom.xml b/tests/integration/dbclient/mongodb/pom.xml
index 676b1d57dc2..fc50bbe57cb 100644
--- a/tests/integration/dbclient/mongodb/pom.xml
+++ b/tests/integration/dbclient/mongodb/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integration.dbclienthelidon-tests-integration-dbclient-parent
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../parent/pom.xmlhelidon-tests-integration-dbclient-mongodb
diff --git a/tests/integration/dbclient/mysql/pom.xml b/tests/integration/dbclient/mysql/pom.xml
index de3440248ae..932e40ed820 100644
--- a/tests/integration/dbclient/mysql/pom.xml
+++ b/tests/integration/dbclient/mysql/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integration.dbclienthelidon-tests-integration-dbclient-parent
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../parent/pom.xmlhelidon-tests-integration-dbclient-mysql
diff --git a/tests/integration/dbclient/oracle/pom.xml b/tests/integration/dbclient/oracle/pom.xml
index 114e15d4097..776a3679afc 100644
--- a/tests/integration/dbclient/oracle/pom.xml
+++ b/tests/integration/dbclient/oracle/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integration.dbclienthelidon-tests-integration-dbclient-parent
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../parent/pom.xmlhelidon-tests-integration-dbclient-oracle
diff --git a/tests/integration/dbclient/parent/pom.xml b/tests/integration/dbclient/parent/pom.xml
index 2acb24b05ed..28fc904cba0 100644
--- a/tests/integration/dbclient/parent/pom.xml
+++ b/tests/integration/dbclient/parent/pom.xml
@@ -22,7 +22,7 @@
io.helidon.applicationshelidon-se
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../../../../applications/se/pom.xmlio.helidon.tests.integration.dbclient
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/tests/integration/dbclient/pgsql/pom.xml b/tests/integration/dbclient/pgsql/pom.xml
index 63f11281e70..d7f365ee9d3 100644
--- a/tests/integration/dbclient/pgsql/pom.xml
+++ b/tests/integration/dbclient/pgsql/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integration.dbclienthelidon-tests-integration-dbclient-parent
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../parent/pom.xmlhelidon-tests-integration-dbclient-pgsql
diff --git a/tests/integration/dbclient/pom.xml b/tests/integration/dbclient/pom.xml
index 3806838afeb..fb92f145a15 100644
--- a/tests/integration/dbclient/pom.xml
+++ b/tests/integration/dbclient/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests.integration.dbclienthelidon-tests-integration-dbclient-project
diff --git a/tests/integration/harness/pom.xml b/tests/integration/harness/pom.xml
index 109c34b5b7b..839c63fcc16 100644
--- a/tests/integration/harness/pom.xml
+++ b/tests/integration/harness/pom.xml
@@ -21,7 +21,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-harnessHelidon Tests Integration Harness
diff --git a/tests/integration/health/mp-disabled/pom.xml b/tests/integration/health/mp-disabled/pom.xml
index 1507c478c44..2879f71356f 100644
--- a/tests/integration/health/mp-disabled/pom.xml
+++ b/tests/integration/health/mp-disabled/pom.xml
@@ -22,7 +22,7 @@
io.helidon.applicationshelidon-mp
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../../../../applications/mp/pom.xmlio.helidon.tests.integration.health
diff --git a/tests/integration/health/pom.xml b/tests/integration/health/pom.xml
index 88af4cf1140..1a0f15c3093 100644
--- a/tests/integration/health/pom.xml
+++ b/tests/integration/health/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests.integration.healthhelidon-tests-integration-health-project
diff --git a/tests/integration/jep290/check_f_f_ok/pom.xml b/tests/integration/jep290/check_f_f_ok/pom.xml
index 643232b0cb6..7459bef9fbb 100644
--- a/tests/integration/jep290/check_f_f_ok/pom.xml
+++ b/tests/integration/jep290/check_f_f_ok/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integration.jep290helidon-tests-integration-jep290-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-jep290-check_f_f_ok
diff --git a/tests/integration/jep290/check_f_f_w/pom.xml b/tests/integration/jep290/check_f_f_w/pom.xml
index 1c3e2f5a85a..32d667b6949 100644
--- a/tests/integration/jep290/check_f_f_w/pom.xml
+++ b/tests/integration/jep290/check_f_f_w/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integration.jep290helidon-tests-integration-jep290-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-jep290-check_f_f_w
diff --git a/tests/integration/jep290/check_f_p_ok/pom.xml b/tests/integration/jep290/check_f_p_ok/pom.xml
index 4a98bf1ef2d..e065da13342 100644
--- a/tests/integration/jep290/check_f_p_ok/pom.xml
+++ b/tests/integration/jep290/check_f_p_ok/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integration.jep290helidon-tests-integration-jep290-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-jep290-check_f_p_ok
diff --git a/tests/integration/jep290/check_f_p_w/pom.xml b/tests/integration/jep290/check_f_p_w/pom.xml
index a5cf3c15f6c..b8973689762 100644
--- a/tests/integration/jep290/check_f_p_w/pom.xml
+++ b/tests/integration/jep290/check_f_p_w/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integration.jep290helidon-tests-integration-jep290-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-jep290-check_f_p_w
diff --git a/tests/integration/jep290/pom.xml b/tests/integration/jep290/pom.xml
index 8962ffc2808..835796a0465 100644
--- a/tests/integration/jep290/pom.xml
+++ b/tests/integration/jep290/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests.integration.jep290helidon-tests-integration-jep290-project
diff --git a/tests/integration/jep290/server_and_custom/pom.xml b/tests/integration/jep290/server_and_custom/pom.xml
index 12cc6f0e677..e94c6df33ec 100644
--- a/tests/integration/jep290/server_and_custom/pom.xml
+++ b/tests/integration/jep290/server_and_custom/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integration.jep290helidon-tests-integration-jep290-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-jep290-server-and-custom
diff --git a/tests/integration/jep290/set_c_f_c/pom.xml b/tests/integration/jep290/set_c_f_c/pom.xml
index 5f4fe19cd29..c696ff80d47 100644
--- a/tests/integration/jep290/set_c_f_c/pom.xml
+++ b/tests/integration/jep290/set_c_f_c/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integration.jep290helidon-tests-integration-jep290-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-jep290-set_c_f_c
diff --git a/tests/integration/jep290/set_c_f_d/pom.xml b/tests/integration/jep290/set_c_f_d/pom.xml
index 21a6f0a86f9..dd8485441f0 100644
--- a/tests/integration/jep290/set_c_f_d/pom.xml
+++ b/tests/integration/jep290/set_c_f_d/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integration.jep290helidon-tests-integration-jep290-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-jep290-set_c_f_d
diff --git a/tests/integration/jep290/set_c_t_d/pom.xml b/tests/integration/jep290/set_c_t_d/pom.xml
index 26b3fe28567..5662dae6fc5 100644
--- a/tests/integration/jep290/set_c_t_d/pom.xml
+++ b/tests/integration/jep290/set_c_t_d/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integration.jep290helidon-tests-integration-jep290-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-jep290-set_c_t_d
diff --git a/tests/integration/jep290/set_f/pom.xml b/tests/integration/jep290/set_f/pom.xml
index 3a49deb0bd2..4bd2a157d05 100644
--- a/tests/integration/jep290/set_f/pom.xml
+++ b/tests/integration/jep290/set_f/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integration.jep290helidon-tests-integration-jep290-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-jep290-set_f
diff --git a/tests/integration/jep290/set_o/pom.xml b/tests/integration/jep290/set_o/pom.xml
index 6eb3b00c51a..b8cdd63170f 100644
--- a/tests/integration/jep290/set_o/pom.xml
+++ b/tests/integration/jep290/set_o/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integration.jep290helidon-tests-integration-jep290-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-jep290-set_o
diff --git a/tests/integration/jms/pom.xml b/tests/integration/jms/pom.xml
index 20901b05f7b..2530681ed3e 100644
--- a/tests/integration/jms/pom.xml
+++ b/tests/integration/jms/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests.integration.jms
diff --git a/tests/integration/jpa/appl/pom.xml b/tests/integration/jpa/appl/pom.xml
index 7c6c69c2805..0cd004c0790 100644
--- a/tests/integration/jpa/appl/pom.xml
+++ b/tests/integration/jpa/appl/pom.xml
@@ -23,7 +23,7 @@
io.helidon.applicationshelidon-mp
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../../../../applications/mp/pom.xml
diff --git a/tests/integration/jpa/model/pom.xml b/tests/integration/jpa/model/pom.xml
index d66dbb669b7..39c7204c7c0 100644
--- a/tests/integration/jpa/model/pom.xml
+++ b/tests/integration/jpa/model/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integration.jpahelidon-tests-integration-jpa-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests.integration.jpa
diff --git a/tests/integration/jpa/pom.xml b/tests/integration/jpa/pom.xml
index 70aa3320122..da491cc7ffd 100644
--- a/tests/integration/jpa/pom.xml
+++ b/tests/integration/jpa/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpom
diff --git a/tests/integration/jpa/simple/pom.xml b/tests/integration/jpa/simple/pom.xml
index e39bfd5b877..29ddb2abfdd 100644
--- a/tests/integration/jpa/simple/pom.xml
+++ b/tests/integration/jpa/simple/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integration.jpahelidon-tests-integration-jpa-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-jpa-simple
diff --git a/tests/integration/kafka/pom.xml b/tests/integration/kafka/pom.xml
index fc2499b4731..b12491a0b4d 100644
--- a/tests/integration/kafka/pom.xml
+++ b/tests/integration/kafka/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests.integration.kafka
diff --git a/tests/integration/mp-bean-validation/pom.xml b/tests/integration/mp-bean-validation/pom.xml
index 1745ac6f824..e2b61a09e47 100644
--- a/tests/integration/mp-bean-validation/pom.xml
+++ b/tests/integration/mp-bean-validation/pom.xml
@@ -24,7 +24,7 @@
io.helidon.applicationshelidon-mp
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../../../applications/mp/pom.xmlio.helidon.tests.integration
diff --git a/tests/integration/mp-gh-2421/pom.xml b/tests/integration/mp-gh-2421/pom.xml
index f7d0513a142..59d901960cd 100644
--- a/tests/integration/mp-gh-2421/pom.xml
+++ b/tests/integration/mp-gh-2421/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-integration-projectio.helidon.tests.integration
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/mp-gh-2461/pom.xml b/tests/integration/mp-gh-2461/pom.xml
index 320880bef87..5bf7dec9fd1 100644
--- a/tests/integration/mp-gh-2461/pom.xml
+++ b/tests/integration/mp-gh-2461/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-integration-projectio.helidon.tests.integration
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/mp-gh-3246/pom.xml b/tests/integration/mp-gh-3246/pom.xml
index 7f68962da97..ea32512b090 100644
--- a/tests/integration/mp-gh-3246/pom.xml
+++ b/tests/integration/mp-gh-3246/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-integration-projectio.helidon.tests.integration
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/mp-gh-3974/pom.xml b/tests/integration/mp-gh-3974/pom.xml
index 87e6175b835..1ff666fe357 100644
--- a/tests/integration/mp-gh-3974/pom.xml
+++ b/tests/integration/mp-gh-3974/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-integration-projectio.helidon.tests.integration
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/mp-gh-4123/pom.xml b/tests/integration/mp-gh-4123/pom.xml
index b4a7ba33c8d..520a24e2f11 100644
--- a/tests/integration/mp-gh-4123/pom.xml
+++ b/tests/integration/mp-gh-4123/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/mp-gh-4654/pom.xml b/tests/integration/mp-gh-4654/pom.xml
index 40f47ef07cd..50fb9a9ed37 100644
--- a/tests/integration/mp-gh-4654/pom.xml
+++ b/tests/integration/mp-gh-4654/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/mp-gh-5328/pom.xml b/tests/integration/mp-gh-5328/pom.xml
index 73771b36c3b..34a18767f7d 100644
--- a/tests/integration/mp-gh-5328/pom.xml
+++ b/tests/integration/mp-gh-5328/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/mp-gh-8349/pom.xml b/tests/integration/mp-gh-8349/pom.xml
index fc77821940f..3fad987f473 100644
--- a/tests/integration/mp-gh-8349/pom.xml
+++ b/tests/integration/mp-gh-8349/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/mp-gh-8478/pom.xml b/tests/integration/mp-gh-8478/pom.xml
index edbbf70ac40..59350f2290e 100644
--- a/tests/integration/mp-gh-8478/pom.xml
+++ b/tests/integration/mp-gh-8478/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/mp-gh-8493/pom.xml b/tests/integration/mp-gh-8493/pom.xml
index 64eeaeff943..0bd5cdab4d1 100644
--- a/tests/integration/mp-gh-8493/pom.xml
+++ b/tests/integration/mp-gh-8493/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/mp-gh-8495/pom.xml b/tests/integration/mp-gh-8495/pom.xml
index d9b9512743a..1cb205f4806 100644
--- a/tests/integration/mp-gh-8495/pom.xml
+++ b/tests/integration/mp-gh-8495/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/mp-graphql/pom.xml b/tests/integration/mp-graphql/pom.xml
index 5ff5e2db8b7..4adfd967b69 100644
--- a/tests/integration/mp-graphql/pom.xml
+++ b/tests/integration/mp-graphql/pom.xml
@@ -21,7 +21,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-mp-graphql
diff --git a/tests/integration/mp-security-client/pom.xml b/tests/integration/mp-security-client/pom.xml
index e7bbe2fc62f..a28881e8e9d 100644
--- a/tests/integration/mp-security-client/pom.xml
+++ b/tests/integration/mp-security-client/pom.xml
@@ -22,7 +22,7 @@
io.helidon.applicationshelidon-mp
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../../../applications/mp/pom.xmlio.helidon.tests.integration
diff --git a/tests/integration/mp-ws-services/pom.xml b/tests/integration/mp-ws-services/pom.xml
index 8d64fbe4135..6044c6ae3e9 100644
--- a/tests/integration/mp-ws-services/pom.xml
+++ b/tests/integration/mp-ws-services/pom.xml
@@ -22,7 +22,7 @@
io.helidon.applicationshelidon-mp
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../../../applications/mp/pom.xmlio.helidon.tests.integration
diff --git a/tests/integration/oidc/pom.xml b/tests/integration/oidc/pom.xml
index 6b7e37a216c..dc08579de1b 100644
--- a/tests/integration/oidc/pom.xml
+++ b/tests/integration/oidc/pom.xml
@@ -21,7 +21,7 @@
helidon-tests-integration-projectio.helidon.tests.integration
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/packaging/mp-1/pom.xml b/tests/integration/packaging/mp-1/pom.xml
index c499ce8f17b..9053497177b 100644
--- a/tests/integration/packaging/mp-1/pom.xml
+++ b/tests/integration/packaging/mp-1/pom.xml
@@ -22,7 +22,7 @@
io.helidon.applicationshelidon-mp
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../../../../applications/mp/pom.xmlio.helidon.tests.integration.packaging
diff --git a/tests/integration/packaging/mp-2/pom.xml b/tests/integration/packaging/mp-2/pom.xml
index d26f8644a8f..be51c3b9827 100644
--- a/tests/integration/packaging/mp-2/pom.xml
+++ b/tests/integration/packaging/mp-2/pom.xml
@@ -22,7 +22,7 @@
io.helidon.applicationshelidon-mp
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../../../../applications/mp/pom.xmlio.helidon.tests.integration.packaging
diff --git a/tests/integration/packaging/mp-3/pom.xml b/tests/integration/packaging/mp-3/pom.xml
index f6383cc74d6..b12400f384d 100644
--- a/tests/integration/packaging/mp-3/pom.xml
+++ b/tests/integration/packaging/mp-3/pom.xml
@@ -22,7 +22,7 @@
io.helidon.applicationshelidon-mp
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../../../../applications/mp/pom.xmlio.helidon.tests.integration.packaging
diff --git a/tests/integration/packaging/pom.xml b/tests/integration/packaging/pom.xml
index 461d5928a4a..04b8081e3ae 100644
--- a/tests/integration/packaging/pom.xml
+++ b/tests/integration/packaging/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomio.helidon.tests.integration.packaging
diff --git a/tests/integration/packaging/se-1/pom.xml b/tests/integration/packaging/se-1/pom.xml
index 9e942340afd..7b759abcf76 100644
--- a/tests/integration/packaging/se-1/pom.xml
+++ b/tests/integration/packaging/se-1/pom.xml
@@ -22,7 +22,7 @@
io.helidon.applicationshelidon-se
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../../../../applications/se/pom.xmlio.helidon.tests.integration.packaging
diff --git a/tests/integration/packaging/se-1/src/main/resources/logging.properties b/tests/integration/packaging/se-1/src/main/resources/logging.properties
index 48878bf93d9..c19a3babf9c 100644
--- a/tests/integration/packaging/se-1/src/main/resources/logging.properties
+++ b/tests/integration/packaging/se-1/src/main/resources/logging.properties
@@ -19,18 +19,11 @@
# Send messages to the console
handlers=io.helidon.logging.jul.HelidonConsoleHandler
-
-# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
-java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+# Use JSON formatter with default format
+io.helidon.logging.jul.HelidonConsoleHandler.formatter=io.helidon.logging.jul.HelidonJsonFormatter
# Global logging level. Can be overridden by specific loggers
.level=INFO
-# Component specific log levels
-#io.helidon.webserver.level=INFO
-#io.helidon.config.level=INFO
-#io.helidon.security.level=INFO
-#io.helidon.common.level=INFO
-
io.helidon.webserver.staticcontent.ClassPathContentHandler.level=FINEST
io.helidon.webserver.staticcontent.FileSystemContentHandler.level=FINEST
diff --git a/tests/integration/packaging/static-content/pom.xml b/tests/integration/packaging/static-content/pom.xml
index 66293c0042e..70e445d5af6 100644
--- a/tests/integration/packaging/static-content/pom.xml
+++ b/tests/integration/packaging/static-content/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integration.packaginghelidon-tests-integration-packaging
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-packaging-static-contentHelidon Tests Integration Packaging Static Content
diff --git a/tests/integration/pom.xml b/tests/integration/pom.xml
index d29401cf66f..aaf2d127b53 100644
--- a/tests/integration/pom.xml
+++ b/tests/integration/pom.xml
@@ -22,7 +22,7 @@
io.helidon.testshelidon-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests.integrationhelidon-tests-integration-project
diff --git a/tests/integration/restclient-connector/pom.xml b/tests/integration/restclient-connector/pom.xml
index f1bc2e369aa..f36cca4f12b 100644
--- a/tests/integration/restclient-connector/pom.xml
+++ b/tests/integration/restclient-connector/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-restclient-connector
diff --git a/tests/integration/restclient/pom.xml b/tests/integration/restclient/pom.xml
index ddf62de58c0..76bca01d835 100644
--- a/tests/integration/restclient/pom.xml
+++ b/tests/integration/restclient/pom.xml
@@ -21,7 +21,7 @@
helidon-tests-integration-projectio.helidon.tests.integration
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/security/abac-policy/pom.xml b/tests/integration/security/abac-policy/pom.xml
new file mode 100644
index 00000000000..661dececfe3
--- /dev/null
+++ b/tests/integration/security/abac-policy/pom.xml
@@ -0,0 +1,63 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.tests.integration
+ helidon-tests-integration-security
+ 4.2.0-SNAPSHOT
+
+
+ helidon-tests-integration-security-abac-policy
+ Helidon Tests Integration ABAC Policy Validator
+
+
+
+ io.helidon.microprofile.bundles
+ helidon-microprofile
+
+
+ io.helidon.security.abac
+ helidon-security-abac-policy-el
+
+
+ org.glassfish
+ jakarta.el
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.microprofile.testing
+ helidon-microprofile-testing-junit5
+ test
+
+
+
+
\ No newline at end of file
diff --git a/tests/integration/security/abac-policy/src/main/java/io/helidon/tests/integration/security/abac/policy/PolicyStatementExplicitResource.java b/tests/integration/security/abac-policy/src/main/java/io/helidon/tests/integration/security/abac/policy/PolicyStatementExplicitResource.java
new file mode 100644
index 00000000000..c470f7ae02c
--- /dev/null
+++ b/tests/integration/security/abac-policy/src/main/java/io/helidon/tests/integration/security/abac/policy/PolicyStatementExplicitResource.java
@@ -0,0 +1,79 @@
+/*
+ * 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.tests.integration.security.abac.policy;
+
+import io.helidon.security.AuthorizationResponse;
+import io.helidon.security.SecurityContext;
+import io.helidon.security.abac.policy.PolicyValidator;
+import io.helidon.security.annotations.Authorized;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.Response;
+
+/**
+ * A resource with explicit authorization.
+ */
+@Path("/explicit")
+public class PolicyStatementExplicitResource {
+
+ /**
+ * Policy statement and explicit authorization is set via annotations and not overridden.
+ *
+ * @param context security context to perform an explicit authorization
+ * @return result of the authorization
+ */
+ @GET
+ @Path("annotation")
+ @Authorized(explicit = true)
+ @PolicyValidator.PolicyStatement("${env.time.year >= 2017}")
+ public Response annotation(@Context SecurityContext context) {
+ AuthorizationResponse atzResponse = context.authorize();
+
+ if (atzResponse.isPermitted()) {
+ return Response.ok().entity("passed").build();
+ } else {
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity(atzResponse.description().orElse("Access not granted"))
+ .build();
+ }
+ }
+
+ /**
+ * Policy statement and explicit authorization is set via configuration.
+ *
+ * @param context security context to perform an explicit authorization
+ * @return result of the authorization
+ */
+ @GET
+ @Path("configuration")
+ @Authorized
+ @PolicyValidator.PolicyStatement("${env.time.year < 2017}")
+ public Response configuration(@Context SecurityContext context) {
+ AuthorizationResponse atzResponse = context.authorize();
+
+ if (atzResponse.isPermitted()) {
+ return Response.ok().entity("passed").build();
+ } else {
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity(atzResponse.description().orElse("Access not granted"))
+ .build();
+ }
+ }
+
+}
diff --git a/tests/integration/security/abac-policy/src/main/java/io/helidon/tests/integration/security/abac/policy/PolicyStatementResource.java b/tests/integration/security/abac-policy/src/main/java/io/helidon/tests/integration/security/abac/policy/PolicyStatementResource.java
new file mode 100644
index 00000000000..1b3551b90db
--- /dev/null
+++ b/tests/integration/security/abac-policy/src/main/java/io/helidon/tests/integration/security/abac/policy/PolicyStatementResource.java
@@ -0,0 +1,103 @@
+/*
+ * 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.tests.integration.security.abac.policy;
+
+import io.helidon.security.abac.policy.PolicyValidator;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Response;
+
+/**
+ * A resource with abac policy statements.
+ */
+@Path("/policy")
+public class PolicyStatementResource {
+
+ /**
+ * Policy statement configured via annotation.
+ *
+ * @return passed value
+ */
+ @GET
+ @Path("/annotation")
+ @PolicyValidator.PolicyStatement("${env.time.year >= 2017}")
+ public String annotation() {
+ return "passed";
+ }
+
+ /**
+ * Policy statement overridden by the config.
+ *
+ * @return passed value
+ */
+ @GET
+ @Path("/override")
+ @PolicyValidator.PolicyStatement("${env.time.year <= 2017}")
+ public String override() {
+ return "passed";
+ }
+
+ /**
+ * Policy statement should not be overridden by the config.
+ *
+ * @return passed value
+ */
+ @GET
+ @Path("/override2")
+ @PolicyValidator.PolicyStatement("${env.time.year <= 2017}")
+ public String override2() {
+ return "should not pass";
+ }
+
+ /**
+ * Policy statement overridden by the config with asterisk present in path.
+ *
+ * @return passed value
+ */
+ @GET
+ @Path("/asterisk")
+ @PolicyValidator.PolicyStatement("${env.time.year <= 2017}")
+ public String asterisk() {
+ return "passed";
+ }
+
+ /**
+ * Policy statement overridden by the config with asterisk present in path.
+ *
+ * @return passed value
+ */
+ @GET
+ @Path("/asterisk2")
+ @PolicyValidator.PolicyStatement("${env.time.year <= 2017}")
+ public String asterisk2() {
+ return "passed";
+ }
+
+ /**
+ * Policy statement not overridden by configuration and should not let anyone in.
+ *
+ * @return should not pass value
+ */
+ @GET
+ @Path("/notOverride")
+ @PolicyValidator.PolicyStatement("${env.time.year <= 2017}")
+ public String notOverride() {
+ return "should not pass";
+ }
+
+}
diff --git a/tests/integration/security/abac-policy/src/main/resources/META-INF/beans.xml b/tests/integration/security/abac-policy/src/main/resources/META-INF/beans.xml
new file mode 100644
index 00000000000..52f89a20d18
--- /dev/null
+++ b/tests/integration/security/abac-policy/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/tests/integration/security/abac-policy/src/main/resources/application.yaml b/tests/integration/security/abac-policy/src/main/resources/application.yaml
new file mode 100644
index 00000000000..b3be701fd6a
--- /dev/null
+++ b/tests/integration/security/abac-policy/src/main/resources/application.yaml
@@ -0,0 +1,35 @@
+#
+# 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.
+#
+server:
+ port: 0
+ features:
+ security:
+ endpoints:
+ - path: "/policy/override"
+ config:
+ abac.policy-validator.statement: "${env.time.year >= 2017}"
+ - path: "/policy/asterisk*"
+ config:
+ abac.policy-validator.statement: "${env.time.year >= 2017}"
+ - path: "/explicit/configuration"
+ config:
+ authorize: true
+ authorization-explicit: true
+ abac.policy-validator.statement: "${env.time.year >= 2017}"
+
+security:
+ providers:
+ - abac:
\ No newline at end of file
diff --git a/tests/integration/security/abac-policy/src/main/resources/logging.properties b/tests/integration/security/abac-policy/src/main/resources/logging.properties
new file mode 100644
index 00000000000..2a6f3a7be2a
--- /dev/null
+++ b/tests/integration/security/abac-policy/src/main/resources/logging.properties
@@ -0,0 +1,28 @@
+#
+# Copyright (c) 2024 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
diff --git a/tests/integration/security/abac-policy/src/test/java/io/helidon/tests/integration/security/abac/policy/ExplicitPolicyTest.java b/tests/integration/security/abac-policy/src/test/java/io/helidon/tests/integration/security/abac/policy/ExplicitPolicyTest.java
new file mode 100644
index 00000000000..a77ab2618ab
--- /dev/null
+++ b/tests/integration/security/abac-policy/src/test/java/io/helidon/tests/integration/security/abac/policy/ExplicitPolicyTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.tests.integration.security.abac.policy;
+
+import io.helidon.http.Status;
+import io.helidon.microprofile.testing.junit5.HelidonTest;
+
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Response;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@HelidonTest
+class ExplicitPolicyTest {
+
+ @Test
+ void testAnnotation(WebTarget target) {
+ try (Response response = target.path("/explicit/annotation")
+ .request()
+ .get()) {
+ assertThat(response.getStatus(), is(Status.OK_200.code()));
+ assertThat(response.readEntity(String.class), is("passed"));
+ }
+ }
+
+ @Test
+ void testConfiguration(WebTarget target) {
+ try (Response response = target.path("/explicit/configuration")
+ .request()
+ .get()) {
+ assertThat(response.getStatus(), is(Status.OK_200.code()));
+ assertThat(response.readEntity(String.class), is("passed"));
+ }
+ }
+
+}
diff --git a/tests/integration/security/abac-policy/src/test/java/io/helidon/tests/integration/security/abac/policy/PolicyTest.java b/tests/integration/security/abac-policy/src/test/java/io/helidon/tests/integration/security/abac/policy/PolicyTest.java
new file mode 100644
index 00000000000..bf63cc4b70e
--- /dev/null
+++ b/tests/integration/security/abac-policy/src/test/java/io/helidon/tests/integration/security/abac/policy/PolicyTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.tests.integration.security.abac.policy;
+
+import io.helidon.http.Status;
+import io.helidon.microprofile.testing.junit5.AddConfig;
+import io.helidon.microprofile.testing.junit5.HelidonTest;
+
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Response;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@HelidonTest
+class PolicyTest {
+
+ @Test
+ void testAnnotation(WebTarget target) {
+ try (Response response = target.path("/policy/annotation")
+ .request()
+ .get()) {
+ assertThat(response.getStatus(), is(Status.OK_200.code()));
+ assertThat(response.readEntity(String.class), is("passed"));
+ }
+ }
+
+ @Test
+ void testExplicitOverride(WebTarget target) {
+ try (Response response = target.path("/policy/override")
+ .request()
+ .get()) {
+ assertThat(response.getStatus(), is(Status.OK_200.code()));
+ assertThat(response.readEntity(String.class), is("passed"));
+ }
+ try (Response response = target.path("/policy/override2")
+ .request()
+ .get()) {
+ assertThat(response.getStatus(), is(Status.FORBIDDEN_403.code()));
+ }
+ }
+
+ @Test
+ void testAsteriskOverride(WebTarget target) {
+ try (Response response = target.path("/policy/asterisk")
+ .request()
+ .get()) {
+ assertThat(response.getStatus(), is(Status.OK_200.code()));
+ assertThat(response.readEntity(String.class), is("passed"));
+ }
+ try (Response response = target.path("/policy/asterisk2")
+ .request()
+ .get()) {
+ assertThat(response.getStatus(), is(Status.OK_200.code()));
+ assertThat(response.readEntity(String.class), is("passed"));
+ }
+ }
+
+ @Test
+ void testNotOverride(WebTarget target) {
+ try (Response response = target.path("/policy/notOverride")
+ .request()
+ .get()) {
+ assertThat(response.getStatus(), is(Status.FORBIDDEN_403.code()));
+ }
+ }
+}
diff --git a/tests/integration/security/gh1487/pom.xml b/tests/integration/security/gh1487/pom.xml
index e4d2997f64b..ed911f1ea97 100644
--- a/tests/integration/security/gh1487/pom.xml
+++ b/tests/integration/security/gh1487/pom.xml
@@ -21,7 +21,7 @@
helidon-tests-integration-securityio.helidon.tests.integration
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/security/gh2297/pom.xml b/tests/integration/security/gh2297/pom.xml
index 45feb6d3743..6bf6788e6bc 100644
--- a/tests/integration/security/gh2297/pom.xml
+++ b/tests/integration/security/gh2297/pom.xml
@@ -21,7 +21,7 @@
helidon-tests-integration-securityio.helidon.tests.integration
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/security/gh2455/pom.xml b/tests/integration/security/gh2455/pom.xml
index 986da2f83b8..efa4681cd1e 100644
--- a/tests/integration/security/gh2455/pom.xml
+++ b/tests/integration/security/gh2455/pom.xml
@@ -21,7 +21,7 @@
helidon-tests-integration-securityio.helidon.tests.integration
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/security/gh2772/pom.xml b/tests/integration/security/gh2772/pom.xml
index 2836a2518e2..f7437ba34c3 100644
--- a/tests/integration/security/gh2772/pom.xml
+++ b/tests/integration/security/gh2772/pom.xml
@@ -21,7 +21,7 @@
helidon-tests-integration-securityio.helidon.tests.integration
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/security/path-params/pom.xml b/tests/integration/security/path-params/pom.xml
index 1dabe3f5406..c5f9b434cac 100644
--- a/tests/integration/security/path-params/pom.xml
+++ b/tests/integration/security/path-params/pom.xml
@@ -22,7 +22,7 @@
helidon-tests-integration-securityio.helidon.tests.integration
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/security/pom.xml b/tests/integration/security/pom.xml
index 259b82483c7..f6d6313f3ef 100644
--- a/tests/integration/security/pom.xml
+++ b/tests/integration/security/pom.xml
@@ -23,7 +23,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpom
@@ -42,5 +42,6 @@
path-paramssecurity-response-mappersecurity-annotation
+ abac-policy
diff --git a/tests/integration/security/security-annotation/pom.xml b/tests/integration/security/security-annotation/pom.xml
index a5af6a875a8..0b2f4244dda 100644
--- a/tests/integration/security/security-annotation/pom.xml
+++ b/tests/integration/security/security-annotation/pom.xml
@@ -21,7 +21,7 @@
io.helidon.tests.integrationhelidon-tests-integration-security
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT
diff --git a/tests/integration/security/security-response-mapper/pom.xml b/tests/integration/security/security-response-mapper/pom.xml
index 6091c8d371e..d6133ac5904 100644
--- a/tests/integration/security/security-response-mapper/pom.xml
+++ b/tests/integration/security/security-response-mapper/pom.xml
@@ -21,7 +21,7 @@
helidon-tests-integration-securityio.helidon.tests.integration
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0
diff --git a/tests/integration/tls-revocation-config/pom.xml b/tests/integration/tls-revocation-config/pom.xml
index 26eef2d67b8..68eff6d6cf2 100644
--- a/tests/integration/tls-revocation-config/pom.xml
+++ b/tests/integration/tls-revocation-config/pom.xml
@@ -21,7 +21,7 @@
io.helidon.tests.integrationhelidon-tests-integration-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tests-integration-tls-revocation-config
diff --git a/tests/integration/vault/pom.xml b/tests/integration/vault/pom.xml
index dc9faec973a..3047602b3fd 100644
--- a/tests/integration/vault/pom.xml
+++ b/tests/integration/vault/pom.xml
@@ -23,7 +23,7 @@
helidon-tests-integration-projectio.helidon.tests.integration
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests.integration.vault
diff --git a/tests/integration/zipkin-mp-2.2/pom.xml b/tests/integration/zipkin-mp-2.2/pom.xml
index 99b8e6183d1..79c150b6ce4 100644
--- a/tests/integration/zipkin-mp-2.2/pom.xml
+++ b/tests/integration/zipkin-mp-2.2/pom.xml
@@ -22,7 +22,7 @@
io.helidon.applicationshelidon-mp
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT../../../applications/mp/pom.xmlio.helidon.tests.integration
diff --git a/tests/pom.xml b/tests/pom.xml
index 7dffe1b5c74..bbccbcca873 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -23,7 +23,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.tests
diff --git a/tests/tck/pom.xml b/tests/tck/pom.xml
index 73b3d07505d..4b6c8e31b10 100644
--- a/tests/tck/pom.xml
+++ b/tests/tck/pom.xml
@@ -23,7 +23,7 @@
io.helidon.testshelidon-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0pom
diff --git a/tests/tck/tck-reactive-streams/pom.xml b/tests/tck/tck-reactive-streams/pom.xml
index 6d7d070a9e7..c1033af7165 100644
--- a/tests/tck/tck-reactive-streams/pom.xml
+++ b/tests/tck/tck-reactive-streams/pom.xml
@@ -23,7 +23,7 @@
io.helidon.teststck-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOT4.0.0tck-reactive-streams
diff --git a/tracing/config/pom.xml b/tracing/config/pom.xml
index 61396dc214c..0922aa94363 100644
--- a/tracing/config/pom.xml
+++ b/tracing/config/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tracinghelidon-tracing-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tracing-config
diff --git a/tracing/jersey-client/pom.xml b/tracing/jersey-client/pom.xml
index f21a97da106..d0c18b0a08b 100644
--- a/tracing/jersey-client/pom.xml
+++ b/tracing/jersey-client/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tracinghelidon-tracing-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tracing-jersey-client
diff --git a/tracing/jersey/pom.xml b/tracing/jersey/pom.xml
index 9360ebe8fd1..29daeb3697b 100644
--- a/tracing/jersey/pom.xml
+++ b/tracing/jersey/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tracinghelidon-tracing-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tracing-jersey
diff --git a/tracing/pom.xml b/tracing/pom.xml
index c22c81e2812..da42c8fc106 100644
--- a/tracing/pom.xml
+++ b/tracing/pom.xml
@@ -22,7 +22,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomio.helidon.tracing
diff --git a/tracing/provider-tests/pom.xml b/tracing/provider-tests/pom.xml
index 0344d71e8f2..f976d19c20b 100644
--- a/tracing/provider-tests/pom.xml
+++ b/tracing/provider-tests/pom.xml
@@ -21,7 +21,7 @@
io.helidon.tracinghelidon-tracing-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tracing-provider-tests
diff --git a/tracing/providers/jaeger/pom.xml b/tracing/providers/jaeger/pom.xml
index 731cb3709a3..1768d7baec7 100644
--- a/tracing/providers/jaeger/pom.xml
+++ b/tracing/providers/jaeger/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tracing.providershelidon-tracing-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tracing-providers-jaeger
diff --git a/tracing/providers/opentelemetry/pom.xml b/tracing/providers/opentelemetry/pom.xml
index d8bdf95fa07..d3b1bde9b37 100644
--- a/tracing/providers/opentelemetry/pom.xml
+++ b/tracing/providers/opentelemetry/pom.xml
@@ -21,7 +21,7 @@
io.helidon.tracing.providershelidon-tracing-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tracing-providers-opentelemetry
diff --git a/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetryDataPropagationProvider.java b/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetryDataPropagationProvider.java
index 014c1283c32..1705dc40ea7 100644
--- a/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetryDataPropagationProvider.java
+++ b/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetryDataPropagationProvider.java
@@ -62,7 +62,7 @@ public void propagateData(OpenTelemetryContext context) {
}
/**
- * OpenTelementry context.
+ * OpenTelemetry context.
*/
public static class OpenTelemetryContext {
private final Span span;
diff --git a/tracing/providers/opentracing/pom.xml b/tracing/providers/opentracing/pom.xml
index 48f08bf14d9..4feeb428db3 100644
--- a/tracing/providers/opentracing/pom.xml
+++ b/tracing/providers/opentracing/pom.xml
@@ -21,7 +21,7 @@
io.helidon.tracing.providershelidon-tracing-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tracing-providers-opentracing
diff --git a/tracing/providers/opentracing/src/main/java/io/helidon/tracing/providers/opentracing/OpenTracingTracerProvider.java b/tracing/providers/opentracing/src/main/java/io/helidon/tracing/providers/opentracing/OpenTracingTracerProvider.java
index 2926fa3f385..c3c887be51e 100644
--- a/tracing/providers/opentracing/src/main/java/io/helidon/tracing/providers/opentracing/OpenTracingTracerProvider.java
+++ b/tracing/providers/opentracing/src/main/java/io/helidon/tracing/providers/opentracing/OpenTracingTracerProvider.java
@@ -23,6 +23,8 @@
import io.helidon.common.LazyValue;
import io.helidon.common.Weight;
import io.helidon.common.Weighted;
+import io.helidon.common.config.Config;
+import io.helidon.common.config.GlobalConfig;
import io.helidon.tracing.Span;
import io.helidon.tracing.SpanListener;
import io.helidon.tracing.Tracer;
@@ -35,6 +37,10 @@
/**
* {@link java.util.ServiceLoader} service implementation of {@link io.helidon.tracing.spi.TracerProvider} for Open Tracing
* tracers.
+ *
+ * When dealing with the global tracer, manage both the Helidon one and also the OpenTracing one in concert, whether
+ * defaulting them or assigning them via {@link #global(io.helidon.tracing.Tracer)}.
+ *
*/
@Weight(Weighted.DEFAULT_WEIGHT - 50) // low weight, so it is easy to override
public class OpenTracingTracerProvider implements TracerProvider {
@@ -42,6 +48,19 @@ public class OpenTracingTracerProvider implements TracerProvider {
private static final LazyValue> SPAN_LISTENERS =
LazyValue.create(() -> HelidonServiceLoader.create(ServiceLoader.load(SpanListener.class)).asList());
+ private LazyValue globalHelidonTracer = LazyValue.create(() -> {
+ Config tracingConfig = GlobalConfig.config().get("tracing");
+
+ // Set up to create an explicit OpenTracing tracer only if we have config for tracing, indicating that the user wants
+ // something other than the no-op implementation.
+ if (tracingConfig.exists()) {
+ io.opentracing.Tracer openTracingTracer = OpenTracingTracerBuilder.create(tracingConfig)
+ .build();
+ GlobalTracer.registerIfAbsent(openTracingTracer);
+ }
+ return OpenTracingTracer.create(GlobalTracer.get());
+ });
+
@Override
public TracerBuilder> createBuilder() {
return OpenTracingTracer.builder();
@@ -49,13 +68,17 @@ public TracerBuilder> createBuilder() {
@Override
public Tracer global() {
- return OpenTracingTracer.create(GlobalTracer.get());
+ return globalHelidonTracer.get();
}
@Override
public void global(Tracer tracer) {
if (tracer instanceof OpenTracingTracer opt) {
- GlobalTracer.registerIfAbsent(opt.openTracing());
+ GlobalTracer.registerIfAbsent(() -> {
+ io.opentracing.Tracer openTracingTracer = opt.openTracing();
+ globalHelidonTracer = LazyValue.create(OpenTracingTracer.create(openTracingTracer));
+ return openTracingTracer;
+ });
}
}
diff --git a/tracing/providers/pom.xml b/tracing/providers/pom.xml
index 0d3d4997bde..7a605dba855 100644
--- a/tracing/providers/pom.xml
+++ b/tracing/providers/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tracinghelidon-tracing-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpomio.helidon.tracing.providers
diff --git a/tracing/providers/zipkin/pom.xml b/tracing/providers/zipkin/pom.xml
index 26ca50caa6a..6073a49b77a 100644
--- a/tracing/providers/zipkin/pom.xml
+++ b/tracing/providers/zipkin/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tracing.providershelidon-tracing-providers-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tracing-providers-zipkin
@@ -57,6 +57,10 @@
io.opentracing.bravebrave-opentracing
+
+ io.helidon.common
+ helidon-common-context
+ io.helidon.common.featureshelidon-common-features-api
@@ -138,17 +142,33 @@
org.apache.maven.pluginsmaven-surefire-plugin
-
-
- io.helidon.tracing.providers.tests.TestTracerAndSpanPropagation.java
-
-
+
+
+ default-test
+
+
+ io.helidon.tracing.providers.tests.TestTracerAndSpanPropagation.java
+
+
+
+
+ zipkin-propagation-test
+
+ test
+
+
+
+ io.helidon.tracing.providers.tests.TestTracerAndSpanPropagation.java
+
+
+
+
diff --git a/tracing/providers/zipkin/src/main/java/io/helidon/tracing/providers/zipkin/ZipkinDataPropagationProvider.java b/tracing/providers/zipkin/src/main/java/io/helidon/tracing/providers/zipkin/ZipkinDataPropagationProvider.java
new file mode 100644
index 00000000000..123d32fb83b
--- /dev/null
+++ b/tracing/providers/zipkin/src/main/java/io/helidon/tracing/providers/zipkin/ZipkinDataPropagationProvider.java
@@ -0,0 +1,80 @@
+/*
+ * 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.tracing.providers.zipkin;
+
+import io.helidon.common.context.Contexts;
+import io.helidon.common.context.spi.DataPropagationProvider;
+
+import io.opentracing.Scope;
+import io.opentracing.Span;
+import io.opentracing.Tracer;
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * Data propagation provider for the Helidon Zipkin tracing provider.
+ */
+public class ZipkinDataPropagationProvider implements DataPropagationProvider {
+
+ private static final System.Logger LOGGER = System.getLogger(ZipkinDataPropagationProvider.class.getName());
+
+ /**
+ * Creates new provider; public for service loading.
+ */
+ public ZipkinDataPropagationProvider() {
+ }
+
+ @Override
+ public ZipkinContext data() {
+ Tracer tracer = Contexts.context()
+ .flatMap(ctx -> ctx.get(Tracer.class))
+ .orElseGet(GlobalTracer::get);
+ Span span = tracer.activeSpan();
+ return new ZipkinContext(tracer, span);
+ }
+
+ @Override
+ public void propagateData(ZipkinContext data) {
+ if (data != null && data.span != null) {
+ data.scope = data.tracer.activateSpan(data.span);
+ }
+ }
+
+ @Override
+ public void clearData(ZipkinContext data) {
+ if (data != null && data.scope != null) {
+ try {
+ data.scope.close();
+ } catch (Exception e) {
+ LOGGER.log(System.Logger.Level.TRACE, "Cannot close tracing span", e);
+ }
+ }
+ }
+
+ /**
+ * Zipkin-specific propagation context.
+ */
+ static class ZipkinContext {
+
+ private final Tracer tracer;
+ private final Span span;
+ private Scope scope;
+
+ private ZipkinContext(Tracer tracer, Span span) {
+ this.tracer = tracer;
+ this.span = span;
+ }
+ }
+}
diff --git a/tracing/providers/zipkin/src/main/java/io/helidon/tracing/providers/zipkin/ZipkinTracerProvider.java b/tracing/providers/zipkin/src/main/java/io/helidon/tracing/providers/zipkin/ZipkinTracerProvider.java
index 2077f3901bf..fcdc8f2e816 100644
--- a/tracing/providers/zipkin/src/main/java/io/helidon/tracing/providers/zipkin/ZipkinTracerProvider.java
+++ b/tracing/providers/zipkin/src/main/java/io/helidon/tracing/providers/zipkin/ZipkinTracerProvider.java
@@ -48,6 +48,12 @@ public class ZipkinTracerProvider implements OpenTracingProvider {
private static final List TRACING_CONTEXT_PROPAGATION_HEADERS =
List.of(X_OT_SPAN_CONTEXT, X_B3_TRACE_ID, X_B3_SPAN_ID, X_B3_PARENT_SPAN_ID, X_B3_SAMPLED, X_B3_FLAGS);
+ /**
+ * Public constructor for service loading.
+ */
+ public ZipkinTracerProvider() {
+ }
+
@Override
public ZipkinTracerBuilder createBuilder() {
return ZipkinTracerBuilder.create();
diff --git a/tracing/providers/zipkin/src/main/java/module-info.java b/tracing/providers/zipkin/src/main/java/module-info.java
index afc01904698..23d95ba0206 100644
--- a/tracing/providers/zipkin/src/main/java/module-info.java
+++ b/tracing/providers/zipkin/src/main/java/module-info.java
@@ -30,6 +30,7 @@
requires brave.opentracing;
requires brave;
requires io.helidon.common;
+ requires io.helidon.common.context;
requires io.helidon.tracing.providers.opentracing;
requires io.opentracing.noop;
requires io.opentracing.util;
@@ -51,4 +52,6 @@
provides io.helidon.tracing.providers.opentracing.spi.OpenTracingProvider
with io.helidon.tracing.providers.zipkin.ZipkinTracerProvider;
+ provides io.helidon.common.context.spi.DataPropagationProvider
+ with io.helidon.tracing.providers.zipkin.ZipkinDataPropagationProvider;
}
\ No newline at end of file
diff --git a/tracing/providers/zipkin/src/test/resources/application.yaml b/tracing/providers/zipkin/src/test/resources/application.yaml
index 0f359e5fb7e..57a4e673af9 100644
--- a/tracing/providers/zipkin/src/test/resources/application.yaml
+++ b/tracing/providers/zipkin/src/test/resources/application.yaml
@@ -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.
@@ -45,4 +45,5 @@ tracing:
int-tags:
tag5: 145
tag6: 741
-
+ # With changes to OpenTracing global tracer handling, provide a service name for the Zipkin implementation to use.
+ service: "helidon-test-service"
diff --git a/tracing/tests/it-tracing-client-zipkin/pom.xml b/tracing/tests/it-tracing-client-zipkin/pom.xml
index c96ae79683c..c7de2c92661 100644
--- a/tracing/tests/it-tracing-client-zipkin/pom.xml
+++ b/tracing/tests/it-tracing-client-zipkin/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tracinghelidon-tracing-tests
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tracing-tests-it1
diff --git a/tracing/tests/pom.xml b/tracing/tests/pom.xml
index 78f85fd2361..80831fbd699 100644
--- a/tracing/tests/pom.xml
+++ b/tracing/tests/pom.xml
@@ -25,7 +25,7 @@
io.helidon.tracinghelidon-tracing-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpom
diff --git a/tracing/tracer-resolver/pom.xml b/tracing/tracer-resolver/pom.xml
index 722e0029241..30d5941b504 100644
--- a/tracing/tracer-resolver/pom.xml
+++ b/tracing/tracer-resolver/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tracinghelidon-tracing-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tracing-tracer-resolver
diff --git a/tracing/tracing/pom.xml b/tracing/tracing/pom.xml
index 6a0440a5081..7fcb650806b 100644
--- a/tracing/tracing/pom.xml
+++ b/tracing/tracing/pom.xml
@@ -22,7 +22,7 @@
io.helidon.tracinghelidon-tracing-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-tracing
diff --git a/webclient/api/pom.xml b/webclient/api/pom.xml
index 4fbd24dff15..c4af5d2e73d 100644
--- a/webclient/api/pom.xml
+++ b/webclient/api/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webclienthelidon-webclient-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-apiHelidon WebClient API
diff --git a/webclient/api/src/main/java/io/helidon/webclient/api/ClientRequestBase.java b/webclient/api/src/main/java/io/helidon/webclient/api/ClientRequestBase.java
index bdd3f053203..d1cec37df6e 100644
--- a/webclient/api/src/main/java/io/helidon/webclient/api/ClientRequestBase.java
+++ b/webclient/api/src/main/java/io/helidon/webclient/api/ClientRequestBase.java
@@ -262,7 +262,7 @@ public R request() {
}
@Override
- public final R submit(Object entity) {
+ public R submit(Object entity) {
if (!(entity instanceof byte[] bytes && bytes.length == 0)) {
rejectHeadWithEntity();
}
@@ -271,7 +271,7 @@ public final R submit(Object entity) {
}
@Override
- public final R outputStream(OutputStreamHandler outputStreamConsumer) {
+ public R outputStream(OutputStreamHandler outputStreamConsumer) {
rejectHeadWithEntity();
additionalHeaders();
return doOutputStream(outputStreamConsumer);
@@ -481,7 +481,7 @@ private String resolvePathParams(String path) {
}
private void rejectHeadWithEntity() {
- if (this.method.equals(Method.HEAD)) {
+ if (Method.HEAD.equals(this.method)) {
throw new IllegalArgumentException("Payload in method '" + Method.HEAD + "' has no defined semantics");
}
}
diff --git a/webclient/dns-resolver/first/pom.xml b/webclient/dns-resolver/first/pom.xml
index e1ac0ee0398..f182b063682 100644
--- a/webclient/dns-resolver/first/pom.xml
+++ b/webclient/dns-resolver/first/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webclient.dns.resolverhelidon-webclient-dns-resolver-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-dns-resolver-first
diff --git a/webclient/dns-resolver/pom.xml b/webclient/dns-resolver/pom.xml
index 53694a73527..1e229f827a1 100644
--- a/webclient/dns-resolver/pom.xml
+++ b/webclient/dns-resolver/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webclienthelidon-webclient-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTpom
diff --git a/webclient/dns-resolver/round-robin/pom.xml b/webclient/dns-resolver/round-robin/pom.xml
index ac887964cf2..478a5f401d6 100644
--- a/webclient/dns-resolver/round-robin/pom.xml
+++ b/webclient/dns-resolver/round-robin/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webclient.dns.resolverhelidon-webclient-dns-resolver-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-dns-resolver-round-robin
diff --git a/webclient/grpc/etc/spotbugs/exclude.xml b/webclient/grpc/etc/spotbugs/exclude.xml
new file mode 100644
index 00000000000..2474da70a2c
--- /dev/null
+++ b/webclient/grpc/etc/spotbugs/exclude.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webclient/grpc/pom.xml b/webclient/grpc/pom.xml
index 5058e5c98cc..618e8559637 100644
--- a/webclient/grpc/pom.xml
+++ b/webclient/grpc/pom.xml
@@ -21,12 +21,16 @@
io.helidon.webclienthelidon-webclient-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-grpcHelidon WebClient gRPC
+
+ etc/spotbugs/exclude.xml
+
+
io.grpc
diff --git a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/ClientUriSupplier.java b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/ClientUriSupplier.java
new file mode 100644
index 00000000000..f63693ec4a9
--- /dev/null
+++ b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/ClientUriSupplier.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.webclient.grpc;
+
+import java.util.Iterator;
+
+import io.helidon.webclient.api.ClientUri;
+
+/**
+ * Interface implemented by all client URI suppliers.
+ */
+public interface ClientUriSupplier extends Iterator, Iterable {
+
+ @Override
+ default Iterator iterator() {
+ return this;
+ }
+}
diff --git a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/ClientUriSuppliers.java b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/ClientUriSuppliers.java
new file mode 100644
index 00000000000..9c279f7a370
--- /dev/null
+++ b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/ClientUriSuppliers.java
@@ -0,0 +1,203 @@
+/*
+ * 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.webclient.grpc;
+
+import java.net.URI;
+import java.security.SecureRandom;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import io.helidon.webclient.api.ClientUri;
+
+/**
+ * Some popular implementations of the {@link io.helidon.webclient.grpc.ClientUriSupplier}
+ * interface.
+ */
+public class ClientUriSuppliers {
+
+ /**
+ * Supplies an iterator that returns URIs chosen in order from
+ * first to last.
+ */
+ public static class OrderedSupplier implements ClientUriSupplier {
+
+ private final Iterator clientUris;
+
+ /**
+ * Creates an ordered supplier.
+ *
+ * @param clientUris array of client URIs
+ * @return new supplier
+ */
+ public static OrderedSupplier create(ClientUri... clientUris) {
+ return new OrderedSupplier(List.of(clientUris));
+ }
+
+ /**
+ * Creates an ordered supplier.
+ *
+ * @param clientUris collection of client URIs
+ * @return new supplier
+ */
+ public static OrderedSupplier create(Collection clientUris) {
+ return new OrderedSupplier(clientUris);
+ }
+
+ protected OrderedSupplier(Collection clientUris) {
+ this.clientUris = List.copyOf(clientUris).iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return clientUris.hasNext();
+ }
+
+ @Override
+ public ClientUri next() {
+ return clientUris.next();
+ }
+ }
+
+ /**
+ * Supplies a neven-ending iterator that returns URIs chosen using
+ * a round-robin strategy.
+ */
+ public static class RoundRobinSupplier implements ClientUriSupplier {
+
+ private int next;
+ private final ClientUri[] clientUris;
+
+ /**
+ * Creates a round-robin supplier.
+ *
+ * @param clientUris array of client URIs
+ * @return new supplier
+ */
+ public static RoundRobinSupplier create(ClientUri... clientUris) {
+ return new RoundRobinSupplier(clientUris);
+ }
+
+ /**
+ * Creates a round-robin supplier.
+ *
+ * @param clientUris collection of client URIs
+ * @return new supplier
+ */
+ public static RoundRobinSupplier create(Collection clientUris) {
+ return new RoundRobinSupplier(clientUris.toArray(new ClientUri[]{}));
+ }
+
+ protected RoundRobinSupplier(ClientUri[] clientUris) {
+ this.clientUris = clientUris;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return true;
+ }
+
+ @Override
+ public ClientUri next() {
+ return clientUris[next++ % clientUris.length];
+ }
+ }
+
+ /**
+ * Supplies the same client URI over and over, never ends.
+ */
+ public static class SingleSupplier implements ClientUriSupplier {
+
+ private final ClientUri clientUri;
+
+ /**
+ * Creates a single supplier.
+ *
+ * @param clientUri the client URI as a string
+ * @return new supplier
+ */
+ public static SingleSupplier create(String clientUri) {
+ return new SingleSupplier(ClientUri.create(URI.create(clientUri)));
+ }
+
+ /**
+ * Creates a single supplier.
+ *
+ * @param clientUri the client URI
+ * @return new supplier
+ */
+ public static SingleSupplier create(ClientUri clientUri) {
+ return new SingleSupplier(clientUri);
+ }
+
+ protected SingleSupplier(ClientUri clientUri) {
+ this.clientUri = clientUri;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return true;
+ }
+
+ @Override
+ public ClientUri next() {
+ return clientUri;
+ }
+ }
+
+ /**
+ * Supplies an iterator that returns a URI chosen at random, never ends.
+ */
+ public static class RandomSupplier implements ClientUriSupplier {
+
+ private final ClientUri[] clientUris;
+ private final SecureRandom random = new SecureRandom();
+
+ /**
+ * Creates a random supplier.
+ *
+ * @param clientUris array of client URIs
+ * @return new supplier
+ */
+ public static RandomSupplier create(ClientUri... clientUris) {
+ return new RandomSupplier(clientUris);
+ }
+
+ /**
+ * Creates a random supplier.
+ *
+ * @param clientUris collection of client URIs
+ * @return new supplier
+ */
+ public static RandomSupplier create(Collection clientUris) {
+ return new RandomSupplier(clientUris.toArray(new ClientUri[]{}));
+ }
+
+ protected RandomSupplier(ClientUri[] clientUris) {
+ this.clientUris = clientUris;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return true;
+ }
+
+ @Override
+ public ClientUri next() {
+ return clientUris[random.nextInt(clientUris.length)];
+ }
+ }
+}
diff --git a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcBaseClientCall.java b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcBaseClientCall.java
index 65ebfced73d..4186ed74597 100644
--- a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcBaseClientCall.java
+++ b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcBaseClientCall.java
@@ -66,12 +66,14 @@ abstract class GrpcBaseClientCall extends ClientCall {
protected static final BufferData EMPTY_BUFFER_DATA = BufferData.empty();
private final GrpcClientImpl grpcClient;
+ private final GrpcChannel grpcChannel;
private final MethodDescriptor methodDescriptor;
private final CallOptions callOptions;
private final int initBufferSize;
private final Duration pollWaitTime;
private final boolean abortPollTimeExpired;
private final Duration heartbeatPeriod;
+ private final ClientUriSupplier clientUriSupplier;
private final MethodDescriptor.Marshaller requestMarshaller;
private final MethodDescriptor.Marshaller responseMarshaller;
@@ -81,8 +83,9 @@ abstract class GrpcBaseClientCall extends ClientCall {
private volatile Listener responseListener;
private volatile HelidonSocket socket;
- GrpcBaseClientCall(GrpcClientImpl grpcClient, MethodDescriptor methodDescriptor, CallOptions callOptions) {
- this.grpcClient = grpcClient;
+ GrpcBaseClientCall(GrpcChannel grpcChannel, MethodDescriptor methodDescriptor, CallOptions callOptions) {
+ this.grpcClient = (GrpcClientImpl) grpcChannel.grpcClient();
+ this.grpcChannel = grpcChannel;
this.methodDescriptor = methodDescriptor;
this.callOptions = callOptions;
this.requestMarshaller = methodDescriptor.getRequestMarshaller();
@@ -91,6 +94,7 @@ abstract class GrpcBaseClientCall extends ClientCall {
this.pollWaitTime = grpcClient.prototype().protocolConfig().pollWaitTime();
this.abortPollTimeExpired = grpcClient.prototype().protocolConfig().abortPollTimeExpired();
this.heartbeatPeriod = grpcClient.prototype().protocolConfig().heartbeatPeriod();
+ this.clientUriSupplier = grpcClient.prototype().clientUriSupplier().orElse(null);
}
@Override
@@ -100,10 +104,11 @@ public void start(Listener responseListener, Metadata metadata) {
this.responseListener = responseListener;
// obtain HTTP2 connection
- ClientConnection clientConnection = clientConnection();
+ ClientUri clientUri = nextClientUri();
+ ClientConnection clientConnection = clientConnection(clientUri);
socket = clientConnection.helidonSocket();
connection = Http2ClientConnection.create((Http2ClientImpl) grpcClient.http2Client(),
- clientConnection, true);
+ clientConnection, true);
// create HTTP2 stream from connection
clientStream = new GrpcClientStream(
@@ -134,7 +139,6 @@ public Duration readTimeout() {
startStreamingThreads();
// send HEADERS frame
- ClientUri clientUri = grpcClient.prototype().baseUri().orElseThrow();
WritableHeaders> headers = WritableHeaders.create();
headers.add(Http2Headers.AUTHORITY_NAME, clientUri.authority());
headers.add(Http2Headers.METHOD_NAME, "POST");
@@ -165,11 +169,13 @@ protected void unblockUnaryExecutor() {
}
}
- protected ClientConnection clientConnection() {
- GrpcClientConfig clientConfig = grpcClient.prototype();
- ClientUri clientUri = clientConfig.baseUri().orElseThrow();
- WebClient webClient = grpcClient.webClient();
+ protected GrpcClientImpl grpcClient() {
+ return grpcClient;
+ }
+ protected ClientConnection clientConnection(ClientUri clientUri) {
+ WebClient webClient = grpcClient.webClient();
+ GrpcClientConfig clientConfig = grpcClient.prototype();
ConnectionKey connectionKey = new ConnectionKey(
clientUri.scheme(),
clientUri.host(),
@@ -179,13 +185,12 @@ protected ClientConnection clientConnection() {
DefaultDnsResolver.create(),
DnsAddressLookup.defaultLookup(),
Proxy.noProxy());
-
return TcpClientConnection.create(webClient,
- connectionKey,
- Collections.emptyList(),
- connection -> false,
- connection -> {
- }).connect();
+ connectionKey,
+ Collections.emptyList(),
+ connection -> false,
+ connection -> {
+ }).connect();
}
protected boolean isRemoteOpen() {
@@ -245,4 +250,16 @@ protected Listener responseListener() {
protected HelidonSocket socket() {
return socket;
}
+
+ /**
+ * Retrieves the next URI either from the supplier or directly from config. If
+ * a supplier is provided, it will take precedence.
+ *
+ * @return the next {@link ClientUri}
+ * @throws java.util.NoSuchElementException if supplier has been exhausted
+ */
+ private ClientUri nextClientUri() {
+ return clientUriSupplier == null ? grpcClient.prototype().baseUri().orElseThrow()
+ : clientUriSupplier.next();
+ }
}
diff --git a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcChannel.java b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcChannel.java
index f4d5e006cb7..caa4df37b78 100644
--- a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcChannel.java
+++ b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcChannel.java
@@ -53,8 +53,8 @@ public ClientCall newCall(
MethodDescriptor methodDescriptor, CallOptions callOptions) {
MethodDescriptor.MethodType methodType = methodDescriptor.getType();
return methodType == MethodDescriptor.MethodType.UNARY
- ? new GrpcUnaryClientCall<>(grpcClient, methodDescriptor, callOptions)
- : new GrpcClientCall<>(grpcClient, methodDescriptor, callOptions);
+ ? new GrpcUnaryClientCall<>(this, methodDescriptor, callOptions)
+ : new GrpcClientCall<>(this, methodDescriptor, callOptions);
}
@Override
diff --git a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClientCall.java b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClientCall.java
index 96573554350..8016ce42438 100644
--- a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClientCall.java
+++ b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClientCall.java
@@ -60,9 +60,9 @@ class GrpcClientCall extends GrpcBaseClientCall {
private volatile Future> writeStreamFuture;
private volatile Future> heartbeatFuture;
- GrpcClientCall(GrpcClientImpl grpcClient, MethodDescriptor methodDescriptor, CallOptions callOptions) {
- super(grpcClient, methodDescriptor, callOptions);
- this.executor = grpcClient.webClient().executor();
+ GrpcClientCall(GrpcChannel grpcChannel, MethodDescriptor methodDescriptor, CallOptions callOptions) {
+ super(grpcChannel, methodDescriptor, callOptions);
+ this.executor = grpcClient().webClient().executor();
}
@Override
diff --git a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClientConfigBlueprint.java b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClientConfigBlueprint.java
index 7403c76a0ad..f6dd3f678e9 100644
--- a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClientConfigBlueprint.java
+++ b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcClientConfigBlueprint.java
@@ -16,6 +16,8 @@
package io.helidon.webclient.grpc;
+import java.util.Optional;
+
import io.helidon.builder.api.Option;
import io.helidon.builder.api.Prototype;
import io.helidon.webclient.api.HttpClientConfig;
@@ -35,4 +37,13 @@ interface GrpcClientConfigBlueprint extends HttpClientConfig, Prototype.Factory<
@Option.Default("create()")
@Option.Configured
GrpcClientProtocolConfig protocolConfig();
+
+ /**
+ * A {@link io.helidon.webclient.grpc.ClientUriSupplier} that can dynamically
+ * provide zero or more {@link io.helidon.webclient.api.ClientUri}s to connect.
+ *
+ * @return a supplier for zero or more client URIs
+ */
+ Optional clientUriSupplier();
}
+
diff --git a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcUnaryClientCall.java b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcUnaryClientCall.java
index 2ce34db5d5b..355932991a6 100644
--- a/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcUnaryClientCall.java
+++ b/webclient/grpc/src/main/java/io/helidon/webclient/grpc/GrpcUnaryClientCall.java
@@ -42,10 +42,10 @@ class GrpcUnaryClientCall extends GrpcBaseClientCall {
private volatile boolean requestSent;
private volatile boolean responseSent;
- GrpcUnaryClientCall(GrpcClientImpl grpcClient,
+ GrpcUnaryClientCall(GrpcChannel grpcChannel,
MethodDescriptor methodDescriptor,
CallOptions callOptions) {
- super(grpcClient, methodDescriptor, callOptions);
+ super(grpcChannel, methodDescriptor, callOptions);
}
@Override
diff --git a/webclient/http1/pom.xml b/webclient/http1/pom.xml
index e21033c0984..4460015f3df 100644
--- a/webclient/http1/pom.xml
+++ b/webclient/http1/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webclienthelidon-webclient-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-http1
diff --git a/webclient/http2/pom.xml b/webclient/http2/pom.xml
index bf32f271791..81f96a76d0b 100644
--- a/webclient/http2/pom.xml
+++ b/webclient/http2/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webclienthelidon-webclient-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-http2
diff --git a/webclient/metrics/pom.xml b/webclient/metrics/pom.xml
index 114777762fa..9ca16cdbf69 100644
--- a/webclient/metrics/pom.xml
+++ b/webclient/metrics/pom.xml
@@ -22,7 +22,7 @@
io.helidon.webclienthelidon-webclient-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-metrics
diff --git a/webclient/pom.xml b/webclient/pom.xml
index e10e2b40719..c3c140cd471 100644
--- a/webclient/pom.xml
+++ b/webclient/pom.xml
@@ -22,7 +22,7 @@
io.helidonhelidon-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.webclienthelidon-webclient-project
diff --git a/webclient/security/pom.xml b/webclient/security/pom.xml
index f899a4f1c18..f6ae575edce 100644
--- a/webclient/security/pom.xml
+++ b/webclient/security/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webclienthelidon-webclient-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-security
diff --git a/webclient/sse/pom.xml b/webclient/sse/pom.xml
index 89f24ae20bb..93ae1a8ac6e 100644
--- a/webclient/sse/pom.xml
+++ b/webclient/sse/pom.xml
@@ -23,7 +23,7 @@
io.helidon.webclienthelidon-webclient-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-sse
diff --git a/webclient/sse/src/main/java/io/helidon/webclient/sse/SseSourceHandlerProvider.java b/webclient/sse/src/main/java/io/helidon/webclient/sse/SseSourceHandlerProvider.java
index 502bb1f8989..e8758e16977 100644
--- a/webclient/sse/src/main/java/io/helidon/webclient/sse/SseSourceHandlerProvider.java
+++ b/webclient/sse/src/main/java/io/helidon/webclient/sse/SseSourceHandlerProvider.java
@@ -25,6 +25,7 @@
import java.time.Duration;
import io.helidon.common.GenericType;
+import io.helidon.common.buffers.DataReader;
import io.helidon.common.media.type.MediaTypes;
import io.helidon.http.media.MediaContext;
import io.helidon.http.sse.SseEvent;
@@ -93,6 +94,9 @@ public > void handle(X source, HttpClientResponse res
}
}
+ source.onClose();
+ } catch (DataReader.InsufficientDataAvailableException e) {
+ // normal SSE termination when connection closed by server
source.onClose();
} catch (IOException e) {
source.onError(e);
diff --git a/webclient/tests/grpc/pom.xml b/webclient/tests/grpc/pom.xml
index 2934bb336bc..38a51ad88e2 100644
--- a/webclient/tests/grpc/pom.xml
+++ b/webclient/tests/grpc/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webclient.testshelidon-webclient-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-tests-grpc
@@ -104,6 +104,11 @@
helidon-webserver-testing-junit5-grpctest
+
+ io.helidon.fault-tolerance
+ helidon-fault-tolerance
+ test
+
diff --git a/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/ClientUriSuppliersTest.java b/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/ClientUriSuppliersTest.java
new file mode 100644
index 00000000000..e2fe0f5cf71
--- /dev/null
+++ b/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/ClientUriSuppliersTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.webclient.grpc.tests;
+
+import java.net.URI;
+import java.util.stream.IntStream;
+
+import io.helidon.webclient.api.ClientUri;
+
+import org.junit.jupiter.api.Test;
+
+import static io.helidon.webclient.grpc.ClientUriSuppliers.OrderedSupplier;
+import static io.helidon.webclient.grpc.ClientUriSuppliers.RandomSupplier;
+import static io.helidon.webclient.grpc.ClientUriSuppliers.RoundRobinSupplier;
+import static io.helidon.webclient.grpc.ClientUriSuppliers.SingleSupplier;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.collection.IsIn.isIn;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+class ClientUriSuppliersTest {
+
+ private static final ClientUri[] CLIENT_URIS = {
+ ClientUri.create(URI.create("http://localhost:8000")),
+ ClientUri.create(URI.create("http://localhost:8001")),
+ ClientUri.create(URI.create("http://localhost:8002"))
+ };
+
+ @Test
+ void testOrderedSupplier() {
+ OrderedSupplier supplier = OrderedSupplier.create(CLIENT_URIS);
+ assertThat(supplier.hasNext(), is(true));
+ assertThat(supplier.next(), is(CLIENT_URIS[0]));
+ assertThat(supplier.hasNext(), is(true));
+ assertThat(supplier.next(), is(CLIENT_URIS[1]));
+ assertThat(supplier.hasNext(), is(true));
+ assertThat(supplier.next(), is(CLIENT_URIS[2]));
+ assertThat(supplier.hasNext(), is(false));
+ }
+
+ @Test
+ void testRoundRobinSupplier() {
+ RoundRobinSupplier supplier = RoundRobinSupplier.create(CLIENT_URIS);
+ IntStream.range(0, 5).forEach(i -> {
+ assertThat(supplier.hasNext(), is(true));
+ assertThat(supplier.next(), is(CLIENT_URIS[0]));
+ assertThat(supplier.hasNext(), is(true));
+ assertThat(supplier.next(), is(CLIENT_URIS[1]));
+ assertThat(supplier.hasNext(), is(true));
+ assertThat(supplier.next(), is(CLIENT_URIS[2]));
+ });
+ }
+
+ @Test
+ void testSingleSupplier() {
+ SingleSupplier supplier = SingleSupplier.create(CLIENT_URIS[0]);
+ IntStream.range(0, 5).forEach(i -> {
+ assertThat(supplier.hasNext(), is(true));
+ assertThat(supplier.next(), is(CLIENT_URIS[0]));
+ });
+ }
+
+ @Test
+ void testRandomSupplier() {
+ RandomSupplier supplier = RandomSupplier.create(CLIENT_URIS);
+ IntStream.range(0, 5).forEach(i -> {
+ assertThat(supplier.hasNext(), is(true));
+ assertThat(supplier.next(), isIn(CLIENT_URIS));
+ });
+ }
+}
diff --git a/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcClientUriTest.java b/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcClientUriTest.java
new file mode 100644
index 00000000000..a602688d6b4
--- /dev/null
+++ b/webclient/tests/grpc/src/test/java/io/helidon/webclient/grpc/tests/GrpcClientUriTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.webclient.grpc.tests;
+
+import java.net.URI;
+import java.util.concurrent.CountDownLatch;
+
+import io.helidon.common.configurable.Resource;
+import io.helidon.common.tls.Tls;
+import io.helidon.faulttolerance.Retry;
+import io.helidon.webclient.api.ClientUri;
+import io.helidon.webclient.grpc.ClientUriSuppliers;
+import io.helidon.webclient.grpc.GrpcClient;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.testing.junit5.ServerTest;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Tests client URI suppliers.
+ */
+@ServerTest
+class GrpcClientUriTest extends GrpcBaseTest {
+
+ private final WebServer server;
+ private final Tls clientTls;
+
+ GrpcClientUriTest(WebServer server) {
+ this.server = server;
+ this.clientTls = Tls.builder()
+ .trust(trust -> trust
+ .keystore(store -> store
+ .passphrase("password")
+ .trustStore(true)
+ .keystore(Resource.create("client.p12"))))
+ .build();
+ }
+
+ /**
+ * Shows how each gRPC call advances the iterator in the {@code ClientUriSupplier}.
+ */
+ @Test
+ void testSupplier() {
+ CountDownLatch latch = new CountDownLatch(2);
+ ClientUri clientUri = ClientUri.create(URI.create("https://localhost:" + server.port()));
+ GrpcClient grpcClient = GrpcClient.builder()
+ .tls(clientTls)
+ .clientUriSupplier(new ClientUriSupplierTest(latch, clientUri, clientUri))
+ .build();
+ StringServiceGrpc.StringServiceBlockingStub service = StringServiceGrpc.newBlockingStub(grpcClient.channel());
+
+ Strings.StringMessage res1 = service.upper(newStringMessage("hello"));
+ assertThat(res1.getText(), is("HELLO"));
+ Strings.StringMessage res2 = service.upper(newStringMessage("hello"));
+ assertThat(res2.getText(), is("HELLO"));
+ assertThat(latch.getCount(), is(0L));
+ }
+
+ /**
+ * Should fail to connect to first URI but succeed with second after retrying.
+ */
+ @Test
+ void testSupplierWithRetries() {
+ CountDownLatch latch = new CountDownLatch(2);
+ ClientUri badUri = ClientUri.create(URI.create("https://foo:8000"));
+ ClientUri goodUri = ClientUri.create(URI.create("https://localhost:" + server.port()));
+ GrpcClient grpcClient = GrpcClient.builder()
+ .tls(clientTls)
+ .clientUriSupplier(new ClientUriSupplierTest(latch, badUri, goodUri))
+ .build();
+ StringServiceGrpc.StringServiceBlockingStub service = StringServiceGrpc.newBlockingStub(grpcClient.channel());
+
+ Retry retry = Retry.builder().calls(2).build();
+ Strings.StringMessage res = retry.invoke(() -> service.upper(newStringMessage("hello")));
+ assertThat(res.getText(), is("HELLO"));
+ assertThat(latch.getCount(), is(0L));
+ }
+
+ static class ClientUriSupplierTest extends ClientUriSuppliers.RoundRobinSupplier {
+
+ private final CountDownLatch latch;
+
+ ClientUriSupplierTest(CountDownLatch latch, ClientUri... clientUris) {
+ super(clientUris);
+ this.latch = latch;
+ }
+
+ @Override
+ public ClientUri next() {
+ latch.countDown(); // should be called twice
+ return super.next();
+ }
+ }
+}
diff --git a/webclient/tests/http1/pom.xml b/webclient/tests/http1/pom.xml
index cd2d23777f0..ee7a3519903 100644
--- a/webclient/tests/http1/pom.xml
+++ b/webclient/tests/http1/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webclient.testshelidon-webclient-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-tests-http1
diff --git a/webclient/tests/http2/pom.xml b/webclient/tests/http2/pom.xml
index aca82463d1c..79c4ab89ca6 100644
--- a/webclient/tests/http2/pom.xml
+++ b/webclient/tests/http2/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webclient.testshelidon-webclient-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-tests-http2
diff --git a/webclient/tests/pom.xml b/webclient/tests/pom.xml
index 31fa199711e..94a4ea7a68b 100644
--- a/webclient/tests/pom.xml
+++ b/webclient/tests/pom.xml
@@ -24,7 +24,7 @@
io.helidon.webclienthelidon-webclient-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOTio.helidon.webclient.tests
diff --git a/webclient/tests/webclient/pom.xml b/webclient/tests/webclient/pom.xml
index 855dc1e41b4..7af7e955858 100644
--- a/webclient/tests/webclient/pom.xml
+++ b/webclient/tests/webclient/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webclient.testshelidon-webclient-tests-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-tests-webclient
@@ -124,5 +124,10 @@
helidon-logging-jultest
+
+ org.mockito
+ mockito-core
+ test
+
diff --git a/webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/WebClientMockTest.java b/webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/WebClientMockTest.java
new file mode 100644
index 00000000000..a560ff22b55
--- /dev/null
+++ b/webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/WebClientMockTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.webclient.tests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import io.helidon.http.HeaderName;
+import io.helidon.http.HeaderNames;
+import io.helidon.http.Status;
+import io.helidon.webclient.api.HttpClientRequest;
+import io.helidon.webclient.api.HttpClientResponse;
+import io.helidon.webclient.api.WebClient;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests WebClientMockTest.
+ */
+class WebClientMockTest {
+
+ @Test
+ public void useMock() {
+ WebClient webClient = mockWebClient(Status.OK_200);
+ HttpClientResponse response = webClient.post("http://test.com")
+ .header(HeaderNames.AUTHORIZATION, "Bearer asdsadsad")
+ .submit(new Object());
+ assertEquals(Status.OK_200, response.status());
+ }
+
+ private WebClient mockWebClient(Status status) {
+ WebClient webClient = mock(WebClient.class);
+ HttpClientRequest request = mock(HttpClientRequest.class);
+ HttpClientResponse response = mock(HttpClientResponse.class);
+ doReturn(request).when(webClient).post(anyString());
+ doReturn(request).when(request).header(any(HeaderName.class), anyString());
+ doReturn(response).when(request).submit(any(Object.class));
+ when(response.status()).thenReturn(status);
+ return webClient;
+ }
+
+}
diff --git a/webclient/tracing/pom.xml b/webclient/tracing/pom.xml
index 98712d72b4d..8465e55e252 100644
--- a/webclient/tracing/pom.xml
+++ b/webclient/tracing/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webclienthelidon-webclient-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-tracing
diff --git a/webclient/webclient/pom.xml b/webclient/webclient/pom.xml
index 8c7bdc65f89..45c8e5cd5c8 100644
--- a/webclient/webclient/pom.xml
+++ b/webclient/webclient/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webclienthelidon-webclient-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient
diff --git a/webclient/websocket/pom.xml b/webclient/websocket/pom.xml
index 766e5fe7b93..b791bb7b310 100644
--- a/webclient/websocket/pom.xml
+++ b/webclient/websocket/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webclienthelidon-webclient-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webclient-websocket
diff --git a/webserver/access-log/pom.xml b/webserver/access-log/pom.xml
index 4e083d987ce..f33b809c1d2 100644
--- a/webserver/access-log/pom.xml
+++ b/webserver/access-log/pom.xml
@@ -21,7 +21,7 @@
io.helidon.webserverhelidon-webserver-project
- 4.1.0-SNAPSHOT
+ 4.2.0-SNAPSHOThelidon-webserver-access-log
diff --git a/webserver/concurrency-limits/pom.xml b/webserver/concurrency-limits/pom.xml
new file mode 100644
index 00000000000..61bb4bb3010
--- /dev/null
+++ b/webserver/concurrency-limits/pom.xml
@@ -0,0 +1,129 @@
+
+
+
+ 4.0.0
+
+ io.helidon.webserver
+ helidon-webserver-project
+ 4.2.0-SNAPSHOT
+
+
+ helidon-webserver-concurrency-limits
+ Helidon WebServer Concurrency Limits
+ Feature that adds filters for concurrency limits
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.common.concurrency
+ helidon-common-concurrency-limits
+
+
+ io.helidon.common
+ helidon-common-config
+
+
+ helidon-builder-api
+ io.helidon.builder
+
+
+ io.helidon.webserver.testing.junit5
+ helidon-webserver-testing-junit5
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.logging
+ helidon-logging-jul
+ test
+
+
+ io.helidon.config
+ helidon-config-yaml
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ io.helidon.config.metadata
+ helidon-config-metadata-codegen
+ ${helidon.version}
+
+
+ io.helidon.codegen
+ helidon-codegen-apt
+ ${helidon.version}
+
+
+ io.helidon.builder
+ helidon-builder-codegen
+ ${helidon.version}
+
+
+ io.helidon.codegen
+ helidon-codegen-helidon-copyright
+ ${helidon.version}
+
+
+
+
+
+ io.helidon.config.metadata
+ helidon-config-metadata-codegen
+ ${helidon.version}
+
+
+ io.helidon.codegen
+ helidon-codegen-apt
+ ${helidon.version}
+
+
+ io.helidon.builder
+ helidon-builder-codegen
+ ${helidon.version}
+
+
+ io.helidon.codegen
+ helidon-codegen-helidon-copyright
+ ${helidon.version}
+
+
+
+
+
+
diff --git a/webserver/concurrency-limits/src/main/java/io/helidon/webserver/concurrency/limits/LimitsFeature.java b/webserver/concurrency-limits/src/main/java/io/helidon/webserver/concurrency/limits/LimitsFeature.java
new file mode 100644
index 00000000000..62f77a2cb10
--- /dev/null
+++ b/webserver/concurrency-limits/src/main/java/io/helidon/webserver/concurrency/limits/LimitsFeature.java
@@ -0,0 +1,147 @@
+/*
+ * 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.webserver.concurrency.limits;
+
+import java.util.Set;
+import java.util.function.Consumer;
+
+import io.helidon.builder.api.RuntimeType;
+import io.helidon.common.Weighted;
+import io.helidon.common.config.Config;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.spi.ServerFeature;
+
+/**
+ * Server feature that adds limits as filters.
+ *
+ * When using this feature, the limits operation is enforced within a filter, i.e. after the request
+ * is accepted. This means it is used only for HTTP requests.
+ */
+@RuntimeType.PrototypedBy(LimitsFeatureConfig.class)
+public class LimitsFeature implements ServerFeature, Weighted, RuntimeType.Api {
+ /**
+ * Default weight of this feature. It is the first feature to be registered (above context and access log).
+ *
+ * Context: 1100
+ *
+ * Access Log: 1000
+ *
+ * This feature: {@value}
+ */
+ public static final double WEIGHT = 2000;
+ static final String ID = "limits";
+
+ private final LimitsFeatureConfig config;
+
+ private LimitsFeature(LimitsFeatureConfig config) {
+ this.config = config;
+ }
+
+ /**
+ * Fluent API builder to set up an instance.
+ *
+ * @return a new builder
+ */
+ public static LimitsFeatureConfig.Builder builder() {
+ return LimitsFeatureConfig.builder();
+ }
+
+ /**
+ * Create a new instance from its configuration.
+ *
+ * @param config configuration
+ * @return a new feature
+ */
+ public static LimitsFeature create(LimitsFeatureConfig config) {
+ return new LimitsFeature(config);
+ }
+
+ /**
+ * Create a new instance customizing its configuration.
+ *
+ * @param builderConsumer consumer of configuration
+ * @return a new feature
+ */
+ public static LimitsFeature create(Consumer builderConsumer) {
+ return builder()
+ .update(builderConsumer)
+ .build();
+ }
+
+ /**
+ * Create a new limits feature with default setup, but enabled.
+ *
+ * @return a new feature
+ */
+ public static LimitsFeature create() {
+ return builder()
+ .enabled(true)
+ .build();
+ }
+
+ /**
+ * Create a new context feature with custom setup.
+ *
+ * @param config configuration
+ * @return a new configured feature
+ */
+ public static LimitsFeature create(Config config) {
+ return builder()
+ .config(config)
+ .build();
+ }
+
+ @Override
+ public void setup(ServerFeatureContext featureContext) {
+ double featureWeight = config.weight();
+ // all sockets
+ Set sockets = config.sockets();
+ if (sockets.isEmpty()) {
+ // configure on default only
+ featureContext.socket(WebServer.DEFAULT_SOCKET_NAME)
+ .httpRouting()
+ .addFeature(new LimitsRoutingFeature(config, featureWeight));
+ } else {
+ // configure on all configured
+ for (String socket : sockets) {
+ featureContext.socket(socket)
+ .httpRouting()
+ .addFeature(new LimitsRoutingFeature(config, featureWeight));
+ }
+ }
+ }
+
+ @Override
+ public String name() {
+ return config.name();
+ }
+
+ @Override
+ public String type() {
+ return ID;
+ }
+
+ @Override
+ public double weight() {
+ return config.weight();
+ }
+
+ @Override
+ public LimitsFeatureConfig prototype() {
+ return config;
+ }
+}
diff --git a/webserver/concurrency-limits/src/main/java/io/helidon/webserver/concurrency/limits/LimitsFeatureConfigBlueprint.java b/webserver/concurrency-limits/src/main/java/io/helidon/webserver/concurrency/limits/LimitsFeatureConfigBlueprint.java
new file mode 100644
index 00000000000..dc681c4c172
--- /dev/null
+++ b/webserver/concurrency-limits/src/main/java/io/helidon/webserver/concurrency/limits/LimitsFeatureConfigBlueprint.java
@@ -0,0 +1,76 @@
+/*
+ * 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.webserver.concurrency.limits;
+
+import java.util.Optional;
+import java.util.Set;
+
+import io.helidon.builder.api.Option;
+import io.helidon.builder.api.Prototype;
+import io.helidon.common.concurrency.limits.Limit;
+import io.helidon.common.concurrency.limits.spi.LimitProvider;
+import io.helidon.webserver.spi.ServerFeatureProvider;
+
+@Prototype.Blueprint
+@Prototype.Configured(value = LimitsFeature.ID, root = false)
+@Prototype.Provides(ServerFeatureProvider.class)
+interface LimitsFeatureConfigBlueprint extends Prototype.Factory {
+ /**
+ * Weight of the context feature. As it is used by other features, the default is quite high:
+ * {@value LimitsFeature#WEIGHT}.
+ *
+ * @return weight of the feature
+ */
+ @Option.DefaultDouble(LimitsFeature.WEIGHT)
+ @Option.Configured
+ double weight();
+
+ /**
+ * List of sockets to register this feature on. If empty, it would get registered on all sockets.
+ *
+ * @return socket names to register on, defaults to empty (all available sockets)
+ */
+ @Option.Configured
+ Set sockets();
+
+ /**
+ * Name of this instance.
+ *
+ * @return instance name
+ */
+ @Option.Default(LimitsFeature.ID)
+ String name();
+
+ /**
+ * Concurrency limit to use to limit concurrent execution of incoming requests.
+ * The default is to have unlimited concurrency.
+ *
+ * @return concurrency limit
+ */
+ @Option.Provider(value = LimitProvider.class, discoverServices = false)
+ @Option.Configured
+ Optional concurrencyLimit();
+
+ /**
+ * Whether this feature is enabled, defaults to {@code true}.
+ *
+ * @return whether to enable this feature
+ */
+ @Option.DefaultBoolean(true)
+ @Option.Configured
+ boolean enabled();
+}
diff --git a/webserver/concurrency-limits/src/main/java/io/helidon/webserver/concurrency/limits/LimitsFeatureProvider.java b/webserver/concurrency-limits/src/main/java/io/helidon/webserver/concurrency/limits/LimitsFeatureProvider.java
new file mode 100644
index 00000000000..5d26cf8e461
--- /dev/null
+++ b/webserver/concurrency-limits/src/main/java/io/helidon/webserver/concurrency/limits/LimitsFeatureProvider.java
@@ -0,0 +1,58 @@
+/*
+ * 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.webserver.concurrency.limits;
+
+import io.helidon.common.Weight;
+import io.helidon.common.config.Config;
+import io.helidon.webserver.spi.ServerFeatureProvider;
+
+/**
+ * {@link java.util.ServiceLoader} provider implementation to automatically register this service.
+ *
+ * The required configuration (disabled by default):
+ *